Exemple #1
0
        private static IContainer RegisterDependencies()
        {
            var containerBuilder = new ContainerBuilder();


            containerBuilder.Register(c =>
            {
                var logger = new TraceLogger();

                var store = AzureWebHookStore.CreateStore(logger);

                var sender = new DataflowWebHookSender(logger);

                var manager = new WebHookManager(store, sender, logger);

                return(manager);
            })
            .As <IWebHookManager>()
            .InstancePerLifetimeScope();

            containerBuilder.RegisterType <Functions>()
            .AsSelf()
            .InstancePerLifetimeScope();

            return(containerBuilder.Build());
        }
        public async Task <ActionResult> Delete(string id = null, string listId = null)
        {
            var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);

            using (var cc = spContext.CreateUserClientContextForSPHost())
            {
                if (cc != null)
                {
                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;
                    // Just load the Url property to trigger the ExecutingWebRequest event handler to fire
                    cc.Load(cc.Web, w => w.Url);
                    cc.ExecuteQueryRetry();

                    var webHookManager = new WebHookManager();
                    // delete the web hook
                    if (await webHookManager.DeleteListWebHookAsync(cc.Web.Url, listId, id, this.accessToken))
                    {
                        using (var dbContext = new SharePointWebHooks())
                        {
                            var webHookRow = await dbContext.ListWebHooks.FindAsync(new Guid(id));

                            if (webHookRow != null)
                            {
                                dbContext.ListWebHooks.Remove(webHookRow);
                                var saveResult = await dbContext.SaveChangesAsync();
                            }
                        }
                    }
                }
            }

            return(RedirectToAction($"Index", $"Home", new { SPHostUrl = Request.QueryString[$"SPHostUrl"] }));
        }
        public async Task <ActionResult> Delete(string id = null, string listId = null)
        {
            using (var cc = ContextProvider.GetWebApplicationClientContext(Settings.SiteCollection))
            {
                if (cc != null)
                {
                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;
                    // Just load the Url property to trigger the ExecutingWebRequest event handler to fire
                    cc.Load(cc.Web, w => w.Url);
                    cc.ExecuteQueryRetry();

                    WebHookManager webHookManager = new WebHookManager();
                    // delete the web hook
                    if (await webHookManager.DeleteListWebHookAsync(cc.Web.Url, listId, id, this.accessToken))
                    {
                        using (SharePointWebHooks dbContext = new SharePointWebHooks())
                        {
                            var webHookRow = await dbContext.ListWebHooks.FindAsync(new Guid(id));

                            if (webHookRow != null)
                            {
                                dbContext.ListWebHooks.Remove(webHookRow);
                                var saveResult = await dbContext.SaveChangesAsync();
                            }
                        }
                    }
                }
            }

            return(RedirectToAction("Index", "Home"));
        }
        public async Task <ActionResult> Create(string selectedSharePointList)
        {
            using (var cc = ContextProvider.GetWebApplicationClientContext(Settings.SiteCollection))
            {
                if (cc != null)
                {
                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;

                    ListCollection     lists           = cc.Web.Lists;
                    Guid               listId          = new Guid(selectedSharePointList);
                    IEnumerable <List> sharePointLists = cc.LoadQuery <List>(lists.Where(lst => lst.Id == listId));
                    cc.Load(cc.Web, w => w.Url);
                    cc.ExecuteQueryRetry();

                    WebHookManager webHookManager = new WebHookManager();
                    var            res            = await webHookManager.AddListWebHookAsync(cc.Web.Url, listId.ToString(), CloudConfigurationManager.GetSetting("WebHookEndPoint"), this.accessToken);

                    // persist the latest changetoken of the list when we create a new webhook. This allows use to only grab the changes as of web hook creation when the first notification comes in
                    using (SharePointWebHooks dbContext = new SharePointWebHooks())
                    {
                        dbContext.ListWebHooks.Add(new ListWebHooks()
                        {
                            Id              = new Guid(res.Id),
                            ListId          = listId,
                            LastChangeToken = sharePointLists.FirstOrDefault().CurrentChangeToken.StringValue,
                        });
                        var saveResult = await dbContext.SaveChangesAsync();
                    }
                }
            }

            return(RedirectToAction("Index", "Home"));
        }
Exemple #5
0
        private void Form1_Load(object sender, EventArgs e)
        {
            //設定 Logger
            Logger = new TraceLogger();
            //設定 SQL 來存 WebHook 的資料
            Store = SqlWebHookStore.CreateStore(Logger);
            //Sender 使用 DataflowWebHookSender
            var webhookSender = new DataflowWebHookSender(Logger);

            Manager = new WebHookManager(Store, webhookSender, Logger);
        }
