Skip to content
This repository has been archived by the owner on Jan 19, 2021. It is now read-only.

Add List Extensions methods to manage Webhooks subscriptions #1197

Merged
merged 2 commits into from Jun 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion Core/OfficeDevPnP.Core.Tests/App.config.sample
Expand Up @@ -61,7 +61,7 @@
<add key="AzureStorageKey" value="" />
<!-- Support for storing file change tracking information into a database-->
<add key="TestAutomationDatabaseConnectionString" value="" />

<!-- Support for High Trust certificate testing -->
<!-- Full path to the PFX to be used for the high trust certificate testing -->
<add key="HighTrustCertificatePath" value="" />
Expand All @@ -76,6 +76,9 @@
<!-- The thumbprint / hash of the High Trust certificate in the Windows certificate store -->
<add key="HighTrustCertificateStoreThumbprint" value="bef80ea8c46391873e00a0ea588bd8feadd01d22" />


<!--Testing for Webhook test endpoint-->
<add key="TestWebhookUrl" value="https://[test-functions].azurewebsites.net" />
</appSettings>
<system.diagnostics>
<sharedListeners>
Expand Down
103 changes: 101 additions & 2 deletions Core/OfficeDevPnP.Core.Tests/Extensions/ListExtensionsTests.cs
Expand Up @@ -62,7 +62,7 @@ public void Initialize()

// List

_textFieldId = Guid.NewGuid();
_textFieldId = Guid.NewGuid();

var fieldCI = new FieldCreationInformation(FieldType.Text)
{
Expand Down Expand Up @@ -240,7 +240,7 @@ public void ListExistsByUrlPathParamTest()
false);

Assert.IsNotNull(list);
Assert.IsTrue(clientContext.Web.ListExists(new Uri(siteRelativePath,UriKind.Relative)));
Assert.IsTrue(clientContext.Web.ListExists(new Uri(siteRelativePath, UriKind.Relative)));

//Delete List
list.DeleteObject();
Expand Down Expand Up @@ -306,5 +306,104 @@ public void SetDefaultColumnValuesTest()
}
#endregion

#region Webhooks tests
[TestMethod]
public void SubscribeWebhookTest()
{
using (var clientContext = TestCommon.CreateClientContext())
{
var testList = clientContext.Web.Lists.GetById(_listId);
clientContext.Load(testList);
clientContext.ExecuteQueryRetry();

WebhookSubscription expectedSubscription = new WebhookSubscription()
{
ExpirationDateTime = DateTime.Today.AddMonths(3),
NotificationUrl = TestCommon.TestWebhookUrl,
Resource = TestCommon.DevSiteUrl + string.Format("/_api/lists('{0}')", _listId)
};
WebhookSubscription actualSubscription = testList.AddWebhookSubscription(TestCommon.TestWebhookUrl, 3);

// Compare properties of expected and actual
Assert.IsTrue(Equals(expectedSubscription.ClientState, actualSubscription.ClientState)
&& Equals(expectedSubscription.ExpirationDateTime, actualSubscription.ExpirationDateTime)
&& Equals(expectedSubscription.NotificationUrl, actualSubscription.NotificationUrl)
&& Equals(expectedSubscription.Resource, actualSubscription.Resource));
}
}

[TestMethod]
public void UnsubscribeWebhookTestFromGuid()
{
using (var clientContext = TestCommon.CreateClientContext())
{
var testList = clientContext.Web.Lists.GetById(_listId);
clientContext.Load(testList);
clientContext.ExecuteQueryRetry();


WebhookSubscription actualSubscription = testList.AddWebhookSubscription(TestCommon.TestWebhookUrl, 3);

bool result = testList.RemoveWebhookSubscription(Guid.Parse(actualSubscription.Id));

Assert.IsTrue(result);
}
}

[TestMethod]
public void UnsubscribeWebhookTestFromEntity()
{
using (var clientContext = TestCommon.CreateClientContext())
{
var testList = clientContext.Web.Lists.GetById(_listId);
clientContext.Load(testList);
clientContext.ExecuteQueryRetry();


WebhookSubscription actualSubscription = testList.AddWebhookSubscription(TestCommon.TestWebhookUrl, 3);

bool result = testList.RemoveWebhookSubscription(actualSubscription);

Assert.IsTrue(result);
}
}

[TestMethod]
public void UnsubscribeWebhookTestFromString()
{
using (var clientContext = TestCommon.CreateClientContext())
{
var testList = clientContext.Web.Lists.GetById(_listId);
clientContext.Load(testList);
clientContext.ExecuteQueryRetry();


WebhookSubscription actualSubscription = testList.AddWebhookSubscription(TestCommon.TestWebhookUrl, 3);

bool result = testList.RemoveWebhookSubscription(actualSubscription.Id);

Assert.IsTrue(result);
}
}

