diff --git a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer201909Tests.cs b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer201909Tests.cs index 33bad25c6..ca3d58a07 100644 --- a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer201909Tests.cs +++ b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer201909Tests.cs @@ -3137,6 +3137,7 @@ public void XMLSerializer_Deserialize_ListInstances() Assert.AreEqual("fake-template.stp", list.TemplateInternalName); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("ABC", list.Webhooks[0].ClientState); // security var security = list.Security; @@ -3413,7 +3414,8 @@ public void XMLSerializer_Serialize_ListInstances() list.Webhooks.Add(new PnP.Framework.Provisioning.Model.Webhook { ExpiresInDays = 120, - ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener" + ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", + ClientState = "CBA" }); list.IRMSettings = new PnP.Framework.Provisioning.Model.IRMSettings @@ -3668,6 +3670,7 @@ public void XMLSerializer_Serialize_ListInstances() Assert.AreEqual("/Lists/ProjectDocuments", l.Url); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("CBA", list.Webhooks[0].ClientState); Assert.IsNotNull(l.Security); var security = l.Security.BreakRoleInheritance; @@ -4799,6 +4802,7 @@ public void XMLSerializer_Deserialize_SiteWebhooks() Assert.AreEqual(120, webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("DEF", webhooks[0].ClientState); } [TestMethod] @@ -4813,7 +4817,8 @@ public void XMLSerializer_Serialize_SiteWebhooks() { SiteWebhookType = SiteWebhookType.WebCreated, ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", - ExpiresInDays = 120 + ExpiresInDays = 120, + ClientState = "FED" }); var serializer = new XMLPnPSchemaV201909Serializer(); @@ -4833,6 +4838,7 @@ public void XMLSerializer_Serialize_SiteWebhooks() Assert.AreEqual("120", webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookSiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("FED", webhooks[0].ClientState); } [TestMethod] diff --git a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202002Tests.cs b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202002Tests.cs index b9959e5ae..b30a4d8da 100644 --- a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202002Tests.cs +++ b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202002Tests.cs @@ -3238,6 +3238,7 @@ public void XMLSerializer_Deserialize_ListInstances() Assert.AreEqual("fake-template.stp", list.TemplateInternalName); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("ABC", list.Webhooks[0].ClientState); // security var security = list.Security; @@ -3514,7 +3515,8 @@ public void XMLSerializer_Serialize_ListInstances() list.Webhooks.Add(new PnP.Framework.Provisioning.Model.Webhook { ExpiresInDays = 120, - ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener" + ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", + ClientState = "CBA" }); list.IRMSettings = new PnP.Framework.Provisioning.Model.IRMSettings @@ -3769,6 +3771,7 @@ public void XMLSerializer_Serialize_ListInstances() Assert.AreEqual("/Lists/ProjectDocuments", l.Url); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("CBA", list.Webhooks[0].ClientState); Assert.IsNotNull(l.Security); var security = l.Security.BreakRoleInheritance; @@ -4900,6 +4903,7 @@ public void XMLSerializer_Deserialize_SiteWebhooks() Assert.AreEqual(120, webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("DEF", webhooks[0].ClientState); } [TestMethod] @@ -4914,7 +4918,8 @@ public void XMLSerializer_Serialize_SiteWebhooks() { SiteWebhookType = SiteWebhookType.WebCreated, ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", - ExpiresInDays = 120 + ExpiresInDays = 120, + ClientState = "FED" }); var serializer = new XMLPnPSchemaV202002Serializer(); @@ -4934,6 +4939,7 @@ public void XMLSerializer_Serialize_SiteWebhooks() Assert.AreEqual("120", webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookSiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("FED", webhooks[0].ClientState); } [TestMethod] diff --git a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202103Tests.cs b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202103Tests.cs index 4ad027a82..fa4e9969c 100644 --- a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202103Tests.cs +++ b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202103Tests.cs @@ -3403,6 +3403,7 @@ public void XMLSerializer_Deserialize_ListInstances() Assert.AreEqual("fake-template.stp", list.TemplateInternalName); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("ABC", list.Webhooks[0].ClientState); // security var security = list.Security; @@ -3687,7 +3688,8 @@ public void XMLSerializer_Serialize_ListInstances() list.Webhooks.Add(new PnP.Framework.Provisioning.Model.Webhook { ExpiresInDays = 120, - ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener" + ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", + ClientState = "CBA" }); list.IRMSettings = new PnP.Framework.Provisioning.Model.IRMSettings @@ -3942,6 +3944,7 @@ public void XMLSerializer_Serialize_ListInstances() Assert.AreEqual("/Lists/ProjectDocuments", l.Url); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("CBA", list.Webhooks[0].ClientState); Assert.IsNotNull(l.Security); var security = l.Security.BreakRoleInheritance; @@ -5084,6 +5087,7 @@ public void XMLSerializer_Deserialize_SiteWebhooks() Assert.AreEqual(120, webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("DEF", webhooks[0].ClientState); } [TestMethod] @@ -5098,7 +5102,8 @@ public void XMLSerializer_Serialize_SiteWebhooks() { SiteWebhookType = SiteWebhookType.WebCreated, ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", - ExpiresInDays = 120 + ExpiresInDays = 120, + ClientState = "FED" }); var serializer = new XMLPnPSchemaV202103Serializer(); @@ -5118,6 +5123,7 @@ public void XMLSerializer_Serialize_SiteWebhooks() Assert.AreEqual("120", webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookSiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("FED", webhooks[0].ClientState); } [TestMethod] diff --git a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202209Tests.cs b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202209Tests.cs index f84a066e7..a6201186c 100644 --- a/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202209Tests.cs +++ b/src/lib/PnP.Framework.Test/Framework/Providers/XMLSerializer202209Tests.cs @@ -3454,6 +3454,7 @@ public void XMLSerializer_Deserialize_ListInstances() Assert.IsTrue(list.EnableClassicAudienceTargeting); Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("ABC", list.Webhooks[0].ClientState); // security var security = list.Security; @@ -3754,7 +3755,8 @@ public void XMLSerializer_Serialize_ListInstances() list.Webhooks.Add(new PnP.Framework.Provisioning.Model.Webhook { ExpiresInDays = 120, - ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener" + ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", + ClientState = "CBA" }); list.IRMSettings = new PnP.Framework.Provisioning.Model.IRMSettings @@ -4026,6 +4028,7 @@ public void XMLSerializer_Serialize_ListInstances() Assert.AreEqual(120, list.Webhooks[0].ExpiresInDays); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", list.Webhooks[0].ServerNotificationUrl); + Assert.AreEqual("CBA", list.Webhooks[0].ClientState); Assert.IsNotNull(l.Security); var security = l.Security.BreakRoleInheritance; @@ -5168,6 +5171,7 @@ public void XMLSerializer_Deserialize_SiteWebhooks() Assert.AreEqual(120, webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("DEF", webhooks[0].ClientState); } [TestMethod] @@ -5182,7 +5186,8 @@ public void XMLSerializer_Serialize_SiteWebhooks() { SiteWebhookType = SiteWebhookType.WebCreated, ServerNotificationUrl = "http://myapp.azurewebsites.net/WebHookListener", - ExpiresInDays = 120 + ExpiresInDays = 120, + ClientState = "FED" }); var serializer = new TargetSerializer(); @@ -5202,6 +5207,7 @@ public void XMLSerializer_Serialize_SiteWebhooks() Assert.AreEqual("120", webhooks[0].ExpiresInDays); Assert.AreEqual(SiteWebhookSiteWebhookType.WebCreated, webhooks[0].SiteWebhookType); Assert.AreEqual("http://myapp.azurewebsites.net/WebHookListener", webhooks[0].ServerNotificationUrl); + Assert.AreEqual("FED", webhooks[0].ClientState); } [TestMethod] diff --git a/src/lib/PnP.Framework/Extensions/ListExtensions.cs b/src/lib/PnP.Framework/Extensions/ListExtensions.cs index 98f54e041..e894d3479 100644 --- a/src/lib/PnP.Framework/Extensions/ListExtensions.cs +++ b/src/lib/PnP.Framework/Extensions/ListExtensions.cs @@ -222,8 +222,9 @@ public static WebhookSubscription AddWebhookSubscription(this List list, string /// Url of the web hook service endpoint (the one that will be called during an event) /// New web hook expiration date /// (optional) The access token to SharePoint + /// (optional) New web hook client state /// true if the update succeeded, false otherwise - public static bool UpdateWebhookSubscription(this List list, string subscriptionId, string webHookEndPoint, DateTime expirationDateTime, string accessToken = null) + public static bool UpdateWebhookSubscription(this List list, string subscriptionId, string webHookEndPoint, DateTime expirationDateTime, string accessToken = null, string clientState= null) { // Get the access from the client context if not specified. accessToken = accessToken ?? list.Context.GetAccessToken(); @@ -233,7 +234,7 @@ public static bool UpdateWebhookSubscription(this List list, string subscription try { - return WebhookUtility.UpdateWebhookSubscriptionAsync(list.Context.Url, WebHookResourceType.List, listId.ToString(), subscriptionId, webHookEndPoint, expirationDateTime, accessToken, list.Context as ClientContext).Result; + return WebhookUtility.UpdateWebhookSubscriptionAsync(list.Context.Url, WebHookResourceType.List, listId.ToString(), subscriptionId, webHookEndPoint, expirationDateTime, accessToken, list.Context as ClientContext, clientState).Result; } catch (AggregateException ex) { @@ -250,10 +251,11 @@ public static bool UpdateWebhookSubscription(this List list, string subscription /// The id of the subscription to remove /// New web hook expiration date /// (optional) The access token to SharePoint + /// (optional) New web hook client state /// true if the update succeeded, false otherwise - public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId, DateTime expirationDateTime, string accessToken = null) + public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId, DateTime expirationDateTime, string accessToken = null, string clientState = null) { - return UpdateWebhookSubscription(list, subscriptionId.ToString(), null, expirationDateTime, accessToken); + return UpdateWebhookSubscription(list, subscriptionId.ToString(), null, expirationDateTime, accessToken, clientState); } /// @@ -265,10 +267,11 @@ public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId /// Url of the web hook service endpoint (the one that will be called during an event) /// New web hook expiration date /// (optional) The access token to SharePoint + /// (optional) New web hook client state /// true if the update succeeded, false otherwise - public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId, string webHookEndPoint, DateTime expirationDateTime, string accessToken = null) + public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId, string webHookEndPoint, DateTime expirationDateTime, string accessToken = null, string clientState = null) { - return UpdateWebhookSubscription(list, subscriptionId.ToString(), webHookEndPoint, expirationDateTime, accessToken); + return UpdateWebhookSubscription(list, subscriptionId.ToString(), webHookEndPoint, expirationDateTime, accessToken, clientState); } /// @@ -281,7 +284,7 @@ public static bool UpdateWebhookSubscription(this List list, Guid subscriptionId /// true if the update succeeded, false otherwise public static bool UpdateWebhookSubscription(this List list, WebhookSubscription subscription, string accessToken = null) { - return UpdateWebhookSubscription(list, subscription.Id, subscription.NotificationUrl, subscription.ExpirationDateTime, accessToken); + return UpdateWebhookSubscription(list, subscription.Id, subscription.NotificationUrl, subscription.ExpirationDateTime, accessToken, subscription.ClientState); } /// diff --git a/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/SiteWebhook.cs b/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/SiteWebhook.cs index 8139a1f5b..472a3d6f7 100644 --- a/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/SiteWebhook.cs +++ b/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/SiteWebhook.cs @@ -35,9 +35,10 @@ public partial class SiteWebhook : Webhook /// Returns HashCode public override int GetHashCode() { - return (String.Format("{0}|{1}|{2}|", + return (String.Format("{0}|{1}|{2}|{3}|", ServerNotificationUrl?.GetHashCode() ?? 0, ExpiresInDays.GetHashCode(), + ClientState?.GetHashCode() ?? 0, SiteWebhookType.GetHashCode() ).GetHashCode()); } @@ -57,7 +58,7 @@ public override bool Equals(object obj) } /// - /// Compares SiteWebhook object based on ServerNotificationUrl, ExpiresInDays, and SiteWebhookType + /// Compares SiteWebhook object based on ServerNotificationUrl, ExpiresInDays, ClientState and SiteWebhookType /// /// SiteWebhook Class object /// true if the SiteWebhook object is equal to the current object; otherwise, false. @@ -70,6 +71,7 @@ public bool Equals(SiteWebhook other) return (this.ServerNotificationUrl == other.ServerNotificationUrl && this.ExpiresInDays == other.ExpiresInDays && + this.ClientState == other.ClientState && this.SiteWebhookType == other.SiteWebhookType ); } diff --git a/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/Webhook.cs b/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/Webhook.cs index d4602fdfc..ccc67af65 100644 --- a/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/Webhook.cs +++ b/src/lib/PnP.Framework/Provisioning/Model/SharePoint/Customizations/Webhook.cs @@ -22,6 +22,11 @@ public partial class Webhook : BaseModel, IEquatable /// public Int32 ExpiresInDays { get; set; } + /// + /// Defines an opaque string passed back to the client on all notifications, optional attribute. + /// + public String ClientState { get; set; } + #endregion #region Comparison code @@ -32,9 +37,10 @@ public partial class Webhook : BaseModel, IEquatable /// Returns HashCode public override int GetHashCode() { - return (String.Format("{0}|{1}|", + return (String.Format("{0}|{1}|{2}|", ServerNotificationUrl?.GetHashCode() ?? 0, - ExpiresInDays.GetHashCode() + ExpiresInDays.GetHashCode(), + ClientState?.GetHashCode() ?? 0 ).GetHashCode()); } @@ -53,7 +59,7 @@ public override bool Equals(object obj) } /// - /// Compares Webhook object based on ServerNotificationUrl and ExpiresInDays + /// Compares Webhook object based on ServerNotificationUrl, ExpiresInDays and ClientState /// /// Webhook Class object /// true if the Webhook object is equal to the current object; otherwise, false. @@ -65,7 +71,8 @@ public bool Equals(Webhook other) } return (this.ServerNotificationUrl == other.ServerNotificationUrl && - this.ExpiresInDays == other.ExpiresInDays + this.ExpiresInDays == other.ExpiresInDays && + this.ClientState == other.ClientState ); } diff --git a/src/lib/PnP.Framework/Provisioning/ObjectHandlers/ObjectListInstance.cs b/src/lib/PnP.Framework/Provisioning/ObjectHandlers/ObjectListInstance.cs index 75e3ce31d..81d018322 100644 --- a/src/lib/PnP.Framework/Provisioning/ObjectHandlers/ObjectListInstance.cs +++ b/src/lib/PnP.Framework/Provisioning/ObjectHandlers/ObjectListInstance.cs @@ -2060,7 +2060,7 @@ private static void AddOrUpdateListWebHook(List list, Webhook webhook, PnPMonito // for a new list immediately add the webhook if (!isListUpdate) { - var webhookSubscription = list.AddWebhookSubscription(webhookServerNotificationUrl, DateTime.Now.AddDays(webhook.ExpiresInDays)); + var webhookSubscription = list.AddWebhookSubscription(webhookServerNotificationUrl, DateTime.Now.AddDays(webhook.ExpiresInDays), webhook.ClientState); } // for existing lists add a new webhook or update existing webhook else @@ -2073,13 +2073,15 @@ private static void AddOrUpdateListWebHook(List list, Webhook webhook, PnPMonito { // refresh the expiration date of the existing webhook existingWebhook.ExpirationDateTime = DateTime.Now.AddDays(webhook.ExpiresInDays); + // update client state of the existing webhook + existingWebhook.ClientState = webhook.ClientState; // update the existing webhook list.UpdateWebhookSubscription(existingWebhook); } else { // add as new webhook - var webhookSubscription = list.AddWebhookSubscription(webhookServerNotificationUrl, DateTime.Now.AddDays(webhook.ExpiresInDays)); + var webhookSubscription = list.AddWebhookSubscription(webhookServerNotificationUrl, DateTime.Now.AddDays(webhook.ExpiresInDays), webhook.ClientState); } } } diff --git a/src/lib/PnP.Framework/Utilities/Webhooks/WebhookUtility.cs b/src/lib/PnP.Framework/Utilities/Webhooks/WebhookUtility.cs index 881b9bbb4..493647673 100644 --- a/src/lib/PnP.Framework/Utilities/Webhooks/WebhookUtility.cs +++ b/src/lib/PnP.Framework/Utilities/Webhooks/WebhookUtility.cs @@ -147,10 +147,11 @@ public static async Task AddWebhookSubscriptionAsync(string /// New web hook expiration date /// Access token to authenticate against SharePoint /// ClientContext instance to use for authentication + /// New web hook client state /// Thrown when expiration date is out of valid range. /// true if succesful, exception in case something went wrong public static async Task UpdateWebhookSubscriptionAsync(string webUrl, WebHookResourceType resourceType, string resourceId, string subscriptionId, - string webHookEndPoint, DateTime expirationDateTime, string accessToken, ClientContext context) + string webHookEndPoint, DateTime expirationDateTime, string accessToken, ClientContext context, string clientState = null) { if (!ValidateExpirationDateTime(expirationDateTime)) throw new ArgumentOutOfRangeException(nameof(expirationDateTime), "The specified expiration date is invalid. Should be greater than today and within 6 months"); @@ -182,7 +183,8 @@ public static async Task UpdateWebhookSubscriptionAsync(string webUrl, Web { webhookSubscription = new WebhookSubscription() { - ExpirationDateTime = expirationDateTime + ExpirationDateTime = expirationDateTime, + ClientState = clientState }; } else @@ -190,7 +192,8 @@ public static async Task UpdateWebhookSubscriptionAsync(string webUrl, Web webhookSubscription = new WebhookSubscription() { NotificationUrl = webHookEndPoint, - ExpirationDateTime = expirationDateTime + ExpirationDateTime = expirationDateTime, + ClientState = clientState }; }