Exemple #6
0
        public void GetWorkItems_FilterSingleNotification(IEnumerable <WebHook> webHooks, IWebHookNotification notification)
        {
            // Act
            IEnumerable <WebHookWorkItem> actual = WebHookManager.GetWorkItems(webHooks.ToArray(), new[] { notification });

            // Assert
            Assert.Equal(webHooks.Count(), actual.Count());
            foreach (WebHookWorkItem workItem in actual)
            {
                Assert.Same(workItem.Notifications.Single(), notification);
            }
        }
Exemple #7
0
        public async Task VerifyWebHookAsync_Throws_IEmptySuccessResponse()
        {
            // Arrange
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) => Task.FromResult(_response);
            WebHook webHook = CreateWebHook();

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal("The WebHook URI did not return the expected echo query parameter value in a plain text response body. This is necessary to ensure that the WebHook is connected correctly.", ex.Message);
        }
Exemple #8
0
        public async Task VerifyWebHookAsync_Throws_IfHttpClientThrows()
        {
            // Arrange
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) => { throw new Exception("Catch this!"); };
            WebHook webHook = CreateWebHook();

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal("WebHook verification failed. Please ensure that the WebHook URI is valid and that the endpoint is accessible. Error encountered: Catch this!", ex.Message);
        }
Exemple #9
0
        public async Task VerifyWebHookAsync_Throws_IfEchoDoesNotMatch()
        {
            // Arrange
            _response.Content    = new StringContent("Hello World");
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) => Task.FromResult(_response);
            WebHook webHook = CreateWebHook();

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal("The HTTP request echo query parameter was not returned as plain text in the response. Please return the echo parameter to verify that the WebHook is working as expected.", ex.Message);
        }
Exemple #10
0
        public async Task VerifyWebHookAsync_Throws_INotSuccessResponse()
        {
            // Arrange
            _response.StatusCode = HttpStatusCode.NotFound;
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) => Task.FromResult(_response);
            WebHook webHook = CreateWebHook();

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal("WebHook verification failed. Please ensure that the WebHook URI is valid and that the endpoint is accessible. Error encountered: NotFound", ex.Message);
        }
Exemple #11
0
        public async Task VerifyWebHookAsync_Throws_IfNotHttpOrHttpsUri(string webHookUri)
        {
            // Arrange
            _manager = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            WebHook webHook = CreateWebHook();

            webHook.WebHookUri = webHookUri != null ? new Uri(webHookUri) : null;

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal(string.Format("The WebHook URI must be absolute with a scheme of either 'http' or 'https' but received '{0}'.", webHook.WebHookUri), ex.Message);
        }
Exemple #12
0
        public async Task VerifyWebHookAsync_Throws_IfInvalidWebHookSecret(string secret)
        {
            // Arrange
            _manager = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            WebHook webHook = CreateWebHook();

            webHook.Secret = secret;

            // Act
            InvalidOperationException ex = await Assert.ThrowsAsync <InvalidOperationException>(() => _manager.VerifyWebHookAsync(webHook));

            // Assert
            Assert.Equal("The WebHook secret key parameter must be between 32 and 64 characters long.", ex.Message);
        }
Exemple #13
0
        public void GetWorkItems_FilterMultipleNotifications(IEnumerable <WebHook> webHooks, IEnumerable <IWebHookNotification> notifications, int expected)
        {
            // Act
            IEnumerable <WebHookWorkItem> actual = WebHookManager.GetWorkItems(webHooks.ToArray(), notifications.ToArray());

            // Assert
            Assert.Equal(expected, actual.Count());
            foreach (WebHookWorkItem workItem in actual)
            {
                foreach (IWebHookNotification notification in workItem.Notifications)
                {
                    Assert.True(workItem.WebHook.MatchesAction(notification.Action));
                }
            }
        }
Exemple #14
0
        public async Task VerifyWebHookAsync_Succeeds_EchoResponse()
        {
            // Arrange
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) =>
            {
                Dictionary <string, StringValues> query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(req.RequestUri.Query);
                _response.Content = new StringContent(query["echo"]);
                return(Task.FromResult(_response));
            };
            WebHook webHook = CreateWebHook();

            // Act
            await _manager.VerifyWebHookAsync(webHook);
        }
Exemple #15
0
        public async Task VerifyWebHookAsync_Succeeds_EchoResponse()
        {
            // Arrange
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) =>
            {
                var query = QueryHelpers.ParseQuery(req.RequestUri.Query);
                query.TryGetValue("echo", out var echo);
                _response.Content = new StringContent(echo);
                return(Task.FromResult(_response));
            };
            WebHook webHook = CreateWebHook();

            // Act
            await _manager.VerifyWebHookAsync(webHook);
        }
        public static IWebHookManager GetManager(IWebHookStore store, ILogger logger)
        {
            if (_manager != null)
            {
                return(_manager);
            }
            if (store == null)
            {
                throw new ArgumentNullException("store");
            }
            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }

            IWebHookManager instance = new WebHookManager(store, logger);

            Interlocked.CompareExchange(ref _manager, instance, null);
            return(_manager);
        }