[TestMethod]
public void GetAllWebhookSubscriptionsTest()
{
using (var clientContext = TestCommon.CreateClientContext())
{
var testList = clientContext.Web.Lists.GetById(_listId);
clientContext.Load(testList);
clientContext.ExecuteQueryRetry();


WebhookSubscription createdSubscription = testList.AddWebhookSubscription(TestCommon.TestWebhookUrl, 3);

IList<WebhookSubscription> actualSubscriptions = testList.GetAllWebhookSubscriptions();

Assert.IsTrue(actualSubscriptions.Any(s => s == createdSubscription));
}
}
#endregion

}
}
13 changes: 9 additions & 4 deletions Core/OfficeDevPnP.Core.Tests/TestCommon.cs
Expand Up @@ -18,6 +18,7 @@ static TestCommon()
// Read configuration data
TenantUrl = ConfigurationManager.AppSettings["SPOTenantUrl"];
DevSiteUrl = ConfigurationManager.AppSettings["SPODevSiteUrl"];
TestWebhookUrl = ConfigurationManager.AppSettings["TestWebhookUrl"];

#if !ONPREMISES
if (string.IsNullOrEmpty(TenantUrl))
Expand All @@ -30,6 +31,8 @@ static TestCommon()
throw new ConfigurationErrorsException("Dev site url in App.config are not set up.");
}



// Trim trailing slashes
TenantUrl = TenantUrl.TrimEnd(new[] { '/' });
DevSiteUrl = DevSiteUrl.TrimEnd(new[] { '/' });
Expand Down Expand Up @@ -146,6 +149,8 @@ static TestCommon()
/// </summary>
public static string HighTrustCertificateStoreThumbprint { get; set; }

public static string TestWebhookUrl { get; set; }

public static String AzureStorageKey
{
get
Expand Down Expand Up @@ -188,9 +193,9 @@ public static String ScriptSite
return ConfigurationManager.AppSettings["ScriptSite"];
}
}
#endregion
#endregion

#region Methods
#region Methods
public static ClientContext CreateClientContext()
{
return CreateContext(DevSiteUrl, Credentials);
Expand Down Expand Up @@ -291,7 +296,7 @@ private static ClientContext CreateContext(string contextUrl, ICredentials crede

if (new Uri(DevSiteUrl).DnsSafeHost.Contains("spoppe.com"))
{
context = am.GetAppOnlyAuthenticatedContext(contextUrl, Core.Utilities.TokenHelper.GetRealmFromTargetUrl(new Uri(DevSiteUrl)), AppId, AppSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix:"login");
context = am.GetAppOnlyAuthenticatedContext(contextUrl, Core.Utilities.TokenHelper.GetRealmFromTargetUrl(new Uri(DevSiteUrl)), AppId, AppSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login");
}
else
{
Expand Down Expand Up @@ -319,6 +324,6 @@ private static SecureString GetSecureString(string input)

return secureString;
}
#endregion
#endregion
}
}
31 changes: 31 additions & 0 deletions Core/OfficeDevPnP.Core/Entities/WebhookSubscription.cs
@@ -0,0 +1,31 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OfficeDevPnP.Core.Entities
{
/// <summary>
/// Represents the payload of a Http message
/// </summary>
public class WebhookSubscription
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; set; }

[JsonProperty(PropertyName = "clientState", NullValueHandling = NullValueHandling.Ignore)]
public string ClientState { get; set; }

[JsonProperty(PropertyName = "expirationDateTime")]
public DateTime ExpirationDateTime { get; set; }

[JsonProperty(PropertyName = "notificationUrl")]
public string NotificationUrl { get; set; }

[JsonProperty(PropertyName = "resource", NullValueHandling = NullValueHandling.Ignore)]
public string Resource { get; set; }

}
}
115 changes: 113 additions & 2 deletions Core/OfficeDevPnP.Core/Extensions/ListExtensions.cs
Expand Up @@ -14,6 +14,8 @@
using OfficeDevPnP.Core.Diagnostics;
using OfficeDevPnP.Core.Utilities;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OfficeDevPnP.Core.Utilities.Webhooks;

