diff --git a/samples/react-sp-elevatedprivileges/README.md b/samples/react-sp-elevatedprivileges/README.md
new file mode 100644
index 000000000..811b783a9
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/README.md
@@ -0,0 +1,96 @@
+# Communicate using elevated privileges with SharePoint
+
+## Summary
+
+Sample SharePoint Framework client-side web part illustrating communication with SharePoint using elevated privileges through a custom Web API.
+
+![Sample web part showing orders retrieved from a custom Web API secured with Azure Active Directory](./assets/preview.png)
+
+## Applies to
+
+* [SharePoint Framework Developer Preview](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
+* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
+
+## Solution
+
+Solution|Author(s)
+--------|---------
+react-sp-elevatedprivileges|Waldek Mastykarz (MVP, Rencore, @waldekm)
+
+## Version history
+
+Version|Date|Comments
+-------|----|--------
+1.0|October 12, 2016|Initial release
+
+## Disclaimer
+**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
+
+---
+
+## Minimal Path to Awesome
+
+- clone this repo
+
+### Deploy custom Web API
+
+- in the Azure Management Portal at https://portal.azure.com create a new API App
+ - in the settings enable CORS for all origins using an `*`
+ - copy the URL of the API App
+- in your SharePoint site
+ - create a new list called **Tasks**
+ - navigate to https://yourtenant.sharepoint.com/_layouts/15/appregnew.aspx
+ - generate client ID and copy it
+ - generate client secret and copy it
+ - as the name use: **SPFx sample elevated privileges**
+ - as the URL use the URL of the Azure API App created previously
+ - as the domain use the host name of the Azure API App
+ - navigate to https://yourtenant.sharepoint.com/_layouts/15/appinv.aspx
+ - lookup the newly registered Add-in using the client ID you copied
+ - in the **Permissions** field past the following code:
+
+```xml
+
+
+
+```
+
+- after confirming the changes, when prompted, select the previously created **Tasks** list
+- from the **api** folder, in Visual Studio open the **pnp.api.elevatedprivileges.sln** file
+- in the web.config file
+ - update the value of the **clientId** setting with the previously copied client ID
+ - update the value of the **clientSecret** setting with the previously copied client secret
+ - update hte value of the **siteUrl** setting with the URL of your SharePoint site
+ - if you named your list other than **Tasks** update the value of the **listName** property with the name of your list
+- build the solution
+- deploy the **pnp.api.elevatedprivileges** project to the newly created API App
+- verify that you can access the API by navigating in your web browser to **https://your-api-app.azurewebsites.net/api/items**
+ - you should get an error that GET is not a supported method
+
+### Configure web part
+
+- in the command line
+ - change the working directory to the **webpart** folder
+ - run `npm i`
+- in your code editor open the **webpart** folder
+- in the **./src/webparts/createTask/components/CreateTask.tsx** file
+ - in line 76 replace the URL with the URL of your API App
+- in the command line execute `gulp serve`
+- add the web part to SharePoint workbench
+- enter the name of the new item and click the **Create** button
+ - verify that a new item with the name you specified has been created in the Tasks list
+
+## Features
+
+This project contains sample Web API using app-only permissions to create items in a specific SharePoint list, and a client-side web part connected to that API.
+
+This project illustrates the following concepts:
+- elevating user privileges for communicating with SharePoint through a custom Web API
+- connecting SharePoint Framework client-side web part to a custom Web API hosted in Azure
+- persisting state in React components
+- communicating state updates in React components to users
+- executing REST API web requests from React components
+- using Office UI Fabric React components in SharePoint Framework client-side web parts
+- using form controls in Rest components
+
+
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges.sln b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges.sln
new file mode 100755
index 000000000..7278fca51
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pnp.api.elevatedprivileges", "pnp.api.elevatedprivileges\pnp.api.elevatedprivileges.csproj", "{E9E8B537-849D-47B4-AA97-C3B7ED93227E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E9E8B537-849D-47B4-AA97-C3B7ED93227E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9E8B537-849D-47B4-AA97-C3B7ED93227E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9E8B537-849D-47B4-AA97-C3B7ED93227E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9E8B537-849D-47B4-AA97-C3B7ED93227E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/FilterConfig.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/FilterConfig.cs
new file mode 100755
index 000000000..377d6b31b
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/FilterConfig.cs
@@ -0,0 +1,9 @@
+using System.Web.Mvc;
+
+namespace pnp.api.elevatedprivileges {
+ public class FilterConfig {
+ public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
+ filters.Add(new HandleErrorAttribute());
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/WebApiConfig.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/WebApiConfig.cs
new file mode 100755
index 000000000..8fbd581f1
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/App_Start/WebApiConfig.cs
@@ -0,0 +1,15 @@
+using System.Web.Http;
+
+namespace pnp.api.elevatedprivileges {
+ public static class WebApiConfig {
+ public static void Register(HttpConfiguration config) {
+ config.MapHttpAttributeRoutes();
+
+ config.Routes.MapHttpRoute(
+ name: "DefaultApi",
+ routeTemplate: "api/{controller}/{id}",
+ defaults: new { id = RouteParameter.Optional }
+ );
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Controllers/ItemsController.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Controllers/ItemsController.cs
new file mode 100755
index 000000000..ca93d29a0
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Controllers/ItemsController.cs
@@ -0,0 +1,31 @@
+using Microsoft.SharePoint.Client;
+using OfficeDevPnP.Core;
+using System;
+using System.Configuration;
+using System.Net.Http;
+using System.Web.Http;
+
+namespace pnp.api.elevatedprivileges.Controllers {
+ public class ItemsController : ApiController {
+ // POST api/values
+ public HttpResponseMessage Post([FromBody]dynamic data) {
+ try {
+ AuthenticationManager authMgr = new AuthenticationManager();
+ using (ClientContext ctx = authMgr.GetAppOnlyAuthenticatedContext(
+ ConfigurationManager.AppSettings["siteUrl"],
+ ConfigurationManager.AppSettings["clientId"],
+ ConfigurationManager.AppSettings["clientSecret"])) {
+ var list = ctx.Web.Lists.GetByTitle(ConfigurationManager.AppSettings["listName"]);
+ var item = list.AddItem(new ListItemCreationInformation());
+ item["Title"] = data.title.ToString();
+ item.Update();
+ ctx.ExecuteQuery();
+ return new HttpResponseMessage(System.Net.HttpStatusCode.Created);
+ }
+ }
+ catch {
+ return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
+ }
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Filters/SharePointContextFilterAttribute.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Filters/SharePointContextFilterAttribute.cs
new file mode 100755
index 000000000..e0aad6939
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Filters/SharePointContextFilterAttribute.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Web.Mvc;
+
+namespace pnp.api.elevatedprivileges
+{
+ ///
+ /// SharePoint action filter attribute.
+ ///
+ public class SharePointContextFilterAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ Uri redirectUrl;
+ switch (SharePointContextProvider.CheckRedirectionStatus(filterContext.HttpContext, out redirectUrl))
+ {
+ case RedirectionStatus.Ok:
+ return;
+ case RedirectionStatus.ShouldRedirect:
+ filterContext.Result = new RedirectResult(redirectUrl.AbsoluteUri);
+ break;
+ case RedirectionStatus.CanNotRedirect:
+ filterContext.Result = new ViewResult { ViewName = "Error" };
+ break;
+ }
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax
new file mode 100755
index 000000000..1a1ec49a1
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax
@@ -0,0 +1 @@
+<%@ Application Codebehind="Global.asax.cs" Inherits="pnp.api.elevatedprivileges.WebApiApplication" Language="C#" %>
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax.cs
new file mode 100755
index 000000000..e576debbb
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Global.asax.cs
@@ -0,0 +1,12 @@
+using System.Web.Http;
+using System.Web.Mvc;
+
+namespace pnp.api.elevatedprivileges {
+ public class WebApiApplication : System.Web.HttpApplication {
+ protected void Application_Start() {
+ AreaRegistration.RegisterAllAreas();
+ GlobalConfiguration.Configure(WebApiConfig.Register);
+ FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Properties/AssemblyInfo.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Properties/AssemblyInfo.cs
new file mode 100755
index 000000000..22dfc1de0
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("pnp.api.elevatedprivileges")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("pnp.api.elevatedprivileges")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("eff521b1-a9db-4318-b4a8-800a497de4d2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/SharePointContext.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/SharePointContext.cs
new file mode 100755
index 000000000..288dbd333
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/SharePointContext.cs
@@ -0,0 +1,924 @@
+using Microsoft.IdentityModel.S2S.Protocols.OAuth2;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.SharePoint.Client;
+using System;
+using System.Net;
+using System.Security.Principal;
+using System.Web;
+using System.Web.Configuration;
+
+namespace pnp.api.elevatedprivileges
+{
+ ///
+ /// Encapsulates all the information from SharePoint.
+ ///
+ public abstract class SharePointContext
+ {
+ public const string SPHostUrlKey = "SPHostUrl";
+ public const string SPAppWebUrlKey = "SPAppWebUrl";
+ public const string SPLanguageKey = "SPLanguage";
+ public const string SPClientTagKey = "SPClientTag";
+ public const string SPProductNumberKey = "SPProductNumber";
+
+ protected static readonly TimeSpan AccessTokenLifetimeTolerance = TimeSpan.FromMinutes(5.0);
+
+ private readonly Uri spHostUrl;
+ private readonly Uri spAppWebUrl;
+ private readonly string spLanguage;
+ private readonly string spClientTag;
+ private readonly string spProductNumber;
+
+ //
+ protected Tuple userAccessTokenForSPHost;
+ protected Tuple userAccessTokenForSPAppWeb;
+ protected Tuple appOnlyAccessTokenForSPHost;
+ protected Tuple appOnlyAccessTokenForSPAppWeb;
+
+ ///
+ /// Gets the SharePoint host url from QueryString of the specified HTTP request.
+ ///
+ /// The specified HTTP request.
+ /// The SharePoint host url. Returns null if the HTTP request doesn't contain the SharePoint host url.
+ public static Uri GetSPHostUrl(HttpRequestBase httpRequest)
+ {
+ if (httpRequest == null)
+ {
+ throw new ArgumentNullException("httpRequest");
+ }
+
+ string spHostUrlString = TokenHelper.EnsureTrailingSlash(httpRequest.QueryString[SPHostUrlKey]);
+ Uri spHostUrl;
+ if (Uri.TryCreate(spHostUrlString, UriKind.Absolute, out spHostUrl) &&
+ (spHostUrl.Scheme == Uri.UriSchemeHttp || spHostUrl.Scheme == Uri.UriSchemeHttps))
+ {
+ return spHostUrl;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the SharePoint host url from QueryString of the specified HTTP request.
+ ///
+ /// The specified HTTP request.
+ /// The SharePoint host url. Returns null if the HTTP request doesn't contain the SharePoint host url.
+ public static Uri GetSPHostUrl(HttpRequest httpRequest)
+ {
+ return GetSPHostUrl(new HttpRequestWrapper(httpRequest));
+ }
+
+ ///
+ /// The SharePoint host url.
+ ///
+ public Uri SPHostUrl
+ {
+ get { return this.spHostUrl; }
+ }
+
+ ///
+ /// The SharePoint app web url.
+ ///
+ public Uri SPAppWebUrl
+ {
+ get { return this.spAppWebUrl; }
+ }
+
+ ///
+ /// The SharePoint language.
+ ///
+ public string SPLanguage
+ {
+ get { return this.spLanguage; }
+ }
+
+ ///
+ /// The SharePoint client tag.
+ ///
+ public string SPClientTag
+ {
+ get { return this.spClientTag; }
+ }
+
+ ///
+ /// The SharePoint product number.
+ ///
+ public string SPProductNumber
+ {
+ get { return this.spProductNumber; }
+ }
+
+ ///
+ /// The user access token for the SharePoint host.
+ ///
+ public abstract string UserAccessTokenForSPHost
+ {
+ get;
+ }
+
+ ///
+ /// The user access token for the SharePoint app web.
+ ///
+ public abstract string UserAccessTokenForSPAppWeb
+ {
+ get;
+ }
+
+ ///
+ /// The app only access token for the SharePoint host.
+ ///
+ public abstract string AppOnlyAccessTokenForSPHost
+ {
+ get;
+ }
+
+ ///
+ /// The app only access token for the SharePoint app web.
+ ///
+ public abstract string AppOnlyAccessTokenForSPAppWeb
+ {
+ get;
+ }
+
+ ///
+ /// Constructor.
+ ///
+ /// The SharePoint host url.
+ /// The SharePoint app web url.
+ /// The SharePoint language.
+ /// The SharePoint client tag.
+ /// The SharePoint product number.
+ protected SharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber)
+ {
+ if (spHostUrl == null)
+ {
+ throw new ArgumentNullException("spHostUrl");
+ }
+
+ if (string.IsNullOrEmpty(spLanguage))
+ {
+ throw new ArgumentNullException("spLanguage");
+ }
+
+ if (string.IsNullOrEmpty(spClientTag))
+ {
+ throw new ArgumentNullException("spClientTag");
+ }
+
+ if (string.IsNullOrEmpty(spProductNumber))
+ {
+ throw new ArgumentNullException("spProductNumber");
+ }
+
+ this.spHostUrl = spHostUrl;
+ this.spAppWebUrl = spAppWebUrl;
+ this.spLanguage = spLanguage;
+ this.spClientTag = spClientTag;
+ this.spProductNumber = spProductNumber;
+ }
+
+ ///
+ /// Creates a user ClientContext for the SharePoint host.
+ ///
+ /// A ClientContext instance.
+ public ClientContext CreateUserClientContextForSPHost()
+ {
+ return CreateClientContext(this.SPHostUrl, this.UserAccessTokenForSPHost);
+ }
+
+ ///
+ /// Creates a user ClientContext for the SharePoint app web.
+ ///
+ /// A ClientContext instance.
+ public ClientContext CreateUserClientContextForSPAppWeb()
+ {
+ return CreateClientContext(this.SPAppWebUrl, this.UserAccessTokenForSPAppWeb);
+ }
+
+ ///
+ /// Creates app only ClientContext for the SharePoint host.
+ ///
+ /// A ClientContext instance.
+ public ClientContext CreateAppOnlyClientContextForSPHost()
+ {
+ return CreateClientContext(this.SPHostUrl, this.AppOnlyAccessTokenForSPHost);
+ }
+
+ ///
+ /// Creates an app only ClientContext for the SharePoint app web.
+ ///
+ /// A ClientContext instance.
+ public ClientContext CreateAppOnlyClientContextForSPAppWeb()
+ {
+ return CreateClientContext(this.SPAppWebUrl, this.AppOnlyAccessTokenForSPAppWeb);
+ }
+
+ ///
+ /// Gets the database connection string from SharePoint for autohosted app.
+ /// This method is deprecated because the autohosted option is no longer available.
+ ///
+ [ObsoleteAttribute("This method is deprecated because the autohosted option is no longer available.", true)]
+ public string GetDatabaseConnectionString()
+ {
+ throw new NotSupportedException("This method is deprecated because the autohosted option is no longer available.");
+ }
+
+ ///
+ /// Determines if the specified access token is valid.
+ /// It considers an access token as not valid if it is null, or it has expired.
+ ///
+ /// The access token to verify.
+ /// True if the access token is valid.
+ protected static bool IsAccessTokenValid(Tuple accessToken)
+ {
+ return accessToken != null &&
+ !string.IsNullOrEmpty(accessToken.Item1) &&
+ accessToken.Item2 > DateTime.UtcNow;
+ }
+
+ ///
+ /// Creates a ClientContext with the specified SharePoint site url and the access token.
+ ///
+ /// The site url.
+ /// The access token.
+ /// A ClientContext instance.
+ private static ClientContext CreateClientContext(Uri spSiteUrl, string accessToken)
+ {
+ if (spSiteUrl != null && !string.IsNullOrEmpty(accessToken))
+ {
+ return TokenHelper.GetClientContextWithAccessToken(spSiteUrl.AbsoluteUri, accessToken);
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Redirection status.
+ ///
+ public enum RedirectionStatus
+ {
+ Ok,
+ ShouldRedirect,
+ CanNotRedirect
+ }
+
+ ///
+ /// Provides SharePointContext instances.
+ ///
+ public abstract class SharePointContextProvider
+ {
+ private static SharePointContextProvider current;
+
+ ///
+ /// The current SharePointContextProvider instance.
+ ///
+ public static SharePointContextProvider Current
+ {
+ get { return SharePointContextProvider.current; }
+ }
+
+ ///
+ /// Initializes the default SharePointContextProvider instance.
+ ///
+ static SharePointContextProvider()
+ {
+ if (!TokenHelper.IsHighTrustApp())
+ {
+ SharePointContextProvider.current = new SharePointAcsContextProvider();
+ }
+ else
+ {
+ SharePointContextProvider.current = new SharePointHighTrustContextProvider();
+ }
+ }
+
+ ///
+ /// Registers the specified SharePointContextProvider instance as current.
+ /// It should be called by Application_Start() in Global.asax.
+ ///
+ /// The SharePointContextProvider to be set as current.
+ public static void Register(SharePointContextProvider provider)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException("provider");
+ }
+
+ SharePointContextProvider.current = provider;
+ }
+
+ ///
+ /// Checks if it is necessary to redirect to SharePoint for user to authenticate.
+ ///
+ /// The HTTP context.
+ /// The redirect url to SharePoint if the status is ShouldRedirect. Null if the status is Ok or CanNotRedirect.
+ /// Redirection status.
+ public static RedirectionStatus CheckRedirectionStatus(HttpContextBase httpContext, out Uri redirectUrl)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+
+ redirectUrl = null;
+ bool contextTokenExpired = false;
+
+ try
+ {
+ if (SharePointContextProvider.Current.GetSharePointContext(httpContext) != null)
+ {
+ return RedirectionStatus.Ok;
+ }
+ }
+ catch (SecurityTokenExpiredException)
+ {
+ contextTokenExpired = true;
+ }
+
+ const string SPHasRedirectedToSharePointKey = "SPHasRedirectedToSharePoint";
+
+ if (!string.IsNullOrEmpty(httpContext.Request.QueryString[SPHasRedirectedToSharePointKey]) && !contextTokenExpired)
+ {
+ return RedirectionStatus.CanNotRedirect;
+ }
+
+ Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
+
+ if (spHostUrl == null)
+ {
+ return RedirectionStatus.CanNotRedirect;
+ }
+
+ if (StringComparer.OrdinalIgnoreCase.Equals(httpContext.Request.HttpMethod, "POST"))
+ {
+ return RedirectionStatus.CanNotRedirect;
+ }
+
+ Uri requestUrl = httpContext.Request.Url;
+
+ var queryNameValueCollection = HttpUtility.ParseQueryString(requestUrl.Query);
+
+ // Removes the values that are included in {StandardTokens}, as {StandardTokens} will be inserted at the beginning of the query string.
+ queryNameValueCollection.Remove(SharePointContext.SPHostUrlKey);
+ queryNameValueCollection.Remove(SharePointContext.SPAppWebUrlKey);
+ queryNameValueCollection.Remove(SharePointContext.SPLanguageKey);
+ queryNameValueCollection.Remove(SharePointContext.SPClientTagKey);
+ queryNameValueCollection.Remove(SharePointContext.SPProductNumberKey);
+
+ // Adds SPHasRedirectedToSharePoint=1.
+ queryNameValueCollection.Add(SPHasRedirectedToSharePointKey, "1");
+
+ UriBuilder returnUrlBuilder = new UriBuilder(requestUrl);
+ returnUrlBuilder.Query = queryNameValueCollection.ToString();
+
+ // Inserts StandardTokens.
+ const string StandardTokens = "{StandardTokens}";
+ string returnUrlString = returnUrlBuilder.Uri.AbsoluteUri;
+ returnUrlString = returnUrlString.Insert(returnUrlString.IndexOf("?") + 1, StandardTokens + "&");
+
+ // Constructs redirect url.
+ string redirectUrlString = TokenHelper.GetAppContextTokenRequestUrl(spHostUrl.AbsoluteUri, Uri.EscapeDataString(returnUrlString));
+
+ redirectUrl = new Uri(redirectUrlString, UriKind.Absolute);
+
+ return RedirectionStatus.ShouldRedirect;
+ }
+
+ ///
+ /// Checks if it is necessary to redirect to SharePoint for user to authenticate.
+ ///
+ /// The HTTP context.
+ /// The redirect url to SharePoint if the status is ShouldRedirect. Null if the status is Ok or CanNotRedirect.
+ /// Redirection status.
+ public static RedirectionStatus CheckRedirectionStatus(HttpContext httpContext, out Uri redirectUrl)
+ {
+ return CheckRedirectionStatus(new HttpContextWrapper(httpContext), out redirectUrl);
+ }
+
+ ///
+ /// Creates a SharePointContext instance with the specified HTTP request.
+ ///
+ /// The HTTP request.
+ /// The SharePointContext instance. Returns null if errors occur.
+ public SharePointContext CreateSharePointContext(HttpRequestBase httpRequest)
+ {
+ if (httpRequest == null)
+ {
+ throw new ArgumentNullException("httpRequest");
+ }
+
+ // SPHostUrl
+ Uri spHostUrl = SharePointContext.GetSPHostUrl(httpRequest);
+ if (spHostUrl == null)
+ {
+ return null;
+ }
+
+ // SPAppWebUrl
+ string spAppWebUrlString = TokenHelper.EnsureTrailingSlash(httpRequest.QueryString[SharePointContext.SPAppWebUrlKey]);
+ Uri spAppWebUrl;
+ if (!Uri.TryCreate(spAppWebUrlString, UriKind.Absolute, out spAppWebUrl) ||
+ !(spAppWebUrl.Scheme == Uri.UriSchemeHttp || spAppWebUrl.Scheme == Uri.UriSchemeHttps))
+ {
+ spAppWebUrl = null;
+ }
+
+ // SPLanguage
+ string spLanguage = httpRequest.QueryString[SharePointContext.SPLanguageKey];
+ if (string.IsNullOrEmpty(spLanguage))
+ {
+ return null;
+ }
+
+ // SPClientTag
+ string spClientTag = httpRequest.QueryString[SharePointContext.SPClientTagKey];
+ if (string.IsNullOrEmpty(spClientTag))
+ {
+ return null;
+ }
+
+ // SPProductNumber
+ string spProductNumber = httpRequest.QueryString[SharePointContext.SPProductNumberKey];
+ if (string.IsNullOrEmpty(spProductNumber))
+ {
+ return null;
+ }
+
+ return CreateSharePointContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, httpRequest);
+ }
+
+ ///
+ /// Creates a SharePointContext instance with the specified HTTP request.
+ ///
+ /// The HTTP request.
+ /// The SharePointContext instance. Returns null if errors occur.
+ public SharePointContext CreateSharePointContext(HttpRequest httpRequest)
+ {
+ return CreateSharePointContext(new HttpRequestWrapper(httpRequest));
+ }
+
+ ///
+ /// Gets a SharePointContext instance associated with the specified HTTP context.
+ ///
+ /// The HTTP context.
+ /// The SharePointContext instance. Returns null if not found and a new instance can't be created.
+ public SharePointContext GetSharePointContext(HttpContextBase httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+
+ Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
+ if (spHostUrl == null)
+ {
+ return null;
+ }
+
+ SharePointContext spContext = LoadSharePointContext(httpContext);
+
+ if (spContext == null || !ValidateSharePointContext(spContext, httpContext))
+ {
+ spContext = CreateSharePointContext(httpContext.Request);
+
+ if (spContext != null)
+ {
+ SaveSharePointContext(spContext, httpContext);
+ }
+ }
+
+ return spContext;
+ }
+
+ ///
+ /// Gets a SharePointContext instance associated with the specified HTTP context.
+ ///
+ /// The HTTP context.
+ /// The SharePointContext instance. Returns null if not found and a new instance can't be created.
+ public SharePointContext GetSharePointContext(HttpContext httpContext)
+ {
+ return GetSharePointContext(new HttpContextWrapper(httpContext));
+ }
+
+ ///
+ /// Creates a SharePointContext instance.
+ ///
+ /// The SharePoint host url.
+ /// The SharePoint app web url.
+ /// The SharePoint language.
+ /// The SharePoint client tag.
+ /// The SharePoint product number.
+ /// The HTTP request.
+ /// The SharePointContext instance. Returns null if errors occur.
+ protected abstract SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest);
+
+ ///
+ /// Validates if the given SharePointContext can be used with the specified HTTP context.
+ ///
+ /// The SharePointContext.
+ /// The HTTP context.
+ /// True if the given SharePointContext can be used with the specified HTTP context.
+ protected abstract bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext);
+
+ ///
+ /// Loads the SharePointContext instance associated with the specified HTTP context.
+ ///
+ /// The HTTP context.
+ /// The SharePointContext instance. Returns null if not found.
+ protected abstract SharePointContext LoadSharePointContext(HttpContextBase httpContext);
+
+ ///
+ /// Saves the specified SharePointContext instance associated with the specified HTTP context.
+ /// null is accepted for clearing the SharePointContext instance associated with the HTTP context.
+ ///
+ /// The SharePointContext instance to be saved, or null.
+ /// The HTTP context.
+ protected abstract void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext);
+ }
+
+ #region ACS
+
+ ///
+ /// Encapsulates all the information from SharePoint in ACS mode.
+ ///
+ public class SharePointAcsContext : SharePointContext
+ {
+ private readonly string contextToken;
+ private readonly SharePointContextToken contextTokenObj;
+
+ ///
+ /// The context token.
+ ///
+ public string ContextToken
+ {
+ get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextToken : null; }
+ }
+
+ ///
+ /// The context token's "CacheKey" claim.
+ ///
+ public string CacheKey
+ {
+ get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextTokenObj.CacheKey : null; }
+ }
+
+ ///
+ /// The context token's "refreshtoken" claim.
+ ///
+ public string RefreshToken
+ {
+ get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextTokenObj.RefreshToken : null; }
+ }
+
+ public override string UserAccessTokenForSPHost
+ {
+ get
+ {
+ return GetAccessTokenString(ref this.userAccessTokenForSPHost,
+ () => TokenHelper.GetAccessToken(this.contextTokenObj, this.SPHostUrl.Authority));
+ }
+ }
+
+ public override string UserAccessTokenForSPAppWeb
+ {
+ get
+ {
+ if (this.SPAppWebUrl == null)
+ {
+ return null;
+ }
+
+ return GetAccessTokenString(ref this.userAccessTokenForSPAppWeb,
+ () => TokenHelper.GetAccessToken(this.contextTokenObj, this.SPAppWebUrl.Authority));
+ }
+ }
+
+ public override string AppOnlyAccessTokenForSPHost
+ {
+ get
+ {
+ return GetAccessTokenString(ref this.appOnlyAccessTokenForSPHost,
+ () => TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, this.SPHostUrl.Authority, TokenHelper.GetRealmFromTargetUrl(this.SPHostUrl)));
+ }
+ }
+
+ public override string AppOnlyAccessTokenForSPAppWeb
+ {
+ get
+ {
+ if (this.SPAppWebUrl == null)
+ {
+ return null;
+ }
+
+ return GetAccessTokenString(ref this.appOnlyAccessTokenForSPAppWeb,
+ () => TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, this.SPAppWebUrl.Authority, TokenHelper.GetRealmFromTargetUrl(this.SPAppWebUrl)));
+ }
+ }
+
+ public SharePointAcsContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, string contextToken, SharePointContextToken contextTokenObj)
+ : base(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber)
+ {
+ if (string.IsNullOrEmpty(contextToken))
+ {
+ throw new ArgumentNullException("contextToken");
+ }
+
+ if (contextTokenObj == null)
+ {
+ throw new ArgumentNullException("contextTokenObj");
+ }
+
+ this.contextToken = contextToken;
+ this.contextTokenObj = contextTokenObj;
+ }
+
+ ///
+ /// Ensures the access token is valid and returns it.
+ ///
+ /// The access token to verify.
+ /// The token renewal handler.
+ /// The access token string.
+ private static string GetAccessTokenString(ref Tuple accessToken, Func tokenRenewalHandler)
+ {
+ RenewAccessTokenIfNeeded(ref accessToken, tokenRenewalHandler);
+
+ return IsAccessTokenValid(accessToken) ? accessToken.Item1 : null;
+ }
+
+ ///
+ /// Renews the access token if it is not valid.
+ ///
+ /// The access token to renew.
+ /// The token renewal handler.
+ private static void RenewAccessTokenIfNeeded(ref Tuple accessToken, Func tokenRenewalHandler)
+ {
+ if (IsAccessTokenValid(accessToken))
+ {
+ return;
+ }
+
+ try
+ {
+ OAuth2AccessTokenResponse oAuth2AccessTokenResponse = tokenRenewalHandler();
+
+ DateTime expiresOn = oAuth2AccessTokenResponse.ExpiresOn;
+
+ if ((expiresOn - oAuth2AccessTokenResponse.NotBefore) > AccessTokenLifetimeTolerance)
+ {
+ // Make the access token get renewed a bit earlier than the time when it expires
+ // so that the calls to SharePoint with it will have enough time to complete successfully.
+ expiresOn -= AccessTokenLifetimeTolerance;
+ }
+
+ accessToken = Tuple.Create(oAuth2AccessTokenResponse.AccessToken, expiresOn);
+ }
+ catch (WebException)
+ {
+ }
+ }
+ }
+
+ ///
+ /// Default provider for SharePointAcsContext.
+ ///
+ public class SharePointAcsContextProvider : SharePointContextProvider
+ {
+ private const string SPContextKey = "SPContext";
+ private const string SPCacheKeyKey = "SPCacheKey";
+
+ protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
+ {
+ string contextTokenString = TokenHelper.GetContextTokenFromRequest(httpRequest);
+ if (string.IsNullOrEmpty(contextTokenString))
+ {
+ return null;
+ }
+
+ SharePointContextToken contextToken = null;
+ try
+ {
+ contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, httpRequest.Url.Authority);
+ }
+ catch (WebException)
+ {
+ return null;
+ }
+ catch (AudienceUriValidationFailedException)
+ {
+ return null;
+ }
+
+ return new SharePointAcsContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, contextTokenString, contextToken);
+ }
+
+ protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
+ {
+ SharePointAcsContext spAcsContext = spContext as SharePointAcsContext;
+
+ if (spAcsContext != null)
+ {
+ Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
+ string contextToken = TokenHelper.GetContextTokenFromRequest(httpContext.Request);
+ HttpCookie spCacheKeyCookie = httpContext.Request.Cookies[SPCacheKeyKey];
+ string spCacheKey = spCacheKeyCookie != null ? spCacheKeyCookie.Value : null;
+
+ return spHostUrl == spAcsContext.SPHostUrl &&
+ !string.IsNullOrEmpty(spAcsContext.CacheKey) &&
+ spCacheKey == spAcsContext.CacheKey &&
+ !string.IsNullOrEmpty(spAcsContext.ContextToken) &&
+ (string.IsNullOrEmpty(contextToken) || contextToken == spAcsContext.ContextToken);
+ }
+
+ return false;
+ }
+
+ protected override SharePointContext LoadSharePointContext(HttpContextBase httpContext)
+ {
+ return httpContext.Session[SPContextKey] as SharePointAcsContext;
+ }
+
+ protected override void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
+ {
+ SharePointAcsContext spAcsContext = spContext as SharePointAcsContext;
+
+ if (spAcsContext != null)
+ {
+ HttpCookie spCacheKeyCookie = new HttpCookie(SPCacheKeyKey)
+ {
+ Value = spAcsContext.CacheKey,
+ Secure = true,
+ HttpOnly = true
+ };
+
+ httpContext.Response.AppendCookie(spCacheKeyCookie);
+ }
+
+ httpContext.Session[SPContextKey] = spAcsContext;
+ }
+ }
+
+ #endregion ACS
+
+ #region HighTrust
+
+ ///
+ /// Encapsulates all the information from SharePoint in HighTrust mode.
+ ///
+ public class SharePointHighTrustContext : SharePointContext
+ {
+ private readonly WindowsIdentity logonUserIdentity;
+
+ ///
+ /// The Windows identity for the current user.
+ ///
+ public WindowsIdentity LogonUserIdentity
+ {
+ get { return this.logonUserIdentity; }
+ }
+
+ public override string UserAccessTokenForSPHost
+ {
+ get
+ {
+ return GetAccessTokenString(ref this.userAccessTokenForSPHost,
+ () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPHostUrl, this.LogonUserIdentity));
+ }
+ }
+
+ public override string UserAccessTokenForSPAppWeb
+ {
+ get
+ {
+ if (this.SPAppWebUrl == null)
+ {
+ return null;
+ }
+
+ return GetAccessTokenString(ref this.userAccessTokenForSPAppWeb,
+ () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPAppWebUrl, this.LogonUserIdentity));
+ }
+ }
+
+ public override string AppOnlyAccessTokenForSPHost
+ {
+ get
+ {
+ return GetAccessTokenString(ref this.appOnlyAccessTokenForSPHost,
+ () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPHostUrl, null));
+ }
+ }
+
+ public override string AppOnlyAccessTokenForSPAppWeb
+ {
+ get
+ {
+ if (this.SPAppWebUrl == null)
+ {
+ return null;
+ }
+
+ return GetAccessTokenString(ref this.appOnlyAccessTokenForSPAppWeb,
+ () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPAppWebUrl, null));
+ }
+ }
+
+ public SharePointHighTrustContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, WindowsIdentity logonUserIdentity)
+ : base(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber)
+ {
+ if (logonUserIdentity == null)
+ {
+ throw new ArgumentNullException("logonUserIdentity");
+ }
+
+ this.logonUserIdentity = logonUserIdentity;
+ }
+
+ ///
+ /// Ensures the access token is valid and returns it.
+ ///
+ /// The access token to verify.
+ /// The token renewal handler.
+ /// The access token string.
+ private static string GetAccessTokenString(ref Tuple accessToken, Func tokenRenewalHandler)
+ {
+ RenewAccessTokenIfNeeded(ref accessToken, tokenRenewalHandler);
+
+ return IsAccessTokenValid(accessToken) ? accessToken.Item1 : null;
+ }
+
+ ///
+ /// Renews the access token if it is not valid.
+ ///
+ /// The access token to renew.
+ /// The token renewal handler.
+ private static void RenewAccessTokenIfNeeded(ref Tuple accessToken, Func tokenRenewalHandler)
+ {
+ if (IsAccessTokenValid(accessToken))
+ {
+ return;
+ }
+
+ DateTime expiresOn = DateTime.UtcNow.Add(TokenHelper.HighTrustAccessTokenLifetime);
+
+ if (TokenHelper.HighTrustAccessTokenLifetime > AccessTokenLifetimeTolerance)
+ {
+ // Make the access token get renewed a bit earlier than the time when it expires
+ // so that the calls to SharePoint with it will have enough time to complete successfully.
+ expiresOn -= AccessTokenLifetimeTolerance;
+ }
+
+ accessToken = Tuple.Create(tokenRenewalHandler(), expiresOn);
+ }
+ }
+
+ ///
+ /// Default provider for SharePointHighTrustContext.
+ ///
+ public class SharePointHighTrustContextProvider : SharePointContextProvider
+ {
+ private const string SPContextKey = "SPContext";
+
+ protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
+ {
+ WindowsIdentity logonUserIdentity = httpRequest.LogonUserIdentity;
+ if (logonUserIdentity == null || !logonUserIdentity.IsAuthenticated || logonUserIdentity.IsGuest || logonUserIdentity.User == null)
+ {
+ return null;
+ }
+
+ return new SharePointHighTrustContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, logonUserIdentity);
+ }
+
+ protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
+ {
+ SharePointHighTrustContext spHighTrustContext = spContext as SharePointHighTrustContext;
+
+ if (spHighTrustContext != null)
+ {
+ Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
+ WindowsIdentity logonUserIdentity = httpContext.Request.LogonUserIdentity;
+
+ return spHostUrl == spHighTrustContext.SPHostUrl &&
+ logonUserIdentity != null &&
+ logonUserIdentity.IsAuthenticated &&
+ !logonUserIdentity.IsGuest &&
+ logonUserIdentity.User == spHighTrustContext.LogonUserIdentity.User;
+ }
+
+ return false;
+ }
+
+ protected override SharePointContext LoadSharePointContext(HttpContextBase httpContext)
+ {
+ return httpContext.Session[SPContextKey] as SharePointHighTrustContext;
+ }
+
+ protected override void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
+ {
+ httpContext.Session[SPContextKey] = spContext as SharePointHighTrustContext;
+ }
+ }
+
+ #endregion HighTrust
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/TokenHelper.cs b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/TokenHelper.cs
new file mode 100755
index 000000000..4ce8467b5
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/TokenHelper.cs
@@ -0,0 +1,1223 @@
+using Microsoft.IdentityModel;
+using Microsoft.IdentityModel.S2S.Protocols.OAuth2;
+using Microsoft.IdentityModel.S2S.Tokens;
+using Microsoft.SharePoint.Client;
+using Microsoft.SharePoint.Client.EventReceivers;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IdentityModel.Selectors;
+using System.IdentityModel.Tokens;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.ServiceModel;
+using System.Text;
+using System.Web;
+using System.Web.Configuration;
+using System.Web.Script.Serialization;
+using AudienceRestriction = Microsoft.IdentityModel.Tokens.AudienceRestriction;
+using AudienceUriValidationFailedException = Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException;
+using SecurityTokenHandlerConfiguration = Microsoft.IdentityModel.Tokens.SecurityTokenHandlerConfiguration;
+using X509SigningCredentials = Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials;
+
+namespace pnp.api.elevatedprivileges
+{
+ public static class TokenHelper
+ {
+ #region public fields
+
+ ///
+ /// SharePoint principal.
+ ///
+ public const string SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000";
+
+ ///
+ /// Lifetime of HighTrust access token, 12 hours.
+ ///
+ public static readonly TimeSpan HighTrustAccessTokenLifetime = TimeSpan.FromHours(12.0);
+
+ #endregion public fields
+
+ #region public methods
+
+ ///
+ /// Retrieves the context token string from the specified request by looking for well-known parameter names in the
+ /// POSTed form parameters and the querystring. Returns null if no context token is found.
+ ///
+ /// HttpRequest in which to look for a context token
+ /// The context token string
+ public static string GetContextTokenFromRequest(HttpRequest request)
+ {
+ return GetContextTokenFromRequest(new HttpRequestWrapper(request));
+ }
+
+ ///
+ /// Retrieves the context token string from the specified request by looking for well-known parameter names in the
+ /// POSTed form parameters and the querystring. Returns null if no context token is found.
+ ///
+ /// HttpRequest in which to look for a context token
+ /// The context token string
+ public static string GetContextTokenFromRequest(HttpRequestBase request)
+ {
+ string[] paramNames = { "AppContext", "AppContextToken", "AccessToken", "SPAppToken" };
+ foreach (string paramName in paramNames)
+ {
+ if (!string.IsNullOrEmpty(request.Form[paramName]))
+ {
+ return request.Form[paramName];
+ }
+ if (!string.IsNullOrEmpty(request.QueryString[paramName]))
+ {
+ return request.QueryString[paramName];
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Validate that a specified context token string is intended for this application based on the parameters
+ /// specified in web.config. Parameters used from web.config used for validation include ClientId,
+ /// HostedAppHostNameOverride, HostedAppHostName, ClientSecret, and Realm (if it is specified). If HostedAppHostNameOverride is present,
+ /// it will be used for validation. Otherwise, if the is not
+ /// null, it is used for validation instead of the web.config's HostedAppHostName. If the token is invalid, an
+ /// exception is thrown. If the token is valid, TokenHelper's static STS metadata url is updated based on the token contents
+ /// and a JsonWebSecurityToken based on the context token is returned.
+ ///
+ /// The context token to validate
+ /// The URL authority, consisting of Domain Name System (DNS) host name or IP address and the port number, to use for token audience validation.
+ /// If null, HostedAppHostName web.config setting is used instead. HostedAppHostNameOverride web.config setting, if present, will be used
+ /// for validation instead of .
+ /// A JsonWebSecurityToken based on the context token.
+ public static SharePointContextToken ReadAndValidateContextToken(string contextTokenString, string appHostName = null)
+ {
+ JsonWebSecurityTokenHandler tokenHandler = CreateJsonWebSecurityTokenHandler();
+ SecurityToken securityToken = tokenHandler.ReadToken(contextTokenString);
+ JsonWebSecurityToken jsonToken = securityToken as JsonWebSecurityToken;
+ SharePointContextToken token = SharePointContextToken.Create(jsonToken);
+
+ string stsAuthority = (new Uri(token.SecurityTokenServiceUri)).Authority;
+ int firstDot = stsAuthority.IndexOf('.');
+
+ GlobalEndPointPrefix = stsAuthority.Substring(0, firstDot);
+ AcsHostUrl = stsAuthority.Substring(firstDot + 1);
+
+ tokenHandler.ValidateToken(jsonToken);
+
+ string[] acceptableAudiences;
+ if (!String.IsNullOrEmpty(HostedAppHostNameOverride))
+ {
+ acceptableAudiences = HostedAppHostNameOverride.Split(';');
+ }
+ else if (appHostName == null)
+ {
+ acceptableAudiences = new[] { HostedAppHostName };
+ }
+ else
+ {
+ acceptableAudiences = new[] { appHostName };
+ }
+
+ bool validationSuccessful = false;
+ string realm = Realm ?? token.Realm;
+ foreach (var audience in acceptableAudiences)
+ {
+ string principal = GetFormattedPrincipal(ClientId, audience, realm);
+ if (StringComparer.OrdinalIgnoreCase.Equals(token.Audience, principal))
+ {
+ validationSuccessful = true;
+ break;
+ }
+ }
+
+ if (!validationSuccessful)
+ {
+ throw new AudienceUriValidationFailedException(
+ String.Format(CultureInfo.CurrentCulture,
+ "\"{0}\" is not the intended audience \"{1}\"", String.Join(";", acceptableAudiences), token.Audience));
+ }
+
+ return token;
+ }
+
+ ///
+ /// Retrieves an access token from ACS to call the source of the specified context token at the specified
+ /// targetHost. The targetHost must be registered for the principal that sent the context token.
+ ///
+ /// Context token issued by the intended access token audience
+ /// Url authority of the target principal
+ /// An access token with an audience matching the context token's source
+ public static OAuth2AccessTokenResponse GetAccessToken(SharePointContextToken contextToken, string targetHost)
+ {
+ string targetPrincipalName = contextToken.TargetPrincipalName;
+
+ // Extract the refreshToken from the context token
+ string refreshToken = contextToken.RefreshToken;
+
+ if (String.IsNullOrEmpty(refreshToken))
+ {
+ return null;
+ }
+
+ string targetRealm = Realm ?? contextToken.Realm;
+
+ return GetAccessToken(refreshToken,
+ targetPrincipalName,
+ targetHost,
+ targetRealm);
+ }
+
+ ///
+ /// Uses the specified authorization code to retrieve an access token from ACS to call the specified principal
+ /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
+ /// null, the "Realm" setting in web.config will be used instead.
+ ///
+ /// Authorization code to exchange for access token
+ /// Name of the target principal to retrieve an access token for
+ /// Url authority of the target principal
+ /// Realm to use for the access token's nameid and audience
+ /// Redirect URI registerd for this app
+ /// An access token with an audience of the target principal
+ public static OAuth2AccessTokenResponse GetAccessToken(
+ string authorizationCode,
+ string targetPrincipalName,
+ string targetHost,
+ string targetRealm,
+ Uri redirectUri)
+ {
+ if (targetRealm == null)
+ {
+ targetRealm = Realm;
+ }
+
+ string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
+ string clientId = GetFormattedPrincipal(ClientId, null, targetRealm);
+
+ // Create request for token. The RedirectUri is null here. This will fail if redirect uri is registered
+ OAuth2AccessTokenRequest oauth2Request =
+ OAuth2MessageFactory.CreateAccessTokenRequestWithAuthorizationCode(
+ clientId,
+ ClientSecret,
+ authorizationCode,
+ redirectUri,
+ resource);
+
+ // Get token
+ OAuth2S2SClient client = new OAuth2S2SClient();
+ OAuth2AccessTokenResponse oauth2Response;
+ try
+ {
+ oauth2Response =
+ client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
+ }
+ catch (WebException wex)
+ {
+ using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
+ {
+ string responseText = sr.ReadToEnd();
+ throw new WebException(wex.Message + " - " + responseText, wex);
+ }
+ }
+
+ return oauth2Response;
+ }
+
+ ///
+ /// Uses the specified refresh token to retrieve an access token from ACS to call the specified principal
+ /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
+ /// null, the "Realm" setting in web.config will be used instead.
+ ///
+ /// Refresh token to exchange for access token
+ /// Name of the target principal to retrieve an access token for
+ /// Url authority of the target principal
+ /// Realm to use for the access token's nameid and audience
+ /// An access token with an audience of the target principal
+ public static OAuth2AccessTokenResponse GetAccessToken(
+ string refreshToken,
+ string targetPrincipalName,
+ string targetHost,
+ string targetRealm)
+ {
+ if (targetRealm == null)
+ {
+ targetRealm = Realm;
+ }
+
+ string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
+ string clientId = GetFormattedPrincipal(ClientId, null, targetRealm);
+
+ OAuth2AccessTokenRequest oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithRefreshToken(clientId, ClientSecret, refreshToken, resource);
+
+ // Get token
+ OAuth2S2SClient client = new OAuth2S2SClient();
+ OAuth2AccessTokenResponse oauth2Response;
+ try
+ {
+ oauth2Response =
+ client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
+ }
+ catch (WebException wex)
+ {
+ using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
+ {
+ string responseText = sr.ReadToEnd();
+ throw new WebException(wex.Message + " - " + responseText, wex);
+ }
+ }
+
+ return oauth2Response;
+ }
+
+ ///
+ /// Retrieves an app-only access token from ACS to call the specified principal
+ /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
+ /// null, the "Realm" setting in web.config will be used instead.
+ ///
+ /// Name of the target principal to retrieve an access token for
+ /// Url authority of the target principal
+ /// Realm to use for the access token's nameid and audience
+ /// An access token with an audience of the target principal
+ public static OAuth2AccessTokenResponse GetAppOnlyAccessToken(
+ string targetPrincipalName,
+ string targetHost,
+ string targetRealm)
+ {
+
+ if (targetRealm == null)
+ {
+ targetRealm = Realm;
+ }
+
+ string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
+ string clientId = GetFormattedPrincipal(ClientId, HostedAppHostName, targetRealm);
+
+ OAuth2AccessTokenRequest oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithClientCredentials(clientId, ClientSecret, resource);
+ oauth2Request.Resource = resource;
+
+ // Get token
+ OAuth2S2SClient client = new OAuth2S2SClient();
+
+ OAuth2AccessTokenResponse oauth2Response;
+ try
+ {
+ oauth2Response =
+ client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
+ }
+ catch (WebException wex)
+ {
+ using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
+ {
+ string responseText = sr.ReadToEnd();
+ throw new WebException(wex.Message + " - " + responseText, wex);
+ }
+ }
+
+ return oauth2Response;
+ }
+
+ ///
+ /// Creates a client context based on the properties of a remote event receiver
+ ///
+ /// Properties of a remote event receiver
+ /// A ClientContext ready to call the web where the event originated
+ public static ClientContext CreateRemoteEventReceiverClientContext(SPRemoteEventProperties properties)
+ {
+ Uri sharepointUrl;
+ if (properties.ListEventProperties != null)
+ {
+ sharepointUrl = new Uri(properties.ListEventProperties.WebUrl);
+ }
+ else if (properties.ItemEventProperties != null)
+ {
+ sharepointUrl = new Uri(properties.ItemEventProperties.WebUrl);
+ }
+ else if (properties.WebEventProperties != null)
+ {
+ sharepointUrl = new Uri(properties.WebEventProperties.FullUrl);
+ }
+ else
+ {
+ return null;
+ }
+
+ if (IsHighTrustApp())
+ {
+ return GetS2SClientContextWithWindowsIdentity(sharepointUrl, null);
+ }
+
+ return CreateAcsClientContextForUrl(properties, sharepointUrl);
+ }
+
+ ///
+ /// Creates a client context based on the properties of an app event
+ ///
+ /// Properties of an app event
+ /// True to target the app web, false to target the host web
+ /// A ClientContext ready to call the app web or the parent web
+ public static ClientContext CreateAppEventClientContext(SPRemoteEventProperties properties, bool useAppWeb)
+ {
+ if (properties.AppEventProperties == null)
+ {
+ return null;
+ }
+
+ Uri sharepointUrl = useAppWeb ? properties.AppEventProperties.AppWebFullUrl : properties.AppEventProperties.HostWebFullUrl;
+ if (IsHighTrustApp())
+ {
+ return GetS2SClientContextWithWindowsIdentity(sharepointUrl, null);
+ }
+
+ return CreateAcsClientContextForUrl(properties, sharepointUrl);
+ }
+
+ ///
+ /// Retrieves an access token from ACS using the specified authorization code, and uses that access token to
+ /// create a client context
+ ///
+ /// Url of the target SharePoint site
+ /// Authorization code to use when retrieving the access token from ACS
+ /// Redirect URI registerd for this app
+ /// A ClientContext ready to call targetUrl with a valid access token
+ public static ClientContext GetClientContextWithAuthorizationCode(
+ string targetUrl,
+ string authorizationCode,
+ Uri redirectUri)
+ {
+ return GetClientContextWithAuthorizationCode(targetUrl, SharePointPrincipal, authorizationCode, GetRealmFromTargetUrl(new Uri(targetUrl)), redirectUri);
+ }
+
+ ///
+ /// Retrieves an access token from ACS using the specified authorization code, and uses that access token to
+ /// create a client context
+ ///
+ /// Url of the target SharePoint site
+ /// Name of the target SharePoint principal
+ /// Authorization code to use when retrieving the access token from ACS
+ /// Realm to use for the access token's nameid and audience
+ /// Redirect URI registerd for this app
+ /// A ClientContext ready to call targetUrl with a valid access token
+ public static ClientContext GetClientContextWithAuthorizationCode(
+ string targetUrl,
+ string targetPrincipalName,
+ string authorizationCode,
+ string targetRealm,
+ Uri redirectUri)
+ {
+ Uri targetUri = new Uri(targetUrl);
+
+ string accessToken =
+ GetAccessToken(authorizationCode, targetPrincipalName, targetUri.Authority, targetRealm, redirectUri).AccessToken;
+
+ return GetClientContextWithAccessToken(targetUrl, accessToken);
+ }
+
+ ///
+ /// Uses the specified access token to create a client context
+ ///
+ /// Url of the target SharePoint site
+ /// Access token to be used when calling the specified targetUrl
+ /// A ClientContext ready to call targetUrl with the specified access token
+ public static ClientContext GetClientContextWithAccessToken(string targetUrl, string accessToken)
+ {
+ ClientContext clientContext = new ClientContext(targetUrl);
+
+ clientContext.AuthenticationMode = ClientAuthenticationMode.Anonymous;
+ clientContext.FormDigestHandlingEnabled = false;
+ clientContext.ExecutingWebRequest +=
+ delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
+ {
+ webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
+ "Bearer " + accessToken;
+ };
+
+ return clientContext;
+ }
+
+ ///
+ /// Retrieves an access token from ACS using the specified context token, and uses that access token to create
+ /// a client context
+ ///
+ /// Url of the target SharePoint site
+ /// Context token received from the target SharePoint site
+ /// Url authority of the hosted app. If this is null, the value in the HostedAppHostName
+ /// of web.config will be used instead
+ /// A ClientContext ready to call targetUrl with a valid access token
+ public static ClientContext GetClientContextWithContextToken(
+ string targetUrl,
+ string contextTokenString,
+ string appHostUrl)
+ {
+ SharePointContextToken contextToken = ReadAndValidateContextToken(contextTokenString, appHostUrl);
+
+ Uri targetUri = new Uri(targetUrl);
+
+ string accessToken = GetAccessToken(contextToken, targetUri.Authority).AccessToken;
+
+ return GetClientContextWithAccessToken(targetUrl, accessToken);
+ }
+
+ ///
+ /// Returns the SharePoint url to which the app should redirect the browser to request consent and get back
+ /// an authorization code.
+ ///
+ /// Absolute Url of the SharePoint site
+ /// Space-delimited permissions to request from the SharePoint site in "shorthand" format
+ /// (e.g. "Web.Read Site.Write")
+ /// Url of the SharePoint site's OAuth authorization page
+ public static string GetAuthorizationUrl(string contextUrl, string scope)
+ {
+ return string.Format(
+ "{0}{1}?IsDlg=1&client_id={2}&scope={3}&response_type=code",
+ EnsureTrailingSlash(contextUrl),
+ AuthorizationPage,
+ ClientId,
+ scope);
+ }
+
+ ///
+ /// Returns the SharePoint url to which the app should redirect the browser to request consent and get back
+ /// an authorization code.
+ ///
+ /// Absolute Url of the SharePoint site
+ /// Space-delimited permissions to request from the SharePoint site in "shorthand" format
+ /// (e.g. "Web.Read Site.Write")
+ /// Uri to which SharePoint should redirect the browser to after consent is
+ /// granted
+ /// Url of the SharePoint site's OAuth authorization page
+ public static string GetAuthorizationUrl(string contextUrl, string scope, string redirectUri)
+ {
+ return string.Format(
+ "{0}{1}?IsDlg=1&client_id={2}&scope={3}&response_type=code&redirect_uri={4}",
+ EnsureTrailingSlash(contextUrl),
+ AuthorizationPage,
+ ClientId,
+ scope,
+ redirectUri);
+ }
+
+ ///
+ /// Returns the SharePoint url to which the app should redirect the browser to request a new context token.
+ ///
+ /// Absolute Url of the SharePoint site
+ /// Uri to which SharePoint should redirect the browser to with a context token
+ /// Url of the SharePoint site's context token redirect page
+ public static string GetAppContextTokenRequestUrl(string contextUrl, string redirectUri)
+ {
+ return string.Format(
+ "{0}{1}?client_id={2}&redirect_uri={3}",
+ EnsureTrailingSlash(contextUrl),
+ RedirectPage,
+ ClientId,
+ redirectUri);
+ }
+
+ ///
+ /// Retrieves an S2S access token signed by the application's private certificate on behalf of the specified
+ /// WindowsIdentity and intended for the SharePoint at the targetApplicationUri. If no Realm is specified in
+ /// web.config, an auth challenge will be issued to the targetApplicationUri to discover it.
+ ///
+ /// Url of the target SharePoint site
+ /// Windows identity of the user on whose behalf to create the access token
+ /// An access token with an audience of the target principal
+ public static string GetS2SAccessTokenWithWindowsIdentity(
+ Uri targetApplicationUri,
+ WindowsIdentity identity)
+ {
+ string realm = string.IsNullOrEmpty(Realm) ? GetRealmFromTargetUrl(targetApplicationUri) : Realm;
+
+ JsonWebTokenClaim[] claims = identity != null ? GetClaimsWithWindowsIdentity(identity) : null;
+
+ return GetS2SAccessTokenWithClaims(targetApplicationUri.Authority, realm, claims);
+ }
+
+ ///
+ /// Retrieves an S2S client context with an access token signed by the application's private certificate on
+ /// behalf of the specified WindowsIdentity and intended for application at the targetApplicationUri using the
+ /// targetRealm. If no Realm is specified in web.config, an auth challenge will be issued to the
+ /// targetApplicationUri to discover it.
+ ///
+ /// Url of the target SharePoint site
+ /// Windows identity of the user on whose behalf to create the access token
+ /// A ClientContext using an access token with an audience of the target application
+ public static ClientContext GetS2SClientContextWithWindowsIdentity(
+ Uri targetApplicationUri,
+ WindowsIdentity identity)
+ {
+ string realm = string.IsNullOrEmpty(Realm) ? GetRealmFromTargetUrl(targetApplicationUri) : Realm;
+
+ JsonWebTokenClaim[] claims = identity != null ? GetClaimsWithWindowsIdentity(identity) : null;
+
+ string accessToken = GetS2SAccessTokenWithClaims(targetApplicationUri.Authority, realm, claims);
+
+ return GetClientContextWithAccessToken(targetApplicationUri.ToString(), accessToken);
+ }
+
+ ///
+ /// Get authentication realm from SharePoint
+ ///
+ /// Url of the target SharePoint site
+ /// String representation of the realm GUID
+ public static string GetRealmFromTargetUrl(Uri targetApplicationUri)
+ {
+ WebRequest request = WebRequest.Create(targetApplicationUri + "/_vti_bin/client.svc");
+ request.Headers.Add("Authorization: Bearer ");
+
+ try
+ {
+ using (request.GetResponse())
+ {
+ }
+ }
+ catch (WebException e)
+ {
+ if (e.Response == null)
+ {
+ return null;
+ }
+
+ string bearerResponseHeader = e.Response.Headers["WWW-Authenticate"];
+ if (string.IsNullOrEmpty(bearerResponseHeader))
+ {
+ return null;
+ }
+
+ const string bearer = "Bearer realm=\"";
+ int bearerIndex = bearerResponseHeader.IndexOf(bearer, StringComparison.Ordinal);
+ if (bearerIndex < 0)
+ {
+ return null;
+ }
+
+ int realmIndex = bearerIndex + bearer.Length;
+
+ if (bearerResponseHeader.Length >= realmIndex + 36)
+ {
+ string targetRealm = bearerResponseHeader.Substring(realmIndex, 36);
+
+ Guid realmGuid;
+
+ if (Guid.TryParse(targetRealm, out realmGuid))
+ {
+ return targetRealm;
+ }
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Determines if this is a high trust app.
+ ///
+ /// True if this is a high trust app.
+ public static bool IsHighTrustApp()
+ {
+ return SigningCredentials != null;
+ }
+
+ ///
+ /// Ensures that the specified URL ends with '/' if it is not null or empty.
+ ///
+ /// The url.
+ /// The url ending with '/' if it is not null or empty.
+ public static string EnsureTrailingSlash(string url)
+ {
+ if (!string.IsNullOrEmpty(url) && url[url.Length - 1] != '/')
+ {
+ return url + "/";
+ }
+
+ return url;
+ }
+
+ #endregion
+
+ #region private fields
+
+ //
+ // Configuration Constants
+ //
+
+ private const string AuthorizationPage = "_layouts/15/OAuthAuthorize.aspx";
+ private const string RedirectPage = "_layouts/15/AppRedirect.aspx";
+ private const string AcsPrincipalName = "00000001-0000-0000-c000-000000000000";
+ private const string AcsMetadataEndPointRelativeUrl = "metadata/json/1";
+ private const string S2SProtocol = "OAuth2";
+ private const string DelegationIssuance = "DelegationIssuance1.0";
+ private const string NameIdentifierClaimType = JsonWebTokenConstants.ReservedClaims.NameIdentifier;
+ private const string TrustedForImpersonationClaimType = "trustedfordelegation";
+ private const string ActorTokenClaimType = JsonWebTokenConstants.ReservedClaims.ActorToken;
+
+ //
+ // Environment Constants
+ //
+
+ private static string GlobalEndPointPrefix = "accounts";
+ private static string AcsHostUrl = "accesscontrol.windows.net";
+
+ //
+ // Hosted app configuration
+ //
+ private static readonly string ClientId = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("ClientId")) ? WebConfigurationManager.AppSettings.Get("HostedAppName") : WebConfigurationManager.AppSettings.Get("ClientId");
+ private static readonly string IssuerId = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("IssuerId")) ? ClientId : WebConfigurationManager.AppSettings.Get("IssuerId");
+ private static readonly string HostedAppHostNameOverride = WebConfigurationManager.AppSettings.Get("HostedAppHostNameOverride");
+ private static readonly string HostedAppHostName = WebConfigurationManager.AppSettings.Get("HostedAppHostName");
+ private static readonly string ClientSecret = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("ClientSecret")) ? WebConfigurationManager.AppSettings.Get("HostedAppSigningKey") : WebConfigurationManager.AppSettings.Get("ClientSecret");
+ private static readonly string SecondaryClientSecret = WebConfigurationManager.AppSettings.Get("SecondaryClientSecret");
+ private static readonly string Realm = WebConfigurationManager.AppSettings.Get("Realm");
+ private static readonly string ServiceNamespace = WebConfigurationManager.AppSettings.Get("Realm");
+
+ private static readonly string ClientSigningCertificatePath = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePath");
+ private static readonly string ClientSigningCertificatePassword = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePassword");
+ private static readonly X509Certificate2 ClientCertificate = (string.IsNullOrEmpty(ClientSigningCertificatePath) || string.IsNullOrEmpty(ClientSigningCertificatePassword)) ? null : new X509Certificate2(ClientSigningCertificatePath, ClientSigningCertificatePassword);
+ private static readonly X509SigningCredentials SigningCredentials = (ClientCertificate == null) ? null : new X509SigningCredentials(ClientCertificate, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);
+
+ #endregion
+
+ #region private methods
+
+ private static ClientContext CreateAcsClientContextForUrl(SPRemoteEventProperties properties, Uri sharepointUrl)
+ {
+ string contextTokenString = properties.ContextToken;
+
+ if (String.IsNullOrEmpty(contextTokenString))
+ {
+ return null;
+ }
+
+ SharePointContextToken contextToken = ReadAndValidateContextToken(contextTokenString, OperationContext.Current.IncomingMessageHeaders.To.Host);
+ string accessToken = GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken;
+
+ return GetClientContextWithAccessToken(sharepointUrl.ToString(), accessToken);
+ }
+
+ private static string GetAcsMetadataEndpointUrl()
+ {
+ return Path.Combine(GetAcsGlobalEndpointUrl(), AcsMetadataEndPointRelativeUrl);
+ }
+
+ private static string GetFormattedPrincipal(string principalName, string hostName, string realm)
+ {
+ if (!String.IsNullOrEmpty(hostName))
+ {
+ return String.Format(CultureInfo.InvariantCulture, "{0}/{1}@{2}", principalName, hostName, realm);
+ }
+
+ return String.Format(CultureInfo.InvariantCulture, "{0}@{1}", principalName, realm);
+ }
+
+ private static string GetAcsPrincipalName(string realm)
+ {
+ return GetFormattedPrincipal(AcsPrincipalName, new Uri(GetAcsGlobalEndpointUrl()).Host, realm);
+ }
+
+ private static string GetAcsGlobalEndpointUrl()
+ {
+ return String.Format(CultureInfo.InvariantCulture, "https://{0}.{1}/", GlobalEndPointPrefix, AcsHostUrl);
+ }
+
+ private static JsonWebSecurityTokenHandler CreateJsonWebSecurityTokenHandler()
+ {
+ JsonWebSecurityTokenHandler handler = new JsonWebSecurityTokenHandler();
+ handler.Configuration = new SecurityTokenHandlerConfiguration();
+ handler.Configuration.AudienceRestriction = new AudienceRestriction(AudienceUriMode.Never);
+ handler.Configuration.CertificateValidator = X509CertificateValidator.None;
+
+ List securityKeys = new List();
+ securityKeys.Add(Convert.FromBase64String(ClientSecret));
+ if (!string.IsNullOrEmpty(SecondaryClientSecret))
+ {
+ securityKeys.Add(Convert.FromBase64String(SecondaryClientSecret));
+ }
+
+ List securityTokens = new List();
+ securityTokens.Add(new MultipleSymmetricKeySecurityToken(securityKeys));
+
+ handler.Configuration.IssuerTokenResolver =
+ SecurityTokenResolver.CreateDefaultSecurityTokenResolver(
+ new ReadOnlyCollection(securityTokens),
+ false);
+ SymmetricKeyIssuerNameRegistry issuerNameRegistry = new SymmetricKeyIssuerNameRegistry();
+ foreach (byte[] securitykey in securityKeys)
+ {
+ issuerNameRegistry.AddTrustedIssuer(securitykey, GetAcsPrincipalName(ServiceNamespace));
+ }
+ handler.Configuration.IssuerNameRegistry = issuerNameRegistry;
+ return handler;
+ }
+
+ private static string GetS2SAccessTokenWithClaims(
+ string targetApplicationHostName,
+ string targetRealm,
+ IEnumerable claims)
+ {
+ return IssueToken(
+ ClientId,
+ IssuerId,
+ targetRealm,
+ SharePointPrincipal,
+ targetRealm,
+ targetApplicationHostName,
+ true,
+ claims,
+ claims == null);
+ }
+
+ private static JsonWebTokenClaim[] GetClaimsWithWindowsIdentity(WindowsIdentity identity)
+ {
+ JsonWebTokenClaim[] claims = new JsonWebTokenClaim[]
+ {
+ new JsonWebTokenClaim(NameIdentifierClaimType, identity.User.Value.ToLower()),
+ new JsonWebTokenClaim("nii", "urn:office:idp:activedirectory")
+ };
+ return claims;
+ }
+
+ private static string IssueToken(
+ string sourceApplication,
+ string issuerApplication,
+ string sourceRealm,
+ string targetApplication,
+ string targetRealm,
+ string targetApplicationHostName,
+ bool trustedForDelegation,
+ IEnumerable claims,
+ bool appOnly = false)
+ {
+ if (null == SigningCredentials)
+ {
+ throw new InvalidOperationException("SigningCredentials was not initialized");
+ }
+
+ #region Actor token
+
+ string issuer = string.IsNullOrEmpty(sourceRealm) ? issuerApplication : string.Format("{0}@{1}", issuerApplication, sourceRealm);
+ string nameid = string.IsNullOrEmpty(sourceRealm) ? sourceApplication : string.Format("{0}@{1}", sourceApplication, sourceRealm);
+ string audience = string.Format("{0}/{1}@{2}", targetApplication, targetApplicationHostName, targetRealm);
+
+ List actorClaims = new List();
+ actorClaims.Add(new JsonWebTokenClaim(JsonWebTokenConstants.ReservedClaims.NameIdentifier, nameid));
+ if (trustedForDelegation && !appOnly)
+ {
+ actorClaims.Add(new JsonWebTokenClaim(TrustedForImpersonationClaimType, "true"));
+ }
+
+ // Create token
+ JsonWebSecurityToken actorToken = new JsonWebSecurityToken(
+ issuer: issuer,
+ audience: audience,
+ validFrom: DateTime.UtcNow,
+ validTo: DateTime.UtcNow.Add(HighTrustAccessTokenLifetime),
+ signingCredentials: SigningCredentials,
+ claims: actorClaims);
+
+ string actorTokenString = new JsonWebSecurityTokenHandler().WriteTokenAsString(actorToken);
+
+ if (appOnly)
+ {
+ // App-only token is the same as actor token for delegated case
+ return actorTokenString;
+ }
+
+ #endregion Actor token
+
+ #region Outer token
+
+ List outerClaims = null == claims ? new List() : new List(claims);
+ outerClaims.Add(new JsonWebTokenClaim(ActorTokenClaimType, actorTokenString));
+
+ JsonWebSecurityToken jsonToken = new JsonWebSecurityToken(
+ nameid, // outer token issuer should match actor token nameid
+ audience,
+ DateTime.UtcNow,
+ DateTime.UtcNow.Add(HighTrustAccessTokenLifetime),
+ outerClaims);
+
+ string accessToken = new JsonWebSecurityTokenHandler().WriteTokenAsString(jsonToken);
+
+ #endregion Outer token
+
+ return accessToken;
+ }
+
+ #endregion
+
+ #region AcsMetadataParser
+
+ // This class is used to get MetaData document from the global STS endpoint. It contains
+ // methods to parse the MetaData document and get endpoints and STS certificate.
+ public static class AcsMetadataParser
+ {
+ public static X509Certificate2 GetAcsSigningCert(string realm)
+ {
+ JsonMetadataDocument document = GetMetadataDocument(realm);
+
+ if (null != document.keys && document.keys.Count > 0)
+ {
+ JsonKey signingKey = document.keys[0];
+
+ if (null != signingKey && null != signingKey.keyValue)
+ {
+ return new X509Certificate2(Encoding.UTF8.GetBytes(signingKey.keyValue.value));
+ }
+ }
+
+ throw new Exception("Metadata document does not contain ACS signing certificate.");
+ }
+
+ public static string GetDelegationServiceUrl(string realm)
+ {
+ JsonMetadataDocument document = GetMetadataDocument(realm);
+
+ JsonEndpoint delegationEndpoint = document.endpoints.SingleOrDefault(e => e.protocol == DelegationIssuance);
+
+ if (null != delegationEndpoint)
+ {
+ return delegationEndpoint.location;
+ }
+ throw new Exception("Metadata document does not contain Delegation Service endpoint Url");
+ }
+
+ private static JsonMetadataDocument GetMetadataDocument(string realm)
+ {
+ string acsMetadataEndpointUrlWithRealm = String.Format(CultureInfo.InvariantCulture, "{0}?realm={1}",
+ GetAcsMetadataEndpointUrl(),
+ realm);
+ byte[] acsMetadata;
+ using (WebClient webClient = new WebClient())
+ {
+
+ acsMetadata = webClient.DownloadData(acsMetadataEndpointUrlWithRealm);
+ }
+ string jsonResponseString = Encoding.UTF8.GetString(acsMetadata);
+
+ JavaScriptSerializer serializer = new JavaScriptSerializer();
+ JsonMetadataDocument document = serializer.Deserialize(jsonResponseString);
+
+ if (null == document)
+ {
+ throw new Exception("No metadata document found at the global endpoint " + acsMetadataEndpointUrlWithRealm);
+ }
+
+ return document;
+ }
+
+ public static string GetStsUrl(string realm)
+ {
+ JsonMetadataDocument document = GetMetadataDocument(realm);
+
+ JsonEndpoint s2sEndpoint = document.endpoints.SingleOrDefault(e => e.protocol == S2SProtocol);
+
+ if (null != s2sEndpoint)
+ {
+ return s2sEndpoint.location;
+ }
+
+ throw new Exception("Metadata document does not contain STS endpoint url");
+ }
+
+ private class JsonMetadataDocument
+ {
+ public string serviceName { get; set; }
+ public List endpoints { get; set; }
+ public List keys { get; set; }
+ }
+
+ private class JsonEndpoint
+ {
+ public string location { get; set; }
+ public string protocol { get; set; }
+ public string usage { get; set; }
+ }
+
+ private class JsonKeyValue
+ {
+ public string type { get; set; }
+ public string value { get; set; }
+ }
+
+ private class JsonKey
+ {
+ public string usage { get; set; }
+ public JsonKeyValue keyValue { get; set; }
+ }
+ }
+
+ #endregion
+ }
+
+ ///
+ /// A JsonWebSecurityToken generated by SharePoint to authenticate to a 3rd party application and allow callbacks using a refresh token
+ ///
+ public class SharePointContextToken : JsonWebSecurityToken
+ {
+ public static SharePointContextToken Create(JsonWebSecurityToken contextToken)
+ {
+ return new SharePointContextToken(contextToken.Issuer, contextToken.Audience, contextToken.ValidFrom, contextToken.ValidTo, contextToken.Claims);
+ }
+
+ public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims)
+ : base(issuer, audience, validFrom, validTo, claims)
+ {
+ }
+
+ public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims, SecurityToken issuerToken, JsonWebSecurityToken actorToken)
+ : base(issuer, audience, validFrom, validTo, claims, issuerToken, actorToken)
+ {
+ }
+
+ public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims, SigningCredentials signingCredentials)
+ : base(issuer, audience, validFrom, validTo, claims, signingCredentials)
+ {
+ }
+
+ public string NameId
+ {
+ get
+ {
+ return GetClaimValue(this, "nameid");
+ }
+ }
+
+ ///
+ /// The principal name portion of the context token's "appctxsender" claim
+ ///
+ public string TargetPrincipalName
+ {
+ get
+ {
+ string appctxsender = GetClaimValue(this, "appctxsender");
+
+ if (appctxsender == null)
+ {
+ return null;
+ }
+
+ return appctxsender.Split('@')[0];
+ }
+ }
+
+ ///
+ /// The context token's "refreshtoken" claim
+ ///
+ public string RefreshToken
+ {
+ get
+ {
+ return GetClaimValue(this, "refreshtoken");
+ }
+ }
+
+ ///
+ /// The context token's "CacheKey" claim
+ ///
+ public string CacheKey
+ {
+ get
+ {
+ string appctx = GetClaimValue(this, "appctx");
+ if (appctx == null)
+ {
+ return null;
+ }
+
+ ClientContext ctx = new ClientContext("http://tempuri.org");
+ Dictionary dict = (Dictionary)ctx.ParseObjectFromJsonString(appctx);
+ string cacheKey = (string)dict["CacheKey"];
+
+ return cacheKey;
+ }
+ }
+
+ ///
+ /// The context token's "SecurityTokenServiceUri" claim
+ ///
+ public string SecurityTokenServiceUri
+ {
+ get
+ {
+ string appctx = GetClaimValue(this, "appctx");
+ if (appctx == null)
+ {
+ return null;
+ }
+
+ ClientContext ctx = new ClientContext("http://tempuri.org");
+ Dictionary dict = (Dictionary)ctx.ParseObjectFromJsonString(appctx);
+ string securityTokenServiceUri = (string)dict["SecurityTokenServiceUri"];
+
+ return securityTokenServiceUri;
+ }
+ }
+
+ ///
+ /// The realm portion of the context token's "audience" claim
+ ///
+ public string Realm
+ {
+ get
+ {
+ string aud = Audience;
+ if (aud == null)
+ {
+ return null;
+ }
+
+ string tokenRealm = aud.Substring(aud.IndexOf('@') + 1);
+
+ return tokenRealm;
+ }
+ }
+
+ private static string GetClaimValue(JsonWebSecurityToken token, string claimType)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException("token");
+ }
+
+ foreach (JsonWebTokenClaim claim in token.Claims)
+ {
+ if (StringComparer.Ordinal.Equals(claim.ClaimType, claimType))
+ {
+ return claim.Value;
+ }
+ }
+
+ return null;
+ }
+
+ }
+
+ ///
+ /// Represents a security token which contains multiple security keys that are generated using symmetric algorithms.
+ ///
+ public class MultipleSymmetricKeySecurityToken : SecurityToken
+ {
+ ///
+ /// Initializes a new instance of the MultipleSymmetricKeySecurityToken class.
+ ///
+ /// An enumeration of Byte arrays that contain the symmetric keys.
+ public MultipleSymmetricKeySecurityToken(IEnumerable keys)
+ : this(UniqueId.CreateUniqueId(), keys)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the MultipleSymmetricKeySecurityToken class.
+ ///
+ /// The unique identifier of the security token.
+ /// An enumeration of Byte arrays that contain the symmetric keys.
+ public MultipleSymmetricKeySecurityToken(string tokenId, IEnumerable keys)
+ {
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ if (String.IsNullOrEmpty(tokenId))
+ {
+ throw new ArgumentException("Value cannot be a null or empty string.", "tokenId");
+ }
+
+ foreach (byte[] key in keys)
+ {
+ if (key.Length <= 0)
+ {
+ throw new ArgumentException("The key length must be greater then zero.", "keys");
+ }
+ }
+
+ id = tokenId;
+ effectiveTime = DateTime.UtcNow;
+ securityKeys = CreateSymmetricSecurityKeys(keys);
+ }
+
+ ///
+ /// Gets the unique identifier of the security token.
+ ///
+ public override string Id
+ {
+ get
+ {
+ return id;
+ }
+ }
+
+ ///
+ /// Gets the cryptographic keys associated with the security token.
+ ///
+ public override ReadOnlyCollection SecurityKeys
+ {
+ get
+ {
+ return securityKeys.AsReadOnly();
+ }
+ }
+
+ ///
+ /// Gets the first instant in time at which this security token is valid.
+ ///
+ public override DateTime ValidFrom
+ {
+ get
+ {
+ return effectiveTime;
+ }
+ }
+
+ ///
+ /// Gets the last instant in time at which this security token is valid.
+ ///
+ public override DateTime ValidTo
+ {
+ get
+ {
+ // Never expire
+ return DateTime.MaxValue;
+ }
+ }
+
+ ///
+ /// Returns a value that indicates whether the key identifier for this instance can be resolved to the specified key identifier.
+ ///
+ /// A SecurityKeyIdentifierClause to compare to this instance
+ /// true if keyIdentifierClause is a SecurityKeyIdentifierClause and it has the same unique identifier as the Id property; otherwise, false.
+ public override bool MatchesKeyIdentifierClause(SecurityKeyIdentifierClause keyIdentifierClause)
+ {
+ if (keyIdentifierClause == null)
+ {
+ throw new ArgumentNullException("keyIdentifierClause");
+ }
+
+ // Since this is a symmetric token and we do not have IDs to distinguish tokens, we just check for the
+ // presence of a SymmetricIssuerKeyIdentifier. The actual mapping to the issuer takes place later
+ // when the key is matched to the issuer.
+ if (keyIdentifierClause is SymmetricIssuerKeyIdentifierClause)
+ {
+ return true;
+ }
+ return base.MatchesKeyIdentifierClause(keyIdentifierClause);
+ }
+
+ #region private members
+
+ private List CreateSymmetricSecurityKeys(IEnumerable keys)
+ {
+ List symmetricKeys = new List();
+ foreach (byte[] key in keys)
+ {
+ symmetricKeys.Add(new InMemorySymmetricSecurityKey(key));
+ }
+ return symmetricKeys;
+ }
+
+ private string id;
+ private DateTime effectiveTime;
+ private List securityKeys;
+
+ #endregion
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Debug.config b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Debug.config
new file mode 100755
index 000000000..d425e94b2
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Debug.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Release.config b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Release.config
new file mode 100755
index 000000000..829eb4b8d
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.Release.config
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.config b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.config
new file mode 100755
index 000000000..dac384758
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/Web.config
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/favicon.ico b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/favicon.ico
new file mode 100755
index 000000000..a3a799985
Binary files /dev/null and b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/favicon.ico differ
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/packages.config b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/packages.config
new file mode 100755
index 000000000..a3d90ad01
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/packages.config
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/pnp.api.elevatedprivileges.csproj b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/pnp.api.elevatedprivileges.csproj
new file mode 100755
index 000000000..47809b192
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/api/pnp.api.elevatedprivileges/pnp.api.elevatedprivileges.csproj
@@ -0,0 +1,295 @@
+
+
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {E9E8B537-849D-47B4-AA97-C3B7ED93227E}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ pnp.api.elevatedprivileges
+ pnp.api.elevatedprivileges
+ v4.5.2
+ false
+ true
+
+
+
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Microsoft.Azure.ActiveDirectory.GraphClient.2.1.0\lib\portable-net4+sl5+win+wpa+wp8\Microsoft.Azure.ActiveDirectory.GraphClient.dll
+ True
+
+
+ ..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll
+ True
+
+
+ ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll
+ True
+
+
+
+ ..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll
+ True
+
+
+ ..\packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll
+ True
+
+
+ ..\packages\Microsoft.Data.Services.Client.5.6.4\lib\net40\Microsoft.Data.Services.Client.dll
+ True
+
+
+ True
+
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.Office.Client.Policy.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.Office.Client.TranslationServices.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.Office.SharePoint.Tools.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.Online.SharePoint.Client.Tenant.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.ProjectServer.Client.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.DocumentManagement.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Publishing.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Runtime.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Runtime.Windows.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Search.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Search.Applications.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.Taxonomy.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.UserProfiles.dll
+ True
+
+
+ ..\packages\Microsoft.SharePointOnline.CSOM.16.1.5715.1200\lib\net45\Microsoft.SharePoint.Client.WorkflowServices.dll
+ True
+
+
+ ..\packages\WindowsAzure.Storage.7.0.0\lib\net40\Microsoft.WindowsAzure.Storage.dll
+ True
+
+
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\packages\SharePointPnPCoreOnline.2.8.1610.0\lib\net45\OfficeDevPnP.Core.dll
+ True
+
+
+
+
+
+
+
+
+ ..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+
+
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
+
+
+
+
+ True
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
+
+
+ ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
+
+
+ ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll
+
+
+ True
+ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
+
+
+ ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll
+
+
+ True
+ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
+
+
+ True
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
+
+
+ True
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll
+
+
+ True
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll
+
+
+ True
+ ..\packages\WebGrease.1.5.2\lib\WebGrease.dll
+
+
+ True
+ ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll
+
+
+
+
+
+
+
+
+
+ Global.asax
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+ Web.config
+
+
+ Web.config
+
+
+
+
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ 2495
+ /
+ http://localhost:2495/
+ False
+ False
+
+
+ False
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/assets/preview.png b/samples/react-sp-elevatedprivileges/assets/preview.png
new file mode 100644
index 000000000..5d6dad1ca
Binary files /dev/null and b/samples/react-sp-elevatedprivileges/assets/preview.png differ
diff --git a/samples/react-sp-elevatedprivileges/webpart/.editorconfig b/samples/react-sp-elevatedprivileges/webpart/.editorconfig
new file mode 100644
index 000000000..8ffcdc4ec
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.editorconfig
@@ -0,0 +1,25 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+
+# change these settings to your own preference
+indent_style = space
+indent_size = 2
+
+# we recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[{package,bower}.json]
+indent_style = space
+indent_size = 2
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/.gitattributes b/samples/react-sp-elevatedprivileges/webpart/.gitattributes
new file mode 100644
index 000000000..212566614
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/.gitignore b/samples/react-sp-elevatedprivileges/webpart/.gitignore
new file mode 100644
index 000000000..63c4ae010
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.gitignore
@@ -0,0 +1,32 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Dependency directories
+node_modules
+
+# Build generated files
+dist
+lib
+solution
+temp
+*.spapp
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# OSX
+.DS_Store
+
+# Visual Studio files
+.ntvs_analysis.dat
+.vs
+bin
+obj
+
+# Resx Generated Code
+*.resx.ts
+
+# Styles Generated Code
+*.scss.ts
diff --git a/samples/react-sp-elevatedprivileges/webpart/.npmignore b/samples/react-sp-elevatedprivileges/webpart/.npmignore
new file mode 100644
index 000000000..2c93a9384
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.npmignore
@@ -0,0 +1,14 @@
+# Folders
+.vscode
+coverage
+node_modules
+sharepoint
+src
+temp
+
+# Files
+*.csproj
+.git*
+.yo-rc.json
+gulpfile.js
+tsconfig.json
diff --git a/samples/react-sp-elevatedprivileges/webpart/.vscode/settings.json b/samples/react-sp-elevatedprivileges/webpart/.vscode/settings.json
new file mode 100644
index 000000000..ab46aaa4f
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.vscode/settings.json
@@ -0,0 +1,21 @@
+{
+ // The number of spaces a tab is equal to.
+ "editor.tabSize": 2,
+
+ // When enabled, will trim trailing whitespace when you save a file.
+ "files.trimTrailingWhitespace": true,
+
+ // Controls if the editor should automatically close brackets after opening them
+ "editor.autoClosingBrackets": false,
+
+ // Configure glob patterns for excluding files and folders.
+ "search.exclude": {
+ "**/bower_components": true,
+ "**/node_modules": true,
+ "coverage": true,
+ "dist": true,
+ "lib-amd": true,
+ "lib": true,
+ "temp": true
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/.vscode/tasks.json b/samples/react-sp-elevatedprivileges/webpart/.vscode/tasks.json
new file mode 100644
index 000000000..5204908d6
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.vscode/tasks.json
@@ -0,0 +1,34 @@
+{
+ // See http://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "0.1.0",
+ "command": "gulp",
+ "isShellCommand": true,
+ "showOutput": "always",
+ "args": [
+ "--no-color"
+ ],
+ "tasks": [
+ {
+ "taskName": "bundle",
+ "isBuildCommand": true,
+ "problemMatcher": [
+ "$tsc"
+ ]
+ },
+ {
+ "taskName": "test",
+ "isTestCommand": true,
+ "problemMatcher": [
+ "$tsc"
+ ]
+ },
+ {
+ "taskName": "serve",
+ "isWatching": true,
+ "problemMatcher": [
+ "$tsc"
+ ]
+ }
+ ]
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/.yo-rc.json b/samples/react-sp-elevatedprivileges/webpart/.yo-rc.json
new file mode 100644
index 000000000..711c4e282
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/.yo-rc.json
@@ -0,0 +1,7 @@
+{
+ "@microsoft/generator-sharepoint": {
+ "libraryName": "react-sp-elevatedprivileges",
+ "libraryId": "dbc8827c-729c-4e8f-9ecc-f0d57f279ba2",
+ "framework": "react"
+ }
+}
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/config.json b/samples/react-sp-elevatedprivileges/webpart/config/config.json
new file mode 100644
index 000000000..abbe2fcd4
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/config.json
@@ -0,0 +1,21 @@
+{
+ "entries": [
+ {
+ "entry": "./lib/webparts/createTask/CreateTaskWebPart.js",
+ "manifest": "./src/webparts/createTask/CreateTaskWebPart.manifest.json",
+ "outputPath": "./dist/create-task.bundle.js"
+ }
+ ],
+ "externals": {
+ "@microsoft/sp-client-base": "node_modules/@microsoft/sp-client-base/dist/sp-client-base.js",
+ "@microsoft/sp-client-preview": "node_modules/@microsoft/sp-client-preview/dist/sp-client-preview.js",
+ "@microsoft/sp-lodash-subset": "node_modules/@microsoft/sp-lodash-subset/dist/sp-lodash-subset.js",
+ "office-ui-fabric-react": "node_modules/office-ui-fabric-react/dist/office-ui-fabric-react.js",
+ "react": "node_modules/react/dist/react.min.js",
+ "react-dom": "node_modules/react-dom/dist/react-dom.min.js",
+ "react-dom/server": "node_modules/react-dom/dist/react-dom-server.min.js"
+ },
+ "localizedResources": {
+ "createTaskStrings": "webparts/createTask/loc/{locale}.js"
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/deploy-azure-storage.json b/samples/react-sp-elevatedprivileges/webpart/config/deploy-azure-storage.json
new file mode 100644
index 000000000..e8afa3920
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/deploy-azure-storage.json
@@ -0,0 +1,6 @@
+{
+ "workingDir": "./temp/deploy/",
+ "account": "",
+ "container": "react-sp-elevatedprivileges",
+ "accessKey": ""
+}
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/package-solution.json b/samples/react-sp-elevatedprivileges/webpart/config/package-solution.json
new file mode 100644
index 000000000..4d0261cbd
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/package-solution.json
@@ -0,0 +1,10 @@
+{
+ "solution": {
+ "name": "react-sp-elevatedprivileges-client-side-solution",
+ "id": "dbc8827c-729c-4e8f-9ecc-f0d57f279ba2",
+ "version": "1.0.0.0"
+ },
+ "paths": {
+ "zippedPackage": "solution/react-sp-elevatedprivileges.spapp"
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/prepare-deploy.json b/samples/react-sp-elevatedprivileges/webpart/config/prepare-deploy.json
new file mode 100644
index 000000000..6aca63656
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/prepare-deploy.json
@@ -0,0 +1,3 @@
+{
+ "deployCdnPath": "temp/deploy"
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/serve.json b/samples/react-sp-elevatedprivileges/webpart/config/serve.json
new file mode 100644
index 000000000..087899637
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/serve.json
@@ -0,0 +1,9 @@
+{
+ "port": 4321,
+ "initialPage": "https://localhost:5432/workbench",
+ "https": true,
+ "api": {
+ "port": 5432,
+ "entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/tslint.json b/samples/react-sp-elevatedprivileges/webpart/config/tslint.json
new file mode 100644
index 000000000..bf3362c87
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/tslint.json
@@ -0,0 +1,51 @@
+{
+ // Display errors as warnings
+ "displayAsWarning": true,
+ // The TSLint task may have been configured with several custom lint rules
+ // before this config file is read (for example lint rules from the tslint-microsoft-contrib
+ // project). If true, this flag will deactivate any of these rules.
+ "removeExistingRules": true,
+ // When true, the TSLint task is configured with some default TSLint "rules.":
+ "useDefaultConfigAsBase": false,
+ // Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
+ // which are active, other than the list of rules below.
+ "lintConfig": {
+ // Opt-in to Lint rules which help to eliminate bugs in JavaScript
+ "rules": {
+ "class-name": false,
+ "export-name": false,
+ "forin": false,
+ "label-position": false,
+ "label-undefined": false,
+ "member-access": true,
+ "no-arg": false,
+ "no-console": false,
+ "no-construct": false,
+ "no-duplicate-case": true,
+ "no-duplicate-key": false,
+ "no-duplicate-variable": true,
+ "no-eval": false,
+ "no-function-expression": true,
+ "no-internal-module": true,
+ "no-shadowed-variable": true,
+ "no-switch-case-fall-through": true,
+ "no-unnecessary-semicolons": true,
+ "no-unused-expression": true,
+ "no-unused-imports": true,
+ "no-unused-variable": true,
+ "no-unreachable": true,
+ "no-use-before-declare": true,
+ "no-with-statement": true,
+ "semicolon": true,
+ "trailing-comma": false,
+ "typedef": false,
+ "typedef-whitespace": false,
+ "use-named-parameter": true,
+ "valid-typeof": true,
+ "variable-name": false,
+ "whitespace": false,
+ "prefer-const": true,
+ "a11y-role": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/config/write-manifests.json b/samples/react-sp-elevatedprivileges/webpart/config/write-manifests.json
new file mode 100644
index 000000000..0a4bafb06
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/config/write-manifests.json
@@ -0,0 +1,3 @@
+{
+ "cdnBasePath": ""
+}
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/gulpfile.js b/samples/react-sp-elevatedprivileges/webpart/gulpfile.js
new file mode 100644
index 000000000..7d36ddb1c
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/gulpfile.js
@@ -0,0 +1,6 @@
+'use strict';
+
+const gulp = require('gulp');
+const build = require('@microsoft/sp-build-web');
+
+build.initialize(gulp);
diff --git a/samples/react-sp-elevatedprivileges/webpart/package.json b/samples/react-sp-elevatedprivileges/webpart/package.json
new file mode 100644
index 000000000..848ea4723
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "react-sp-elevatedprivileges",
+ "version": "0.0.1",
+ "private": true,
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "dependencies": {
+ "@microsoft/sp-client-base": "~0.3.0",
+ "@microsoft/sp-client-preview": "~0.4.0",
+ "office-ui-fabric-react": "0.36.0",
+ "react": "0.14.8",
+ "react-dom": "0.14.8"
+ },
+ "devDependencies": {
+ "@microsoft/sp-build-web": "~0.6.0",
+ "@microsoft/sp-module-interfaces": "~0.3.0",
+ "@microsoft/sp-webpart-workbench": "~0.4.0",
+ "gulp": "~3.9.1"
+ },
+ "scripts": {
+ "build": "gulp bundle",
+ "clean": "gulp nuke",
+ "test": "gulp test"
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/react-sp-elevatedprivileges.njsproj b/samples/react-sp-elevatedprivileges/webpart/react-sp-elevatedprivileges.njsproj
new file mode 100644
index 000000000..c77e3da6f
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/react-sp-elevatedprivileges.njsproj
@@ -0,0 +1,86 @@
+
+
+
+ Debug
+ 2.0
+ {dbc8827c-729c-4e8f-9ecc-f0d57f279ba2}
+
+ ProjectFiles
+ node_modules\gulp\bin\gulp.js
+ .
+ .
+ {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}
+ true
+ CommonJS
+ false
+ 11.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ serve
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+ /
+ http://localhost:48022/
+ False
+ True
+ http://localhost:1337
+ False
+
+
+
+
+
+
+ CurrentPage
+ True
+ False
+ False
+ False
+
+
+
+
+
+
+
+
+ False
+ False
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/tests.js b/samples/react-sp-elevatedprivileges/webpart/src/tests.js
new file mode 100644
index 000000000..cb4bb5cf2
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/tests.js
@@ -0,0 +1,5 @@
+var context = require.context('.', true, /.+\.test\.js?$/);
+
+context.keys().forEach(context);
+
+module.exports = context;
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTask.module.scss b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTask.module.scss
new file mode 100644
index 000000000..2843d9409
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTask.module.scss
@@ -0,0 +1,21 @@
+.createTask {
+ .container {
+ max-width: 700px;
+ margin: 0px auto;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+ }
+
+ .row {
+ padding: 20px;
+ }
+
+ .listItem {
+ max-width: 715px;
+ margin: 5px auto 5px auto;
+ box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
+ }
+
+ .button {
+ text-decoration: none;
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.manifest.json b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.manifest.json
new file mode 100644
index 000000000..5972ec4f7
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.manifest.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
+
+ "id": "744bc357-0a6a-4f97-b4ef-312bb8c378d9",
+ "componentType": "WebPart",
+ "version": "0.0.1",
+ "manifestVersion": 2,
+
+ "preconfiguredEntries": [{
+ "groupId": "744bc357-0a6a-4f97-b4ef-312bb8c378d9",
+ "group": { "default": "Under Development" },
+ "title": { "default": "Create task" },
+ "description": { "default": "Creates task using remote API with elevated privileges" },
+ "officeFabricIconFontName": "Page",
+ "properties": {
+ }
+ }]
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.ts b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.ts
new file mode 100644
index 000000000..1f2d72e7f
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/CreateTaskWebPart.ts
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import * as ReactDom from 'react-dom';
+import {
+ BaseClientSideWebPart,
+ IWebPartContext
+} from '@microsoft/sp-client-preview';
+
+import CreateTask, { ICreateTaskProps } from './components/CreateTask';
+import { ICreateTaskWebPartProps } from './ICreateTaskWebPartProps';
+
+export default class CreateTaskWebPart extends BaseClientSideWebPart {
+
+ public constructor(context: IWebPartContext) {
+ super(context);
+ }
+
+ public render(): void {
+ const element: React.ReactElement = React.createElement(CreateTask, {});
+
+ ReactDom.render(element, this.domElement);
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/ICreateTaskWebPartProps.ts b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/ICreateTaskWebPartProps.ts
new file mode 100644
index 000000000..033eddedf
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/ICreateTaskWebPartProps.ts
@@ -0,0 +1,2 @@
+export interface ICreateTaskWebPartProps {
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/components/CreateTask.tsx b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/components/CreateTask.tsx
new file mode 100644
index 000000000..99c963030
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/components/CreateTask.tsx
@@ -0,0 +1,136 @@
+import * as React from 'react';
+import { css, Button, Spinner, TextField } from 'office-ui-fabric-react';
+import { HttpClient } from '@microsoft/sp-client-base';
+
+import styles from '../CreateTask.module.scss';
+import { ICreateTaskWebPartProps } from '../ICreateTaskWebPartProps';
+
+export interface ICreateTaskProps extends ICreateTaskWebPartProps {
+}
+
+export interface ICreateTaskState {
+ itemTitleInvalid: boolean;
+ creatingItem: boolean;
+ itemTitle: string;
+ status: string;
+ itemTitleChanged: boolean;
+}
+
+export default class CreateTask extends React.Component {
+ constructor(props: ICreateTaskProps, state: ICreateTaskState) {
+ super(props);
+
+ this.state = {
+ creatingItem: false,
+ itemTitle: '',
+ itemTitleInvalid: true,
+ status: '',
+ itemTitleChanged: false
+ };
+
+ if (!String.prototype.trim) {
+ (() => {
+ // Make sure we trim BOM and NBSP
+ var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+ String.prototype.trim = function () {
+ return this.replace(rtrim, '');
+ };
+ })();
+ }
+ }
+
+ public render(): JSX.Element {
+ const creatingItem: JSX.Element = this.state.creatingItem ?
: ;
+ const status: JSX.Element = this.state.status.length > 0 ? {this.state.status}
: ;
+
+ return (
+
+
+
+
+
+ Create list item
+
+
{ this.itemTitleChanged(newValue); }}
+ onGetErrorMessage={(): string => { return this.validateItemTitle(); } }
+ value={this.state.itemTitle} />
+
+ {creatingItem}
+ {status}
+
+
+
+
+ );
+ }
+
+ private createItem(): void {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.creatingItem = true;
+ prevState.status = '';
+ return prevState;
+ });
+
+ window.fetch('https://pnp-spfx-elevation-api.azurewebsites.net/api/items', {
+ method: 'POST',
+ body: JSON.stringify({ title: this.state.itemTitle }),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then((response: Response): void => {
+ if (response.ok) {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.creatingItem = false;
+ prevState.status = 'Item successfully created';
+ return prevState;
+ });
+ }
+ else {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.creatingItem = false;
+ prevState.status = 'Error creating item: ' + response.statusText;
+ return prevState;
+ });
+ }
+ }, (error: any): void => {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.creatingItem = false;
+ prevState.status = 'Error creating item: ' + error;
+ return prevState;
+ });
+ });
+ }
+
+ private itemTitleChanged(newValue: string): void {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.itemTitleChanged = true;
+ prevState.status = '';
+ prevState.itemTitle = newValue;
+ return prevState;
+ });
+ }
+
+ private validateItemTitle(): string {
+ if (!this.state.itemTitleChanged) {
+ return '';
+ }
+
+ if (this.state.itemTitle.trim().length === 0) {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.itemTitleInvalid = true;
+ return prevState;
+ });
+ return 'Please enter item title';
+ }
+ else {
+ this.setState((prevState: ICreateTaskState, props: ICreateTaskProps): ICreateTaskState => {
+ prevState.itemTitleInvalid = false;
+ return prevState;
+ });
+ return '';
+ }
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/en-us.js b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/en-us.js
new file mode 100644
index 000000000..89f98bc1e
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/en-us.js
@@ -0,0 +1,7 @@
+define([], function() {
+ return {
+ "PropertyPaneDescription": "Description",
+ "BasicGroupName": "Group Name",
+ "DescriptionFieldLabel": "Description Field"
+ }
+});
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/mystrings.d.ts b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/mystrings.d.ts
new file mode 100644
index 000000000..393cbb879
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/loc/mystrings.d.ts
@@ -0,0 +1,10 @@
+declare interface ICreateTaskStrings {
+ PropertyPaneDescription: string;
+ BasicGroupName: string;
+ DescriptionFieldLabel: string;
+}
+
+declare module 'createTaskStrings' {
+ const strings: ICreateTaskStrings;
+ export = strings;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/tests/CreateTask.test.ts b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/tests/CreateTask.test.ts
new file mode 100644
index 000000000..ea0e70735
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/src/webparts/createTask/tests/CreateTask.test.ts
@@ -0,0 +1,7 @@
+import * as assert from 'assert';
+
+describe('CreateTaskWebPart', () => {
+ it('should do something', () => {
+ assert.ok(true);
+ });
+});
diff --git a/samples/react-sp-elevatedprivileges/webpart/tsconfig.json b/samples/react-sp-elevatedprivileges/webpart/tsconfig.json
new file mode 100644
index 000000000..98c8662a9
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "commonjs",
+ "jsx": "react",
+ "declaration": true,
+ "sourceMap": true
+ }
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp-webpack.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp-webpack.d.ts
new file mode 100644
index 000000000..f2b3b03df
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp-webpack.d.ts
@@ -0,0 +1,13 @@
+// Type definitions for webpack in Microsoft ODSP projects
+// Project: ODSP-WEBPACK
+
+/*
+ * This definition of webpack require overrides all other definitions of require in our toolchain
+ * Make sure all other definitions of require are commented out e.g. in node.d.ts
+ */
+declare var require: {
+ (path: string): any;
+ (paths: string[], callback: (...modules: any[]) => void): void;
+ resolve: (id: string) => string;
+ ensure: (paths: string[], callback: (require: (path: string) => T) => void, path: string) => void;
+};
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp.d.ts
new file mode 100644
index 000000000..ae3334fe0
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/@ms/odsp.d.ts
@@ -0,0 +1,10 @@
+// Type definitions for Microsoft ODSP projects
+// Project: ODSP
+
+///
+
+/* Global definition for DEBUG builds */
+declare const DEBUG: boolean;
+
+/* Global definition for UNIT_TEST builds */
+declare const UNIT_TEST: boolean;
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/assertion-error/assertion-error.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/assertion-error/assertion-error.d.ts
new file mode 100644
index 000000000..08217c9e5
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/assertion-error/assertion-error.d.ts
@@ -0,0 +1,15 @@
+// Type definitions for assertion-error 1.0.0
+// Project: https://github.com/chaijs/assertion-error
+// Definitions by: Bart van der Schoor
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare module 'assertion-error' {
+ class AssertionError implements Error {
+ constructor(message: string, props?: any, ssf?: Function);
+ name: string;
+ message: string;
+ showDiff: boolean;
+ stack: string;
+ }
+ export = AssertionError;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/chai/chai.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/chai/chai.d.ts
new file mode 100644
index 000000000..da4d718e1
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/chai/chai.d.ts
@@ -0,0 +1,388 @@
+// Type definitions for chai 3.2.0
+// Project: http://chaijs.com/
+// Definitions by: Jed Mao ,
+// Bart van der Schoor ,
+// Andrew Brown ,
+// Olivier Chevet
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+//
+
+declare module Chai {
+
+ interface ChaiStatic {
+ expect: ExpectStatic;
+ should(): Should;
+ /**
+ * Provides a way to extend the internals of Chai
+ */
+ use(fn: (chai: any, utils: any) => void): any;
+ assert: AssertStatic;
+ config: Config;
+ AssertionError: AssertionError;
+ }
+
+ export interface ExpectStatic extends AssertionStatic {
+ fail(actual?: any, expected?: any, message?: string, operator?: string): void;
+ }
+
+ export interface AssertStatic extends Assert {
+ }
+
+ export interface AssertionStatic {
+ (target: any, message?: string): Assertion;
+ }
+
+ interface ShouldAssertion {
+ equal(value1: any, value2: any, message?: string): void;
+ Throw: ShouldThrow;
+ throw: ShouldThrow;
+ exist(value: any, message?: string): void;
+ }
+
+ interface Should extends ShouldAssertion {
+ not: ShouldAssertion;
+ fail(actual: any, expected: any, message?: string, operator?: string): void;
+ }
+
+ interface ShouldThrow {
+ (actual: Function): void;
+ (actual: Function, expected: string|RegExp, message?: string): void;
+ (actual: Function, constructor: Error|Function, expected?: string|RegExp, message?: string): void;
+ }
+
+ interface Assertion extends LanguageChains, NumericComparison, TypeComparison {
+ not: Assertion;
+ deep: Deep;
+ any: KeyFilter;
+ all: KeyFilter;
+ a: TypeComparison;
+ an: TypeComparison;
+ include: Include;
+ includes: Include;
+ contain: Include;
+ contains: Include;
+ ok: Assertion;
+ true: Assertion;
+ false: Assertion;
+ null: Assertion;
+ undefined: Assertion;
+ NaN: Assertion;
+ exist: Assertion;
+ empty: Assertion;
+ arguments: Assertion;
+ Arguments: Assertion;
+ equal: Equal;
+ equals: Equal;
+ eq: Equal;
+ eql: Equal;
+ eqls: Equal;
+ property: Property;
+ ownProperty: OwnProperty;
+ haveOwnProperty: OwnProperty;
+ ownPropertyDescriptor: OwnPropertyDescriptor;
+ haveOwnPropertyDescriptor: OwnPropertyDescriptor;
+ length: Length;
+ lengthOf: Length;
+ match: Match;
+ matches: Match;
+ string(string: string, message?: string): Assertion;
+ keys: Keys;
+ key(string: string): Assertion;
+ throw: Throw;
+ throws: Throw;
+ Throw: Throw;
+ respondTo: RespondTo;
+ respondsTo: RespondTo;
+ itself: Assertion;
+ satisfy: Satisfy;
+ satisfies: Satisfy;
+ closeTo(expected: number, delta: number, message?: string): Assertion;
+ members: Members;
+ increase: PropertyChange;
+ increases: PropertyChange;
+ decrease: PropertyChange;
+ decreases: PropertyChange;
+ change: PropertyChange;
+ changes: PropertyChange;
+ extensible: Assertion;
+ sealed: Assertion;
+ frozen: Assertion;
+
+ }
+
+ interface LanguageChains {
+ to: Assertion;
+ be: Assertion;
+ been: Assertion;
+ is: Assertion;
+ that: Assertion;
+ which: Assertion;
+ and: Assertion;
+ has: Assertion;
+ have: Assertion;
+ with: Assertion;
+ at: Assertion;
+ of: Assertion;
+ same: Assertion;
+ }
+
+ interface NumericComparison {
+ above: NumberComparer;
+ gt: NumberComparer;
+ greaterThan: NumberComparer;
+ least: NumberComparer;
+ gte: NumberComparer;
+ below: NumberComparer;
+ lt: NumberComparer;
+ lessThan: NumberComparer;
+ most: NumberComparer;
+ lte: NumberComparer;
+ within(start: number, finish: number, message?: string): Assertion;
+ }
+
+ interface NumberComparer {
+ (value: number, message?: string): Assertion;
+ }
+
+ interface TypeComparison {
+ (type: string, message?: string): Assertion;
+ instanceof: InstanceOf;
+ instanceOf: InstanceOf;
+ }
+
+ interface InstanceOf {
+ (constructor: Object, message?: string): Assertion;
+ }
+
+ interface Deep {
+ equal: Equal;
+ include: Include;
+ property: Property;
+ members: Members;
+ }
+
+ interface KeyFilter {
+ keys: Keys;
+ }
+
+ interface Equal {
+ (value: any, message?: string): Assertion;
+ }
+
+ interface Property {
+ (name: string, value?: any, message?: string): Assertion;
+ }
+
+ interface OwnProperty {
+ (name: string, message?: string): Assertion;
+ }
+
+ interface OwnPropertyDescriptor {
+ (name: string, descriptor: PropertyDescriptor, message?: string): Assertion;
+ (name: string, message?: string): Assertion;
+ }
+
+ interface Length extends LanguageChains, NumericComparison {
+ (length: number, message?: string): Assertion;
+ }
+
+ interface Include {
+ (value: Object, message?: string): Assertion;
+ (value: string, message?: string): Assertion;
+ (value: number, message?: string): Assertion;
+ keys: Keys;
+ members: Members;
+ any: KeyFilter;
+ all: KeyFilter;
+ }
+
+ interface Match {
+ (regexp: RegExp|string, message?: string): Assertion;
+ }
+
+ interface Keys {
+ (...keys: string[]): Assertion;
+ (keys: any[]): Assertion;
+ (keys: Object): Assertion;
+ }
+
+ interface Throw {
+ (): Assertion;
+ (expected: string, message?: string): Assertion;
+ (expected: RegExp, message?: string): Assertion;
+ (constructor: Error, expected?: string, message?: string): Assertion;
+ (constructor: Error, expected?: RegExp, message?: string): Assertion;
+ (constructor: Function, expected?: string, message?: string): Assertion;
+ (constructor: Function, expected?: RegExp, message?: string): Assertion;
+ }
+
+ interface RespondTo {
+ (method: string, message?: string): Assertion;
+ }
+
+ interface Satisfy {
+ (matcher: Function, message?: string): Assertion;
+ }
+
+ interface Members {
+ (set: any[], message?: string): Assertion;
+ }
+
+ interface PropertyChange {
+ (object: Object, prop: string, msg?: string): Assertion;
+ }
+
+ export interface Assert {
+ /**
+ * @param expression Expression to test for truthiness.
+ * @param message Message to display on error.
+ */
+ (expression: any, message?: string): void;
+
+ fail(actual?: any, expected?: any, msg?: string, operator?: string): void;
+
+ ok(val: any, msg?: string): void;
+ isOk(val: any, msg?: string): void;
+ notOk(val: any, msg?: string): void;
+ isNotOk(val: any, msg?: string): void;
+
+ equal(act: any, exp: any, msg?: string): void;
+ notEqual(act: any, exp: any, msg?: string): void;
+
+ strictEqual(act: any, exp: any, msg?: string): void;
+ notStrictEqual(act: any, exp: any, msg?: string): void;
+
+ deepEqual(act: any, exp: any, msg?: string): void;
+ notDeepEqual(act: any, exp: any, msg?: string): void;
+
+ isTrue(val: any, msg?: string): void;
+ isFalse(val: any, msg?: string): void;
+
+ isNull(val: any, msg?: string): void;
+ isNotNull(val: any, msg?: string): void;
+
+ isUndefined(val: any, msg?: string): void;
+ isDefined(val: any, msg?: string): void;
+
+ isNaN(val: any, msg?: string): void;
+ isNotNaN(val: any, msg?: string): void;
+
+ isAbove(val: number, abv: number, msg?: string): void;
+ isBelow(val: number, blw: number, msg?: string): void;
+
+ isFunction(val: any, msg?: string): void;
+ isNotFunction(val: any, msg?: string): void;
+
+ isObject(val: any, msg?: string): void;
+ isNotObject(val: any, msg?: string): void;
+
+ isArray(val: any, msg?: string): void;
+ isNotArray(val: any, msg?: string): void;
+
+ isString(val: any, msg?: string): void;
+ isNotString(val: any, msg?: string): void;
+
+ isNumber(val: any, msg?: string): void;
+ isNotNumber(val: any, msg?: string): void;
+
+ isBoolean(val: any, msg?: string): void;
+ isNotBoolean(val: any, msg?: string): void;
+
+ typeOf(val: any, type: string, msg?: string): void;
+ notTypeOf(val: any, type: string, msg?: string): void;
+
+ instanceOf(val: any, type: Function, msg?: string): void;
+ notInstanceOf(val: any, type: Function, msg?: string): void;
+
+ include(exp: string, inc: any, msg?: string): void;
+ include(exp: any[], inc: any, msg?: string): void;
+
+ notInclude(exp: string, inc: any, msg?: string): void;
+ notInclude(exp: any[], inc: any, msg?: string): void;
+
+ match(exp: any, re: RegExp, msg?: string): void;
+ notMatch(exp: any, re: RegExp, msg?: string): void;
+
+ property(obj: Object, prop: string, msg?: string): void;
+ notProperty(obj: Object, prop: string, msg?: string): void;
+ deepProperty(obj: Object, prop: string, msg?: string): void;
+ notDeepProperty(obj: Object, prop: string, msg?: string): void;
+
+ propertyVal(obj: Object, prop: string, val: any, msg?: string): void;
+ propertyNotVal(obj: Object, prop: string, val: any, msg?: string): void;
+
+ deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): void;
+ deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): void;
+
+ lengthOf(exp: any, len: number, msg?: string): void;
+ //alias frenzy
+ throw(fn: Function, msg?: string): void;
+ throw(fn: Function, regExp: RegExp): void;
+ throw(fn: Function, errType: Function, msg?: string): void;
+ throw(fn: Function, errType: Function, regExp: RegExp): void;
+
+ throws(fn: Function, msg?: string): void;
+ throws(fn: Function, regExp: RegExp): void;
+ throws(fn: Function, errType: Function, msg?: string): void;
+ throws(fn: Function, errType: Function, regExp: RegExp): void;
+
+ Throw(fn: Function, msg?: string): void;
+ Throw(fn: Function, regExp: RegExp): void;
+ Throw(fn: Function, errType: Function, msg?: string): void;
+ Throw(fn: Function, errType: Function, regExp: RegExp): void;
+
+ doesNotThrow(fn: Function, msg?: string): void;
+ doesNotThrow(fn: Function, regExp: RegExp): void;
+ doesNotThrow(fn: Function, errType: Function, msg?: string): void;
+ doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void;
+
+ operator(val: any, operator: string, val2: any, msg?: string): void;
+ closeTo(act: number, exp: number, delta: number, msg?: string): void;
+
+ sameMembers(set1: any[], set2: any[], msg?: string): void;
+ sameDeepMembers(set1: any[], set2: any[], msg?: string): void;
+ includeMembers(superset: any[], subset: any[], msg?: string): void;
+
+ ifError(val: any, msg?: string): void;
+
+ isExtensible(obj: {}, msg?: string): void;
+ extensible(obj: {}, msg?: string): void;
+ isNotExtensible(obj: {}, msg?: string): void;
+ notExtensible(obj: {}, msg?: string): void;
+
+ isSealed(obj: {}, msg?: string): void;
+ sealed(obj: {}, msg?: string): void;
+ isNotSealed(obj: {}, msg?: string): void;
+ notSealed(obj: {}, msg?: string): void;
+
+ isFrozen(obj: Object, msg?: string): void;
+ frozen(obj: Object, msg?: string): void;
+ isNotFrozen(obj: Object, msg?: string): void;
+ notFrozen(obj: Object, msg?: string): void;
+
+
+ }
+
+ export interface Config {
+ includeStack: boolean;
+ }
+
+ export class AssertionError {
+ constructor(message: string, _props?: any, ssf?: Function);
+ name: string;
+ message: string;
+ showDiff: boolean;
+ stack: string;
+ }
+}
+
+declare var chai: Chai.ChaiStatic;
+
+declare module "chai" {
+ export = chai;
+}
+
+interface Object {
+ should: Chai.Assertion;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/combokeys/combokeys.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/combokeys/combokeys.d.ts
new file mode 100644
index 000000000..f7e1e5b03
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/combokeys/combokeys.d.ts
@@ -0,0 +1,107 @@
+// Type definitions for Combokeys v2.4.6
+// Project: https://github.com/PolicyStat/combokeys
+// Definitions by: Ian Clanton-Thuon
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+declare namespace Combokeys {
+ interface CombokeysStatic {
+ new (element: Element): Combokeys;
+
+ /**
+ * all instances of Combokeys
+ */
+ instances: Combokeys[];
+
+ /**
+ * reset all instances
+ */
+ reset(): void;
+ }
+
+ interface Combokeys {
+ element: Element;
+
+ /**
+ * binds an event to Combokeys
+ *
+ * can be a single key, a combination of keys separated with +,
+ * an array of keys, or a sequence of keys separated by spaces
+ *
+ * be sure to list the modifier keys first to make sure that the
+ * correct key ends up getting bound (the last key in the pattern)
+ *
+ * @param {keys} key combination or combinations
+ * @param {callback} callback function
+ * @param {handler} optional - one of "keypress", "keydown", or "keyup"
+ * @returns void
+ */
+ bind(keys: string | string[], callback: () => void, action?: string): void;
+
+
+ /**
+ * binds multiple combinations to the same callback
+ *
+ * @param {keys} key combinations
+ * @param {callback} callback function
+ * @param {handler} optional - one of "keypress", "keydown", or "keyup"
+ * @returns void
+ */
+ bindMultiple(keys: string[], callback: () => void, action?: string): void;
+
+ /**
+ * unbinds an event to Combokeys
+ *
+ * the unbinding sets the callback function of the specified key combo
+ * to an empty function and deletes the corresponding key in the
+ * directMap dict.
+ *
+ * the keycombo+action has to be exactly the same as
+ * it was defined in the bind method
+ *
+ * @param {keys} key combination or combinations
+ * @param {action} optional - one of "keypress", "keydown", or "keyup"
+ * @returns void
+ */
+ unbind(keys: string | string[], action?: string): void;
+
+ /**
+ * triggers an event that has already been bound
+ *
+ * @param {keys} key combination
+ * @param {action} optional - one of "keypress", "keydown", or "keyup"
+ * @returns void
+ */
+ trigger(keys: string, action?: string): void;
+
+ /**
+ * resets the library back to its initial state. This is useful
+ * if you want to clear out the current keyboard shortcuts and bind
+ * new ones - for example if you switch to another page
+ *
+ * @returns void
+ */
+ reset(): void;
+
+ /**
+ * should we stop this event before firing off callbacks
+ *
+ * @param {e} event
+ * @param {element} bound element
+ * @return {boolean}
+ */
+ stopCallback(e: Event, element: Element): boolean;
+
+ /**
+ * detach all listners from the bound element
+ *
+ * @return {void}
+ */
+ detach(): void;
+ }
+}
+
+declare var combokeys: Combokeys.CombokeysStatic;
+
+declare module "combokeys" {
+ export = combokeys;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/es6-collections/es6-collections.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/es6-collections/es6-collections.d.ts
new file mode 100644
index 000000000..bc39df295
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/es6-collections/es6-collections.d.ts
@@ -0,0 +1,113 @@
+// Type definitions for es6-collections v0.5.1
+// Project: https://github.com/WebReflection/es6-collections/
+// Definitions by: Ron Buckton
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+/* *****************************************************************************
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+
+THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+
+See the Apache Version 2.0 License for specific language governing permissions
+and limitations under the License.
+***************************************************************************** */
+
+interface IteratorResult {
+ done: boolean;
+ value?: T;
+}
+
+interface Iterator {
+ next(value?: any): IteratorResult;
+ return?(value?: any): IteratorResult;
+ throw?(e?: any): IteratorResult;
+}
+
+interface ForEachable {
+ forEach(callbackfn: (value: T) => void): void;
+}
+
+interface Map {
+ clear(): void;
+ delete(key: K): boolean;
+ forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void;
+ get(key: K): V;
+ has(key: K): boolean;
+ set(key: K, value?: V): Map;
+ entries(): Iterator<[K, V]>;
+ keys(): Iterator;
+ values(): Iterator;
+ size: number;
+}
+
+interface MapConstructor {
+ new (): Map;
+ new (iterable: ForEachable<[K, V]>): Map;
+ prototype: Map;
+}
+
+declare var Map: MapConstructor;
+
+interface Set {
+ add(value: T): Set;
+ clear(): void;
+ delete(value: T): boolean;
+ forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void;
+ has(value: T): boolean;
+ entries(): Iterator<[T, T]>;
+ keys(): Iterator;
+ values(): Iterator;
+ size: number;
+}
+
+interface SetConstructor {
+ new (): Set;
+ new (iterable: ForEachable): Set;
+ prototype: Set;
+}
+
+declare var Set: SetConstructor;
+
+interface WeakMap {
+ delete(key: K): boolean;
+ clear(): void;
+ get(key: K): V;
+ has(key: K): boolean;
+ set(key: K, value?: V): WeakMap;
+}
+
+interface WeakMapConstructor {
+ new (): WeakMap;
+ new (iterable: ForEachable<[K, V]>): WeakMap;
+ prototype: WeakMap;
+}
+
+declare var WeakMap: WeakMapConstructor;
+
+interface WeakSet {
+ delete(value: T): boolean;
+ clear(): void;
+ add(value: T): WeakSet;
+ has(value: T): boolean;
+}
+
+interface WeakSetConstructor {
+ new (): WeakSet;
+ new (iterable: ForEachable): WeakSet;
+ prototype: WeakSet;
+}
+
+declare var WeakSet: WeakSetConstructor;
+
+declare module "es6-collections" {
+ var Map: MapConstructor;
+ var Set: SetConstructor;
+ var WeakMap: WeakMapConstructor;
+ var WeakSet: WeakSetConstructor;
+}
\ No newline at end of file
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/es6-promise/es6-promise.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/es6-promise/es6-promise.d.ts
new file mode 100644
index 000000000..a8f8d7845
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/es6-promise/es6-promise.d.ts
@@ -0,0 +1,74 @@
+// Type definitions for es6-promise
+// Project: https://github.com/jakearchibald/ES6-Promise
+// Definitions by: François de Campredon , vvakame
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+interface Thenable {
+ then(onFulfilled?: (value: R) => U | Thenable, onRejected?: (error: any) => U | Thenable): Thenable;
+ then(onFulfilled?: (value: R) => U | Thenable, onRejected?: (error: any) => void): Thenable;
+ catch(onRejected?: (error: any) => U | Thenable): Thenable;
+}
+
+declare class Promise implements Thenable {
+ /**
+ * If you call resolve in the body of the callback passed to the constructor,
+ * your promise is fulfilled with result object passed to resolve.
+ * If you call reject your promise is rejected with the object passed to reject.
+ * For consistency and debugging (eg stack traces), obj should be an instanceof Error.
+ * Any errors thrown in the constructor callback will be implicitly passed to reject().
+ */
+ constructor(callback: (resolve : (value?: R | Thenable) => void, reject: (error?: any) => void) => void);
+
+ /**
+ * onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects.
+ * Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called.
+ * Both callbacks have a single parameter , the fulfillment value or rejection reason.
+ * "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve.
+ * If an error is thrown in the callback, the returned promise rejects with that error.
+ *
+ * @param onFulfilled called when/if "promise" resolves
+ * @param onRejected called when/if "promise" rejects
+ */
+ then(onFulfilled?: (value: R) => U | Thenable, onRejected?: (error: any) => U | Thenable): Promise;
+ then(onFulfilled?: (value: R) => U | Thenable, onRejected?: (error: any) => void): Promise;
+
+ /**
+ * Sugar for promise.then(undefined, onRejected)
+ *
+ * @param onRejected called when/if "promise" rejects
+ */
+ catch(onRejected?: (error: any) => U | Thenable): Promise;
+}
+
+declare module Promise {
+ /**
+ * Make a new promise from the thenable.
+ * A thenable is promise-like in as far as it has a "then" method.
+ */
+ function resolve(value?: R | Thenable): Promise;
+
+ /**
+ * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
+ */
+ function reject(error: any): Promise;
+
+ /**
+ * Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects.
+ * the array passed to all can be a mixture of promise-like objects and other objects.
+ * The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
+ */
+ function all(promises: (R | Thenable)[]): Promise;
+
+ /**
+ * Make a Promise that fulfills when any item fulfills, and rejects if any item rejects.
+ */
+ function race(promises: (R | Thenable)[]): Promise;
+}
+
+declare module 'es6-promise' {
+ var foo: typeof Promise; // Temp variable to reference Promise in local context
+ module rsvp {
+ export var Promise: typeof foo;
+ }
+ export = rsvp;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/knockout/knockout.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/knockout/knockout.d.ts
new file mode 100644
index 000000000..267f3174c
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/knockout/knockout.d.ts
@@ -0,0 +1,631 @@
+// Type definitions for Knockout v3.2.0
+// Project: http://knockoutjs.com
+// Definitions by: Boris Yankov , Igor Oleinikov , Clément Bourgeois
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+
+interface KnockoutSubscribableFunctions {
+ [key: string]: KnockoutBindingHandler;
+
+ notifySubscribers(valueToWrite?: T, event?: string): void;
+}
+
+interface KnockoutComputedFunctions {
+ [key: string]: KnockoutBindingHandler;
+}
+
+interface KnockoutObservableFunctions {
+ [key: string]: KnockoutBindingHandler;
+
+ equalityComparer(a: any, b: any): boolean;
+}
+
+interface KnockoutObservableArrayFunctions {
+ // General Array functions
+ indexOf(searchElement: T, fromIndex?: number): number;
+ slice(start: number, end?: number): T[];
+ splice(start: number): T[];
+ splice(start: number, deleteCount: number, ...items: T[]): T[];
+ pop(): T;
+ push(...items: T[]): void;
+ shift(): T;
+ unshift(...items: T[]): number;
+ reverse(): KnockoutObservableArray;
+ sort(): KnockoutObservableArray;
+ sort(compareFunction: (left: T, right: T) => number): KnockoutObservableArray;
+
+ // Ko specific
+ [key: string]: KnockoutBindingHandler;
+
+ replace(oldItem: T, newItem: T): void;
+
+ remove(item: T): T[];
+ remove(removeFunction: (item: T) => boolean): T[];
+ removeAll(items: T[]): T[];
+ removeAll(): T[];
+
+ destroy(item: T): void;
+ destroy(destroyFunction: (item: T) => boolean): void;
+ destroyAll(items: T[]): void;
+ destroyAll(): void;
+}
+
+interface KnockoutSubscribableStatic {
+ fn: KnockoutSubscribableFunctions;
+
+ new (): KnockoutSubscribable;
+}
+
+interface KnockoutSubscription {
+ dispose(): void;
+}
+
+interface KnockoutSubscribable extends KnockoutSubscribableFunctions {
+ subscribe(callback: (newValue: T) => void, target?: any, event?: string): KnockoutSubscription;
+ subscribe(callback: (newValue: TEvent) => void, target: any, event: string): KnockoutSubscription;
+ extend(requestedExtenders: { [key: string]: any; }): KnockoutSubscribable;
+ getSubscriptionsCount(): number;
+}
+
+interface KnockoutComputedStatic {
+ fn: KnockoutComputedFunctions;
+
+ (): KnockoutComputed;
+ (func: () => T, context?: any, options?: any): KnockoutComputed;
+ (def: KnockoutComputedDefine, context?: any): KnockoutComputed;
+}
+
+interface KnockoutComputed extends KnockoutObservable, KnockoutComputedFunctions {
+ fn: KnockoutComputedFunctions;
+
+ dispose(): void;
+ isActive(): boolean;
+ getDependenciesCount(): number;
+ extend(requestedExtenders: { [key: string]: any; }): KnockoutComputed;
+}
+
+interface KnockoutObservableArrayStatic {
+ fn: KnockoutObservableArrayFunctions;
+
+ (value?: T[]): KnockoutObservableArray;
+}
+
+interface KnockoutObservableArray extends KnockoutObservable, KnockoutObservableArrayFunctions {
+ extend(requestedExtenders: { [key: string]: any; }): KnockoutObservableArray;
+}
+
+interface KnockoutObservableStatic {
+ fn: KnockoutObservableFunctions;
+
+ (value?: T): KnockoutObservable;
+}
+
+interface KnockoutObservable extends KnockoutSubscribable, KnockoutObservableFunctions {
+ (): T;
+ (value: T): void;
+
+ peek(): T;
+ valueHasMutated?:{(): void;};
+ valueWillMutate?:{(): void;};
+ extend(requestedExtenders: { [key: string]: any; }): KnockoutObservable;
+}
+
+interface KnockoutComputedDefine {
+ read(): T;
+ write? (value: T): void;
+ disposeWhenNodeIsRemoved?: Node;
+ disposeWhen? (): boolean;
+ owner?: any;
+ deferEvaluation?: boolean;
+ pure?: boolean;
+}
+
+interface KnockoutBindingContext {
+ $parent: any;
+ $parents: any[];
+ $root: any;
+ $data: any;
+ $rawData: any | KnockoutObservable;
+ $index?: KnockoutObservable;
+ $parentContext?: KnockoutBindingContext;
+ $component: any;
+ $componentTemplateNodes: Node[];
+
+ extend(properties: any): any;
+ createChildContext(dataItemOrAccessor: any, dataItemAlias?: any, extendCallback?: Function): any;
+}
+
+interface KnockoutAllBindingsAccessor {
+ (): any;
+ get(name: string): any;
+ has(name: string): boolean;
+}
+
+interface KnockoutBindingHandler {
+ after?: Array;
+ init?: (element: any, valueAccessor: () => any, allBindingsAccessor?: KnockoutAllBindingsAccessor, viewModel?: any, bindingContext?: KnockoutBindingContext) => void | { controlsDescendantBindings: boolean; };
+ update?: (element: any, valueAccessor: () => any, allBindingsAccessor?: KnockoutAllBindingsAccessor, viewModel?: any, bindingContext?: KnockoutBindingContext) => void;
+ options?: any;
+ preprocess?: (value: string, name: string, addBindingCallback?: (name: string, value: string) => void) => string;
+}
+
+interface KnockoutBindingHandlers {
+ [bindingHandler: string]: KnockoutBindingHandler;
+
+ // Controlling text and appearance
+ visible: KnockoutBindingHandler;
+ text: KnockoutBindingHandler;
+ html: KnockoutBindingHandler;
+ css: KnockoutBindingHandler;
+ style: KnockoutBindingHandler;
+ attr: KnockoutBindingHandler;
+
+ // Control Flow
+ foreach: KnockoutBindingHandler;
+ if: KnockoutBindingHandler;
+ ifnot: KnockoutBindingHandler;
+ with: KnockoutBindingHandler;
+
+ // Working with form fields
+ click: KnockoutBindingHandler;
+ event: KnockoutBindingHandler;
+ submit: KnockoutBindingHandler;
+ enable: KnockoutBindingHandler;
+ disable: KnockoutBindingHandler;
+ value: KnockoutBindingHandler;
+ textInput: KnockoutBindingHandler;
+ hasfocus: KnockoutBindingHandler;
+ checked: KnockoutBindingHandler;
+ options: KnockoutBindingHandler;
+ selectedOptions: KnockoutBindingHandler;
+ uniqueName: KnockoutBindingHandler;
+
+ // Rendering templates
+ template: KnockoutBindingHandler;
+
+ // Components (new for v3.2)
+ component: KnockoutBindingHandler;
+}
+
+interface KnockoutMemoization {
+ memoize(callback: () => string): string;
+ unmemoize(memoId: string, callbackParams: any[]): boolean;
+ unmemoizeDomNodeAndDescendants(domNode: any, extraCallbackParamsArray: any[]): boolean;
+ parseMemoText(memoText: string): string;
+}
+
+interface KnockoutVirtualElement {}
+
+interface KnockoutVirtualElements {
+ allowedBindings: { [bindingName: string]: boolean; };
+ emptyNode(node: KnockoutVirtualElement ): void;
+ firstChild(node: KnockoutVirtualElement ): KnockoutVirtualElement;
+ insertAfter( container: KnockoutVirtualElement, nodeToInsert: Node, insertAfter: Node ): void;
+ nextSibling(node: KnockoutVirtualElement): Node;
+ prepend(node: KnockoutVirtualElement, toInsert: Node ): void;
+ setDomNodeChildren(node: KnockoutVirtualElement, newChildren: { length: number;[index: number]: Node; } ): void;
+ childNodes(node: KnockoutVirtualElement ): Node[];
+}
+
+interface KnockoutExtenders {
+ throttle(target: any, timeout: number): KnockoutComputed;
+ notify(target: any, notifyWhen: string): any;
+
+ rateLimit(target: any, timeout: number): any;
+ rateLimit(target: any, options: { timeout: number; method?: string; }): any;
+
+ trackArrayChanges(target: any): any;
+}
+
+//
+// NOTE TO MAINTAINERS AND CONTRIBUTORS : pay attention to only include symbols that are
+// publicly exported in the minified version of ko, without that you can give the false
+// impression that some functions will be available in production builds.
+//
+interface KnockoutUtils {
+ //////////////////////////////////
+ // utils.domData.js
+ //////////////////////////////////
+
+ domData: {
+ get (node: Element, key: string): any;
+
+ set (node: Element, key: string, value: any): void;
+
+ getAll(node: Element, createIfNotFound: boolean): any;
+
+ clear(node: Element): boolean;
+ };
+
+ //////////////////////////////////
+ // utils.domNodeDisposal.js
+ //////////////////////////////////
+
+ domNodeDisposal: {
+ addDisposeCallback(node: Element, callback: Function): void;
+
+ removeDisposeCallback(node: Element, callback: Function): void;
+
+ cleanNode(node: Node): Element;
+
+ removeNode(node: Node): void;
+ };
+
+ addOrRemoveItem(array: T[] | KnockoutObservable, value: T, included: T): void;
+
+ arrayFilter(array: T[], predicate: (item: T) => boolean): T[];
+
+ arrayFirst(array: T[], predicate: (item: T) => boolean, predicateOwner?: any): T;
+
+ arrayForEach(array: T[], action: (item: T, index: number) => void): void;
+
+ arrayGetDistinctValues(array: T[]): T[];
+
+ arrayIndexOf(array: T[], item: T): number;
+
+ arrayMap(array: T[], mapping: (item: T) => U): U[];
+
+ arrayPushAll(array: T[] | KnockoutObservableArray, valuesToPush: T[]): T[];
+
+ arrayRemoveItem(array: any[], itemToRemove: any): void;
+
+ compareArrays(a: T[], b: T[]): Array>;
+
+ extend(target: Object, source: Object): Object;
+
+ fieldsIncludedWithJsonPost: any[];
+
+ getFormFields(form: any, fieldName: string): any[];
+
+ objectForEach(obj: any, action: (key: any, value: any) => void): void;
+
+ parseHtmlFragment(html: string): any[];
+
+ parseJson(jsonString: string): any;
+
+ postJson(urlOrForm: any, data: any, options: any): void;
+
+ peekObservable(value: KnockoutObservable): T;
+
+ range(min: any, max: any): any;
+
+ registerEventHandler(element: any, eventType: any, handler: Function): void;
+
+ setHtml(node: Element, html: () => string): void;
+
+ setHtml(node: Element, html: string): void;
+
+ setTextContent(element: any, textContent: string | KnockoutObservable): void;
+
+ stringifyJson(data: any, replacer?: Function, space?: string): string;
+
+ toggleDomNodeCssClass(node: any, className: string, shouldHaveClass: boolean): void;
+
+ triggerEvent(element: any, eventType: any): void;
+
+ unwrapObservable(value: KnockoutObservable | T): T;
+
+ // NOT PART OF THE MINIFIED API SURFACE (ONLY IN knockout-{version}.debug.js) https://github.com/SteveSanderson/knockout/issues/670
+ // forceRefresh(node: any): void;
+ // ieVersion: number;
+ // isIe6: boolean;
+ // isIe7: boolean;
+ // jQueryHtmlParse(html: string): any[];
+ // makeArray(arrayLikeObject: any): any[];
+ // moveCleanedNodesToContainerElement(nodes: any[]): HTMLElement;
+ // replaceDomNodes(nodeToReplaceOrNodeArray: any, newNodesArray: any[]): void;
+ // setDomNodeChildren(domNode: any, childNodes: any[]): void;
+ // setElementName(element: any, name: string): void;
+ // setOptionNodeSelectionState(optionNode: any, isSelected: boolean): void;
+ // simpleHtmlParse(html: string): any[];
+ // stringStartsWith(str: string, startsWith: string): boolean;
+ // stringTokenize(str: string, delimiter: string): string[];
+ // stringTrim(str: string): string;
+ // tagNameLower(element: any): string;
+}
+
+interface KnockoutArrayChange {
+ status: string;
+ value: T;
+ index: number;
+ moved?: number;
+}
+
+//////////////////////////////////
+// templateSources.js
+//////////////////////////////////
+
+interface KnockoutTemplateSourcesDomElement {
+ text(): any;
+ text(value: any): void;
+
+ data(key: string): any;
+ data(key: string, value: any): any;
+}
+
+interface KnockoutTemplateAnonymous extends KnockoutTemplateSourcesDomElement {
+ nodes(): any;
+ nodes(value: any): void;
+}
+
+interface KnockoutTemplateSources {
+
+ domElement: {
+ prototype: KnockoutTemplateSourcesDomElement
+ new (element: Element): KnockoutTemplateSourcesDomElement
+ };
+
+ anonymousTemplate: {
+ prototype: KnockoutTemplateAnonymous;
+ new (element: Element): KnockoutTemplateAnonymous;
+ };
+}
+
+//////////////////////////////////
+// nativeTemplateEngine.js
+//////////////////////////////////
+
+interface KnockoutNativeTemplateEngine {
+
+ renderTemplateSource(templateSource: Object, bindingContext?: KnockoutBindingContext, options?: Object): any[];
+}
+
+//////////////////////////////////
+// templateEngine.js
+//////////////////////////////////
+
+interface KnockoutTemplateEngine extends KnockoutNativeTemplateEngine {
+
+ createJavaScriptEvaluatorBlock(script: string): string;
+
+ makeTemplateSource(template: any, templateDocument?: Document): any;
+
+ renderTemplate(template: any, bindingContext: KnockoutBindingContext, options: Object, templateDocument: Document): any;
+
+ isTemplateRewritten(template: any, templateDocument: Document): boolean;
+
+ rewriteTemplate(template: any, rewriterCallback: Function, templateDocument: Document): void;
+}
+
+/////////////////////////////////
+
+interface KnockoutStatic {
+ utils: KnockoutUtils;
+ memoization: KnockoutMemoization;
+
+ bindingHandlers: KnockoutBindingHandlers;
+ getBindingHandler(handler: string): KnockoutBindingHandler;
+
+ virtualElements: KnockoutVirtualElements;
+ extenders: KnockoutExtenders;
+
+ applyBindings(viewModelOrBindingContext?: any, rootNode?: any): void;
+ applyBindingsToDescendants(viewModelOrBindingContext: any, rootNode: any): void;
+ applyBindingAccessorsToNode(node: Node, bindings: (bindingContext: KnockoutBindingContext, node: Node) => {}, bindingContext: KnockoutBindingContext): void;
+ applyBindingAccessorsToNode(node: Node, bindings: {}, bindingContext: KnockoutBindingContext): void;
+ applyBindingAccessorsToNode(node: Node, bindings: (bindingContext: KnockoutBindingContext, node: Node) => {}, viewModel: any): void;
+ applyBindingAccessorsToNode(node: Node, bindings: {}, viewModel: any): void;
+ applyBindingsToNode(node: Node, bindings: any, viewModelOrBindingContext?: any): any;
+
+ subscribable: KnockoutSubscribableStatic;
+ observable: KnockoutObservableStatic;
+
+ computed: KnockoutComputedStatic;
+ pureComputed(evaluatorFunction: () => T, context?: any): KnockoutComputed;
+ pureComputed(options: KnockoutComputedDefine, context?: any): KnockoutComputed;
+
+ observableArray: KnockoutObservableArrayStatic;
+
+ contextFor(node: any): any;
+ isSubscribable(instance: any): boolean;
+ toJSON(viewModel: any, replacer?: Function, space?: any): string;
+ toJS(viewModel: any): any;
+ isObservable(instance: any): boolean;
+ isWriteableObservable(instance: any): boolean;
+ isComputed(instance: any): boolean;
+ dataFor(node: any): any;
+ removeNode(node: Element): void;
+ cleanNode(node: Element): Element;
+ renderTemplate(template: Function, viewModel: any, options?: any, target?: any, renderMode?: any): any;
+ renderTemplate(template: string, viewModel: any, options?: any, target?: any, renderMode?: any): any;
+ unwrap(value: KnockoutObservable | T): T;
+
+ computedContext: KnockoutComputedContext;
+
+ //////////////////////////////////
+ // templateSources.js
+ //////////////////////////////////
+
+ templateSources: KnockoutTemplateSources;
+
+ //////////////////////////////////
+ // templateEngine.js
+ //////////////////////////////////
+
+ templateEngine: {
+
+ prototype: KnockoutTemplateEngine;
+
+ new (): KnockoutTemplateEngine;
+ };
+
+ //////////////////////////////////
+ // templateRewriting.js
+ //////////////////////////////////
+
+ templateRewriting: {
+
+ ensureTemplateIsRewritten(template: Node, templateEngine: KnockoutTemplateEngine, templateDocument: Document): any;
+ ensureTemplateIsRewritten(template: string, templateEngine: KnockoutTemplateEngine, templateDocument: Document): any;
+
+ memoizeBindingAttributeSyntax(htmlString: string, templateEngine: KnockoutTemplateEngine): any;
+
+ applyMemoizedBindingsToNextSibling(bindings: any, nodeName: string): string;
+ };
+
+ //////////////////////////////////
+ // nativeTemplateEngine.js
+ //////////////////////////////////
+
+ nativeTemplateEngine: {
+
+ prototype: KnockoutNativeTemplateEngine;
+
+ new (): KnockoutNativeTemplateEngine;
+
+ instance: KnockoutNativeTemplateEngine;
+ };
+
+ //////////////////////////////////
+ // jqueryTmplTemplateEngine.js
+ //////////////////////////////////
+
+ jqueryTmplTemplateEngine: {
+
+ prototype: KnockoutTemplateEngine;
+
+ renderTemplateSource(templateSource: Object, bindingContext: KnockoutBindingContext, options: Object): Node[];
+
+ createJavaScriptEvaluatorBlock(script: string): string;
+
+ addTemplate(templateName: string, templateMarkup: string): void;
+ };
+
+ //////////////////////////////////
+ // templating.js
+ //////////////////////////////////
+
+ setTemplateEngine(templateEngine: KnockoutNativeTemplateEngine): void;
+
+ renderTemplate(template: Function, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
+ renderTemplate(template: any, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
+ renderTemplate(template: Function, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
+ renderTemplate(template: any, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
+ renderTemplate(template: Function, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
+ renderTemplate(template: any, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
+ renderTemplate(template: Function, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
+ renderTemplate(template: any, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
+
+ renderTemplateForEach(template: Function, arrayOrObservableArray: any[], options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
+ renderTemplateForEach(template: any, arrayOrObservableArray: any[], options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
+ renderTemplateForEach(template: Function, arrayOrObservableArray: KnockoutObservable, options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
+ renderTemplateForEach(template: any, arrayOrObservableArray: KnockoutObservable, options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
+
+ expressionRewriting: {
+ bindingRewriteValidators: any;
+ parseObjectLiteral: { (objectLiteralString: string): any[] }
+ };
+
+ /////////////////////////////////
+
+ bindingProvider: {
+ instance: KnockoutBindingProvider;
+ new (): KnockoutBindingProvider;
+ }
+
+ /////////////////////////////////
+ // selectExtensions.js
+ /////////////////////////////////
+
+ selectExtensions: {
+
+ readValue(element: HTMLElement): any;
+
+ writeValue(element: HTMLElement, value: any): void;
+ };
+
+ components: KnockoutComponents;
+}
+
+interface KnockoutBindingProvider {
+ nodeHasBindings(node: Node): boolean;
+ getBindings(node: Node, bindingContext: KnockoutBindingContext): {};
+ getBindingAccessors?(node: Node, bindingContext: KnockoutBindingContext): { [key: string]: string; };
+}
+
+interface KnockoutComputedContext {
+ getDependenciesCount(): number;
+ isInitial: () => boolean;
+ isSleeping: boolean;
+}
+
+//
+// refactored types into a namespace to reduce global pollution
+// and used Union Types to simplify overloads (requires TypeScript 1.4)
+//
+declare module KnockoutComponentTypes {
+
+ interface Config {
+ viewModel?: ViewModelFunction | ViewModelSharedInstance | ViewModelFactoryFunction | AMDModule;
+ template: string | Node[]| DocumentFragment | TemplateElement | AMDModule;
+ synchronous?: boolean;
+ }
+
+ interface ComponentConfig {
+ viewModel?: ViewModelFunction | ViewModelSharedInstance | ViewModelFactoryFunction | AMDModule;
+ template: any;
+ createViewModel?: any;
+ }
+
+ interface EmptyConfig {
+ }
+
+ // common AMD type
+ interface AMDModule {
+ require: string;
+ }
+
+ // viewmodel types
+ interface ViewModelFunction {
+ (params?: any): any;
+ }
+
+ interface ViewModelSharedInstance {
+ instance: any;
+ }
+
+ interface ViewModelFactoryFunction {
+ createViewModel: (params?: any, componentInfo?: ComponentInfo) => any;
+ }
+
+ interface ComponentInfo {
+ element: Node;
+ templateNodes: Node[];
+ }
+
+ interface TemplateElement {
+ element: string | Node;
+ }
+
+ interface Loader {
+ getConfig? (componentName: string, callback: (result: ComponentConfig) => void): void;
+ loadComponent? (componentName: string, config: ComponentConfig, callback: (result: Definition) => void): void;
+ loadTemplate? (componentName: string, templateConfig: any, callback: (result: Node[]) => void): void;
+ loadViewModel? (componentName: string, viewModelConfig: any, callback: (result: any) => void): void;
+ suppressLoaderExceptions?: boolean;
+ }
+
+ interface Definition {
+ template: Node[];
+ createViewModel? (params: any, options: { element: Node; }): any;
+ }
+}
+
+interface KnockoutComponents {
+ // overloads for register method:
+ register(componentName: string, config: KnockoutComponentTypes.Config | KnockoutComponentTypes.EmptyConfig): void;
+
+ isRegistered(componentName: string): boolean;
+ unregister(componentName: string): void;
+ get(componentName: string, callback: (definition: KnockoutComponentTypes.Definition) => void): void;
+ clearCachedDefinition(componentName: string): void
+ defaultLoader: KnockoutComponentTypes.Loader;
+ loaders: KnockoutComponentTypes.Loader[];
+ getComponentNameForNode(node: Node): string;
+}
+
+declare var ko: KnockoutStatic;
+
+declare module "knockout" {
+ export = ko;
+}
diff --git a/samples/react-sp-elevatedprivileges/webpart/typings/lodash/lodash.d.ts b/samples/react-sp-elevatedprivileges/webpart/typings/lodash/lodash.d.ts
new file mode 100644
index 000000000..1e39d223f
--- /dev/null
+++ b/samples/react-sp-elevatedprivileges/webpart/typings/lodash/lodash.d.ts
@@ -0,0 +1,20808 @@
+// Type definitions for Lo-Dash
+// Project: http://lodash.com/
+// Definitions by: Brian Zengel , Ilya Mochalov , Stepan Mikhaylyuk
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+
+/**
+### 4.0.0 Changelog (https://github.com/lodash/lodash/wiki/Changelog)
+
+#### TODO:
+removed:
+- [x] Removed _.support
+- [x] Removed _.findWhere in favor of _.find with iteratee shorthand
+- [x] Removed _.where in favor of _.filter with iteratee shorthand
+- [x] Removed _.pluck in favor of _.map with iteratee shorthand
+
+renamed:
+- [x] Renamed _.first to _.head
+- [x] Renamed _.indexBy to _.keyBy
+- [x] Renamed _.invoke to _.invokeMap
+- [x] Renamed _.overArgs to _.overArgs
+- [x] Renamed _.padLeft & _.padRight to _.padStart & _.padEnd
+- [x] Renamed _.pairs to _.toPairs
+- [x] Renamed _.rest to _.tail
+- [x] Renamed _.restParam to _.rest
+- [x] Renamed _.sortByOrder to _.orderBy
+- [x] Renamed _.trimLeft & _.trimRight to _.trimStart & _.trimEnd
+- [x] Renamed _.trunc to _.truncate
+
+split:
+- [x] Split _.indexOf & _.lastIndexOf into _.sortedIndexOf & _.sortedLastIndexOf
+- [x] Split _.max & _.min into _.maxBy & _.minBy
+- [x] Split _.omit & _.pick into _.omitBy & _.pickBy
+- [x] Split _.sample into _.sampleSize
+- [x] Split _.sortedIndex into _.sortedIndexBy
+- [x] Split _.sortedLastIndex into _.sortedLastIndexBy
+- [x] Split _.uniq into _.sortedUniq, _.sortedUniqBy, & _.uniqBy
+
+changes:
+- [x] Absorbed _.sortByAll into _.sortBy
+- [x] Changed the category of _.at to “Object”
+- [x] Changed the category of _.bindAll to “Utility”
+- [x] Made _.capitalize uppercase the first character & lowercase the rest
+- [x] Made _.functions return only own method names
+
+
+added 23 array methods:
+- [x] _.concat
+- [x] _.differenceBy
+- [x] _.differenceWith
+- [x] _.flatMap
+- [x] _.fromPairs
+- [x] _.intersectionBy
+- [x] _.intersectionWith
+- [x] _.join
+- [x] _.pullAll
+- [x] _.pullAllBy
+- [x] _.reverse
+- [x] _.sortedIndexBy
+- [x] _.sortedIndexOf
+- [x] _.sortedLastIndexBy
+- [x] _.sortedLastIndexOf
+- [x] _.sortedUniq
+- [x] _.sortedUniqBy
+- [x] _.unionBy
+- [x] _.unionWith
+- [x] _.uniqBy
+- [x] _.uniqWith
+- [x] _.xorBy
+- [x] _.xorWith
+
+added 18 lang methods:
+- [x] _.cloneDeepWith
+- [x] _.cloneWith
+- [x] _.eq
+- [x] _.isArrayLike
+- [x] _.isArrayLikeObject
+- [x] _.isEqualWith
+- [x] _.isInteger
+- [x] _.isLength
+- [x] _.isMatchWith
+- [x] _.isNil
+- [x] _.isObjectLike
+- [x] _.isSafeInteger
+- [x] _.isSymbol
+- [x] _.toInteger
+- [x] _.toLength
+- [x] _.toNumber
+- [x] _.toSafeInteger
+- [x] _.toString
+
+added 13 object methods:
+- [x] _.assignIn
+- [x] _.assignInWith
+- [x] _.assignWith
+- [x] _.functionsIn
+- [x] _.hasIn
+- [x] _.mergeWith
+- [x] _.omitBy
+- [x] _.pickBy
+
+
+added 8 string methods:
+- [x] _.lowerCase
+- [x] _.lowerFirst
+- [x] _.upperCase
+- [x] _.upperFirst
+- [x] _.toLower
+- [x] _.toUpper
+
+added 8 utility methods:
+- [x] _.toPath
+
+added 4 math methods:
+- [x] _.maxBy
+- [x] _.mean
+- [x] _.minBy
+- [x] _.sumBy
+
+added 2 function methods:
+- [x] _.flip
+- [x] _.unary
+
+added 2 number methods:
+- [x] _.clamp
+- [x] _.subtract
+
+added collection method:
+- [x] _.sampleSize
+
+Added 3 aliases
+
+- [x] _.first as an alias of _.head
+
+Removed 17 aliases
+- [x] Removed aliase _.all
+- [x] Removed aliase _.any
+- [x] Removed aliase _.backflow
+- [x] Removed aliase _.callback
+- [x] Removed aliase _.collect
+- [x] Removed aliase _.compose
+- [x] Removed aliase _.contains
+- [x] Removed aliase _.detect
+- [x] Removed aliase _.foldl
+- [x] Removed aliase _.foldr
+- [x] Removed aliase _.include
+- [x] Removed aliase _.inject
+- [x] Removed aliase _.methods
+- [x] Removed aliase _.object
+- [x] Removed aliase _.run
+- [x] Removed aliase _.select
+- [x] Removed aliase _.unique
+
+Other changes
+- [x] Added support for array buffers to _.isEqual
+- [x] Added support for converting iterators to _.toArray
+- [x] Added support for deep paths to _.zipObject
+- [x] Changed UMD to export to window or self when available regardless of other exports
+- [x] Ensured debounce cancel clears args & thisArg references
+- [x] Ensured _.add, _.subtract, & _.sum don’t skip NaN values
+- [x] Ensured _.clone treats generators like functions
+- [x] Ensured _.clone produces clones with the source’s [[Prototype]]
+- [x] Ensured _.defaults assigns properties that shadow Object.prototype
+- [x] Ensured _.defaultsDeep doesn’t merge a string into an array
+- [x] Ensured _.defaultsDeep & _.merge don’t modify sources
+- [x] Ensured _.defaultsDeep works with circular references
+- [x] Ensured _.keys skips “length” on strict mode arguments objects in Safari 9
+- [x] Ensured _.merge doesn’t convert strings to arrays
+- [x] Ensured _.merge merges plain-objects onto non plain-objects
+- [x] Ensured _#plant resets iterator data of cloned sequences
+- [x] Ensured _.random swaps min & max if min is greater than max
+- [x] Ensured _.range preserves the sign of start of -0
+- [x] Ensured _.reduce & _.reduceRight use getIteratee in their array branch
+- [x] Fixed rounding issue with the precision param of _.floor
+
+** LATER **
+Misc:
+- [ ] Made _.forEach, _.forIn, _.forOwn, & _.times implicitly end a chain sequence
+- [ ] Removed thisArg params from most methods
+- [ ] Made “By” methods provide a single param to iteratees
+- [ ] Made _.words chainable by default
+- [ ] Removed isDeep params from _.clone & _.flatten
+- [ ] Removed _.bindAll support for binding all methods when no names are provided
+- [ ] Removed func-first param signature from _.before & _.after
+- [ ] _.extend as an alias of _.assignIn
+- [ ] _.extendWith as an alias of _.assignInWith
+- [ ] Added clear method to _.memoize.Cache
+- [ ] Added flush method to debounced & throttled functions
+- [ ] Added support for ES6 maps, sets, & symbols to _.clone, _.isEqual, & _.toArray
+- [ ] Enabled _.flow & _.flowRight to accept an array of functions
+- [ ] Ensured “Collection” methods treat functions as objects
+- [ ] Ensured _.assign, _.defaults, & _.merge coerce object values to objects
+- [ ] Ensured _.bindKey bound functions call object[key] when called with the new operator
+- [ ] Ensured _.isFunction returns true for generator functions
+- [ ] Ensured _.merge assigns typed arrays directly
+- [ ] Made _(...) an iterator & iterable
+- [ ] Made _.drop, _.take, & right forms coerce n of undefined to 0
+
+Methods:
+- [ ] _.concat
+- [ ] _.differenceBy
+- [ ] _.differenceWith
+- [ ] _.flatMap
+- [ ] _.fromPairs
+- [ ] _.intersectionBy
+- [ ] _.intersectionWith
+- [ ] _.join
+- [ ] _.pullAll
+- [ ] _.pullAllBy
+- [ ] _.reverse
+- [ ] _.sortedLastIndexOf
+- [ ] _.unionBy
+- [ ] _.unionWith
+- [ ] _.uniqWith
+- [ ] _.xorBy
+- [ ] _.xorWith
+- [ ] _.toString
+
+- [ ] _.invoke
+- [ ] _.setWith
+- [ ] _.toPairs
+- [ ] _.toPairsIn
+- [ ] _.unset
+
+- [ ] _.replace
+- [ ] _.split
+
+- [ ] _.cond
+- [ ] _.conforms
+- [ ] _.nthArg
+- [ ] _.over
+- [ ] _.overEvery
+- [ ] _.overSome
+- [ ] _.rangeRight
+
+- [ ] _.next
+*/
+
+declare var _: _.LoDashStatic;
+
+declare module _ {
+ interface LoDashStatic {
+ /**
+ * Creates a lodash object which wraps the given value to enable intuitive method chaining.
+ *
+ * In addition to Lo-Dash methods, wrappers also have the following Array methods:
+ * concat, join, pop, push, reverse, shift, slice, sort, splice, and unshift
+ *
+ * Chaining is supported in custom builds as long as the value method is implicitly or
+ * explicitly included in the build.
+ *
+ * The chainable wrapper functions are:
+ * after, assign, bind, bindAll, bindKey, chain, chunk, compact, compose, concat, countBy,
+ * createCallback, curry, debounce, defaults, defer, delay, difference, filter, flatten,
+ * forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, functions, groupBy,
+ * keyBy, initial, intersection, invert, invoke, keys, map, max, memoize, merge, min,
+ * object, omit, once, pairs, partial, partialRight, pick, pluck, pull, push, range, reject,
+ * remove, rest, reverse, sample, shuffle, slice, sort, sortBy, splice, tap, throttle, times,
+ * toArray, transform, union, uniq, unset, unshift, unzip, values, where, without, wrap, and zip
+ *
+ * The non-chainable wrapper functions are:
+ * clone, cloneDeep, contains, escape, every, find, findIndex, findKey, findLast,
+ * findLastIndex, findLastKey, has, identity, indexOf, isArguments, isArray, isBoolean,
+ * isDate, isElement, isEmpty, isEqual, isFinite, isFunction, isNaN, isNull, isNumber,
+ * isObject, isPlainObject, isRegExp, isString, isUndefined, join, lastIndexOf, mixin,
+ * noConflict, parseInt, pop, random, reduce, reduceRight, result, shift, size, some,
+ * sortedIndex, runInContext, template, unescape, uniqueId, and value
+ *
+ * The wrapper functions first and last return wrapped values when n is provided, otherwise
+ * they return unwrapped values.
+ *
+ * Explicit chaining can be enabled by using the _.chain method.
+ **/
+ (value: number): LoDashImplicitWrapper;
+ (value: string): LoDashImplicitStringWrapper;
+ (value: boolean): LoDashImplicitWrapper;
+ (value: Array): LoDashImplicitNumberArrayWrapper;
+ (value: Array): LoDashImplicitArrayWrapper;
+ (value: T): LoDashImplicitObjectWrapper;
+ (value: any): LoDashImplicitWrapper;
+
+ /**
+ * The semantic version number.
+ **/
+ VERSION: string;
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby
+ * (ERB). Change the following template settings to use alternative delimiters.
+ **/
+ templateSettings: TemplateSettings;
+ }
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby
+ * (ERB). Change the following template settings to use alternative delimiters.
+ **/
+ interface TemplateSettings {
+ /**
+ * The "escape" delimiter.
+ **/
+ escape?: RegExp;
+
+ /**
+ * The "evaluate" delimiter.
+ **/
+ evaluate?: RegExp;
+
+ /**
+ * An object to import into the template as local variables.
+ **/
+ imports?: Dictionary;
+
+ /**
+ * The "interpolate" delimiter.
+ **/
+ interpolate?: RegExp;
+
+ /**
+ * Used to reference the data object in the template text.
+ **/
+ variable?: string;
+ }
+
+ /**
+ * Creates a cache object to store key/value pairs.
+ */
+ interface MapCache {
+ /**
+ * Removes `key` and its value from the cache.
+ * @param key The key of the value to remove.
+ * @return Returns `true` if the entry was removed successfully, else `false`.
+ */
+ delete(key: string): boolean;
+
+ /**
+ * Gets the cached value for `key`.
+ * @param key The key of the value to get.
+ * @return Returns the cached value.
+ */
+ get(key: string): any;
+
+ /**
+ * Checks if a cached value for `key` exists.
+ * @param key The key of the entry to check.
+ * @return Returns `true` if an entry for `key` exists, else `false`.
+ */
+ has(key: string): boolean;
+
+ /**
+ * Sets `value` to `key` of the cache.
+ * @param key The key of the value to cache.
+ * @param value The value to cache.
+ * @return Returns the cache object.
+ */
+ set(key: string, value: any): _.Dictionary;
+ }
+
+ interface LoDashWrapperBase { }
+
+ interface LoDashImplicitWrapperBase extends LoDashWrapperBase { }
+
+ interface LoDashExplicitWrapperBase extends LoDashWrapperBase { }
+
+ interface LoDashImplicitWrapper extends LoDashImplicitWrapperBase> { }
+
+ interface LoDashExplicitWrapper extends LoDashExplicitWrapperBase> { }
+
+ interface LoDashImplicitStringWrapper extends LoDashImplicitWrapper { }
+
+ interface LoDashExplicitStringWrapper extends LoDashExplicitWrapper { }
+
+ interface LoDashImplicitObjectWrapper extends LoDashImplicitWrapperBase> { }
+
+ interface LoDashExplicitObjectWrapper extends LoDashExplicitWrapperBase> { }
+
+ interface LoDashImplicitArrayWrapper extends LoDashImplicitWrapperBase> {
+ pop(): T;
+ push(...items: T[]): LoDashImplicitArrayWrapper;
+ shift(): T;
+ sort(compareFn?: (a: T, b: T) => number): LoDashImplicitArrayWrapper;
+ splice(start: number): LoDashImplicitArrayWrapper;
+ splice(start: number, deleteCount: number, ...items: any[]): LoDashImplicitArrayWrapper;
+ unshift(...items: T[]): LoDashImplicitArrayWrapper;
+ }
+
+ interface LoDashExplicitArrayWrapper extends LoDashExplicitWrapperBase> { }
+
+ interface LoDashImplicitNumberArrayWrapper extends LoDashImplicitArrayWrapper { }
+
+ interface LoDashExplicitNumberArrayWrapper extends LoDashExplicitArrayWrapper { }
+
+ /*********
+ * Array *
+ *********/
+
+ //_.chunk
+ interface LoDashStatic {
+ /**
+ * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the
+ * final chunk will be the remaining elements.
+ *
+ * @param array The array to process.
+ * @param size The length of each chunk.
+ * @return Returns the new array containing chunks.
+ */
+ chunk(
+ array: List,
+ size?: number
+ ): T[][];
+ }
+
+ interface LoDashImplicitArrayWrapper {
+ /**
+ * @see _.chunk
+ */
+ chunk(size?: number): LoDashImplicitArrayWrapper;
+ }
+
+ interface LoDashImplicitObjectWrapper {
+ /**
+ * @see _.chunk
+ */
+ chunk(size?: number): LoDashImplicitArrayWrapper;
+ }
+
+ interface LoDashExplicitArrayWrapper {
+ /**
+ * @see _.chunk
+ */
+ chunk(size?: number): LoDashExplicitArrayWrapper;
+ }
+
+ interface LoDashExplicitObjectWrapper {
+ /**
+ * @see _.chunk
+ */
+ chunk