Exemple #17
0
        public async Task VerifyWebHookAsync_Stops_IfNoEchoParameter(string query)
        {
            // Arrange
            bool error = false;

            _response.Content    = new StringContent("Hello World");
            _manager             = new WebHookManager(_storeMock.Object, _senderMock.Object, _loggerMock.Object, _httpClient);
            _handlerMock.Handler = (req, counter) =>
            {
                error = true;
                return(Task.FromResult(_response));
            };
            WebHook webHook = CreateWebHook();

            webHook.WebHookUri = new Uri("http://localhost/hook?" + query);

            // Act
            await _manager.VerifyWebHookAsync(webHook);

            // Assert
            Assert.False(error);
        }
        public static void ProcessNotification(ILogger log, string queueItem)
        {
            NotificationModel notification = JsonConvert.DeserializeObject <NotificationModel>(queueItem);

            log.LogInformation($"Processing notification: {notification.Resource}");
            #region Get Context
            string url = string.Format($"https://{CloudConfigurationManager.GetSetting("SP_Tenant_Name")}.sharepoint.com{notification.SiteUrl}");
            OfficeDevPnP.Core.AuthenticationManager am = new AuthenticationManager();
            ClientContext cc = am.GetAppOnlyAuthenticatedContext(
                url,
                CloudConfigurationManager.GetSetting("SPApp_ClientId"),
                CloudConfigurationManager.GetSetting("SPApp_ClientSecret"));
            #endregion

            #region Grab the list for which the web hook was triggered
            List changeList = cc.Web.GetListById(new Guid(notification.Resource));
            cc.ExecuteQueryRetry();
            if (changeList == null)
            {
                // list has been deleted in between the event being fired and the event being processed
                log.LogInformation($"List \"{notification.Resource}\" no longer exists.");
                return;
            }
            #endregion

            #region Get the Last Change Token from the Azure table
            CloudStorageAccount storageAccount =
                CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("AzureWebJobsStorage"));
            CloudTableClient client = storageAccount.CreateCloudTableClient();
            CloudTable       table  =
                client.GetTableReference(CloudConfigurationManager.GetSetting("LastChangeTokensTableName"));
            table.CreateIfNotExists();
            TableOperation retrieveOperation = TableOperation.Retrieve <LastChangeEntity>("LastChangeToken", notification.Resource);
            TableResult    query             = table.Execute(retrieveOperation);

            ChangeToken lastChangeToken = null;
            if (query.Result != null)
            {
                lastChangeToken = new ChangeToken()
                {
                    StringValue = ((LastChangeEntity)query.Result).LastChangeToken
                };
            }
            if (lastChangeToken == null)
            {
                lastChangeToken = new ChangeToken()
                {
                    StringValue = $"1;3;{notification.Resource};{DateTime.Now.AddMinutes(-60).ToUniversalTime().Ticks.ToString()};-1"
                };
            }
            #endregion

            #region Grab Changes since Last Change Token (in batches)
            ChangeQuery changeQuery = new ChangeQuery(false, true)
            {
                Item       = true,
                FetchLimit = 1000 // Max value is 2000, default = 1000
            };
            //List will keep track of items we have already handled
            List <int> handledListItems = new List <int>();

            // Start pulling down the changes
            bool allChangesRead = false;
            do
            {
                //Assign the change token to the query...this determines from what point in time we'll receive changes
                changeQuery.ChangeTokenStart = lastChangeToken;
                ChangeCollection changes = changeList.GetChanges(changeQuery);
                cc.Load(changes);
                cc.ExecuteQueryRetry();
                #endregion
                // Save last used changetoken to Azure table. We'll start from that one when the next event hits our service
                if (changes.Count > 0)
                {
                    foreach (Change change in changes)
                    {
                        lastChangeToken = change.ChangeToken;
                    }
                }
                LastChangeEntity lce = new LastChangeEntity("LastChangeToken", notification.Resource)
                {
                    LastChangeToken = lastChangeToken.StringValue
                };
                TableOperation insertOperation = TableOperation.InsertOrReplace(lce);
                table.Execute(insertOperation);

                #region Process changes
                log.LogInformation($"Changes found: {changes.Count}");
                if (changes.Count > 0)
                {
                    try
                    {
                        foreach (Change change in changes)
                        {
                            if (change is ChangeItem)
                            {
                                var listItemId = (change as ChangeItem).ItemId;
                                log.LogInformation($"-Item that changed: ItemId: {listItemId}");
                                if (handledListItems.Contains(listItemId))
                                {
                                    log.LogInformation("-ListItem already handled in this batch.");
                                }
                                else
                                {
                                    //DO SOMETHING WITH LIST ITEM
                                    DoWork(log, cc, changeList, change);
                                }
                                RecordChangeInWebhookHistory(cc, changeList, change, log, notification.Resource);
                                handledListItems.Add(listItemId);
                            }
                            lastChangeToken = change.ChangeToken;
                        }
                        if (changes.Count < changeQuery.FetchLimit)
                        {
                            allChangesRead = true;
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception($"ERROR: {ex.Message}");
                    }
                }
                else
                {
                    allChangesRead = true;
                }
                // Are we done?
            } while (allChangesRead == false);
            #endregion

            #region "Update" the web hook expiration date when needed
            // Optionally add logic to "update" the expirationdatetime of the web hook
            // If the web hook is about to expire within the coming 5 days then prolong it
            try
            {
                if (notification.ExpirationDateTime.AddDays(-5) < DateTime.Now)
                {
                    DateTime newDate = DateTime.Now.AddMonths(3);
                    log.LogInformation($"Updating the Webhook expiration date to {newDate}");
                    WebHookManager webHookManager = new WebHookManager();
                    Task <bool>    updateResult   = Task.WhenAny(
                        webHookManager.UpdateListWebHookAsync(
                            url,
                            changeList.Id.ToString(),
                            notification.SubscriptionId,
                            CloudConfigurationManager.GetSetting("AzureWebJobsStorage"),
                            newDate,
                            cc.GetAccessToken())
                        ).Result;

                    if (updateResult.Result == false)
                    {
                        throw new Exception(string.Format("The expiration date of web hook {0} with endpoint {1} could not be updated", notification.SubscriptionId, CloudConfigurationManager.GetSetting("WebHookEndPoint")));
                    }
                }
            }
            catch (Exception ex)
            {
                log.LogInformation($"ERROR: {ex.Message}");
                //throw new Exception($"ERROR: {ex.Message}");
            }
            #endregion

            cc.Dispose();
            log.LogInformation("Processing complete.");
        }
        /// <summary>
        /// Controller servicing data for the add-in's home page
        /// </summary>
        /// <returns></returns>
        public async Task <ActionResult> Index()
        {
            using (var cc = ContextProvider.GetWebApplicationClientContext(Settings.SiteCollection))
            {
                if (cc != null)
                {
                    // Usage tracking
                    SampleUsageTracking(cc);

                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;

                    var lists = cc.Web.Lists;
                    cc.Load(cc.Web, w => w.Url);
                    cc.Load(lists, l => l.Include(p => p.Title, p => p.Id, p => p.Hidden));
                    cc.ExecuteQueryRetry();

                    WebHookManager webHookManager = new WebHookManager();

                    // Grab the current lists
                    List <SharePointList>    modelLists = new List <SharePointList>();
                    List <SubscriptionModel> webHooks   = new List <SubscriptionModel>();

                    foreach (var list in lists)
                    {
                        // Let's only take the hidden lists
                        if (!list.Hidden)
                        {
                            modelLists.Add(new SharePointList()
                            {
                                Title = list.Title, Id = list.Id
                            });

                            // Grab the currently applied web hooks
                            var existingWebHooks = await webHookManager.GetListWebHooksAsync(cc.Web.Url, list.Id.ToString(), this.accessToken);

                            if (existingWebHooks.Value.Count > 0)
                            {
                                foreach (var existingWebHook in existingWebHooks.Value)
                                {
                                    webHooks.Add(existingWebHook);
                                }
                            }
                        }
                    }

                    // Prepare the data model
                    SharePointSiteModel sharePointSiteModel = new SharePointSiteModel();
                    sharePointSiteModel.SharePointSite         = Settings.SiteCollection;
                    sharePointSiteModel.Lists                  = modelLists;
                    sharePointSiteModel.WebHooks               = webHooks;
                    sharePointSiteModel.SelectedSharePointList = modelLists[0].Id;

                    return(View(sharePointSiteModel));
                }
                else
                {
                    throw new Exception("Issue with obtaining a valid client context object, should not happen");
                }
            }
        }
Exemple #20
0
        public static IWebHookManager GetManager(IWebHookStore store, IWebHookSender sender, ILogger logger)
        {
            if (_manager != null)
            {
                return _manager;
            }
            if (store == null)
            {
                throw new ArgumentNullException("store");
            }
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }

            IWebHookManager instance = new WebHookManager(store, sender, logger);
            Interlocked.CompareExchange(ref _manager, instance, null);
            return _manager;
        }