namespace Microsoft.SharePoint.Client
{
Expand Down Expand Up @@ -138,6 +140,115 @@ in list.EventReceivers

#endregion

#region Webhooks
/// <summary>
/// Add the a Webhook subscription to a list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to add a Webhook subscription to</param>
/// <param name="notificationUrl">The Webhook endpoint URL</param>
/// <param name="expirationDate">The expiration date of the subscription</param>
/// <param name="clientState">The client state to use in the Webhook subscription</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns>The added subscription object</returns>
public static WebhookSubscription AddWebhookSubscription(this List list, string notificationUrl,
DateTime expirationDate, string clientState = null, string accessToken = null)
{
// Get the access from the client context if not specified.
accessToken = accessToken ?? Utility.GetAccessTokenFromClientContext(list.Context);

return WebhookUtility.AddWebhookSubscriptionAsync(list.Context.Url,
EHookableResourceType.List, accessToken, new WebhookSubscription()
{
Resource = list.Id.ToString(),
ExpirationDateTime = expirationDate,
NotificationUrl = notificationUrl,
ClientState = clientState
}).Result;
}

/// <summary>
/// Add the a Webhook subscription to a list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to add a Webhook subscription to</param>
/// <param name="notificationUrl">The Webhook endpoint URL</param>
/// <param name="validityInMonths">The validity of the subscriptions in months</param>
/// <param name="clientState">The client state to use in the Webhook subscription</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns>The added subscription object</returns>
public static WebhookSubscription AddWebhookSubscription(this List list, string notificationUrl,
int validityInMonths = 3, string clientState = null, string accessToken = null)
{
// Get the access from the client context if not specified.
accessToken = accessToken ?? Utility.GetAccessTokenFromClientContext(list.Context);

return WebhookUtility.AddWebhookSubscriptionAsync(list.Context.Url,
EHookableResourceType.List, accessToken, list.Id.ToString(),
notificationUrl, clientState, validityInMonths).Result;
}

/// <summary>
/// Remove a Webhook subscription from the list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to remove the Webhook subscription from</param>
/// <param name="subscriptionId">The id of the subscription to remove</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns><c>true</c> if the removal succeeded, <c>false</c> otherwise</returns>
public static bool RemoveWebhookSubscription(this List list, string subscriptionId, string accessToken = null)
{
// Get the access from the client context if not specified.
accessToken = accessToken ?? Utility.GetAccessTokenFromClientContext(list.Context);

return WebhookUtility.DeleteWebhookSubscriptionAsync(list.Context.Url, EHookableResourceType.List, list.Id.ToString(),
subscriptionId, accessToken).Result;
}

/// <summary>
/// Remove a Webhook subscription from the list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to remove the Webhook subscription from</param>
/// <param name="subscriptionId">The id of the subscription to remove</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns><c>true</c> if the removal succeeded, <c>false</c> otherwise</returns>
public static bool RemoveWebhookSubscription(this List list, Guid subscriptionId, string accessToken = null)
{
return RemoveWebhookSubscription(list, subscriptionId.ToString(), accessToken);
}

/// <summary>
/// Remove a Webhook subscription from the list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to remove the Webhook subscription from</param>
/// <param name="subscription">The subscription to remove</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns><c>true</c> if the removal succeeded, <c>false</c> otherwise</returns>
public static bool RemoveWebhookSubscription(this List list, WebhookSubscription subscription, string accessToken = null)
{
return RemoveWebhookSubscription(list, subscription.Id, accessToken);
}

/// <summary>
/// Get all the existing Webhooks subscriptions of the list
/// Note: If the access token is not specified, it will cost a dummy request to retrieve it
/// </summary>
/// <param name="list">The list to get the subscriptions of</param>
/// <param name="accessToken">(optional) The access token to SharePoint</param>
/// <returns>The collection of Webhooks subscriptions of the list</returns>
public static IList<WebhookSubscription> GetAllWebhookSubscriptions(this List list, string accessToken = null)
{
// Get the access from the client context if not specified.
accessToken = accessToken ?? Utility.GetAccessTokenFromClientContext(list.Context);

return WebhookUtility.GetWebhooksSubscriptionsAsync(list.Context.Url,
EHookableResourceType.List, list.Id.ToString(), accessToken).Result.Value;
}

#endregion

#region List Properties

/// <summary>
Expand Down Expand Up @@ -615,7 +726,7 @@ private static void SetJSLinkCustomizationsImplementation(List list, File file,
var wp = wpd.WebPart;

if (wp.Properties.FieldValues.Keys.Contains("JSLink"))
{
{
wp.Properties["JSLink"] = jslink;
wpd.SaveWebPartChanges();

Expand Down Expand Up @@ -903,7 +1014,7 @@ public static void SetListPermission(this List list, Principal principal, RoleTy

// Get role type
var roleDefinition = web.RoleDefinitions.GetByType(roleType);
var rdbColl = new RoleDefinitionBindingCollection(web.Context) {roleDefinition};
var rdbColl = new RoleDefinitionBindingCollection(web.Context) { roleDefinition };

// Set custom permission to the list
list.RoleAssignments.Add(principal, rdbColl);
Expand Down
3 changes: 3 additions & 0 deletions Core/OfficeDevPnP.Core/OfficeDevPnP.Core.csproj
Expand Up @@ -378,6 +378,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Entities\WebhookSubscription.cs" />
<Compile Include="Extensions\ClientContextExtensions.cs" />
<Compile Include="Extensions\ClientObjectExtensions.cs" />
<Compile Include="Extensions\Deprecated\BrandingExtensions.deprecated.cs" />
Expand Down Expand Up @@ -830,6 +831,8 @@
<Compile Include="Diagnostics\Tree\TreeNode.cs" />
<Compile Include="Diagnostics\Tree\TreeNodeList.cs" />
<Compile Include="Utilities\PnPCoreUtilities.cs" />
<Compile Include="Utilities\Webhooks\ResponseModel.cs" />
<Compile Include="Utilities\Webhooks\WebhookUtility.cs" />
<Compile Include="Utilities\WebParts\IWebPartPostProcessor.cs" />
<Compile Include="Utilities\WebParts\Processors\PassThroughProcessor.cs" />
<Compile Include="Utilities\WebParts\Schema\WebPart.v3.cs" />
Expand Down