private async Task <CanProvisionResult> CanProvisionInternal(CanProvisionModel model, Boolean validateUser = true) { var canProvisionResult = new CanProvisionResult(); if (!validateUser || IsValidUser()) { String provisioningScope = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"]; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; var tokenId = $"{model.TenantId}-{model.UserPrincipalName.ToLower().GetHashCode()}-{provisioningScope}-{provisioningEnvironment}"; var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, "https://graph.microsoft.com/"); // Retrieve the provisioning package from the database and from the Blob Storage var context = GetDataContext(); DomainModel.Package package = null; // Get the package if (ProvisioningAppManager.IsTestingEnvironment) { // Process all packages in the test environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId)); } else { // Process not-preview packages in the production environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId) && p.Preview == false); } if (package != null) { if ((package.PackageType == PackageType.Tenant && !this.Request.Url.AbsolutePath.Contains("/tenant/")) || (package.PackageType == PackageType.SiteCollection && !this.Request.Url.AbsolutePath.Contains("/site/"))) { throw new ApplicationException("Invalid request, the requested package/template is not valid for the current request!"); } // Retrieve parameters from the package/template definition var packageFileUrl = new Uri(package.PackageUrl); var packageLocalFolder = packageFileUrl.AbsolutePath.Substring(1, packageFileUrl.AbsolutePath.LastIndexOf('/') - 1); var packageFileName = packageFileUrl.AbsolutePath.Substring(packageLocalFolder.Length + 2); ProvisioningHierarchy hierarchy = GetHierarchyFromStorage(packageLocalFolder, packageFileName); // If we have the hierarchy if (hierarchy != null) { var accessTokens = new Dictionary <String, String>(); AuthenticationManager authManager = new AuthenticationManager(); var ptai = new ProvisioningTemplateApplyingInformation(); // Retrieve the SPO URL for the Admin Site var rootSiteUrl = model.SPORootSiteUrl; // Retrieve the SPO Access Token for SPO var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, rootSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Access Token for any further context cloning accessTokens.Add(new Uri(rootSiteUrl).Authority, spoAccessToken); // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.ContainsKey(r)) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour // (in fact, it's a matter of few seconds) return(await Task.FromResult(accessTokens[r])); } else { // Try to get a fresh new Access Token var token = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, $"https://{r}", ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); accessTokens.Add(r, token); return(token); } })) { // If the user is an admin (SPO or Tenant) we run the Tenant level CanProvision rules if (model.UserIsSPOAdmin || model.UserIsTenantAdmin) { // Retrieve the SPO URL for the Admin Site var adminSiteUrl = model.SPORootSiteUrl.Replace(".sharepoint.com", "-admin.sharepoint.com"); // Retrieve the SPO Access Token for the Admin Site var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, adminSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Admin Access Token for any further context cloning accessTokens.Add(new Uri(adminSiteUrl).Authority, spoAdminAccessToken); // Connect to SPO Admin Site and evaluate the CanProvision rules for the hierarchy using (var tenantContext = authManager.GetAzureADAccessTokenAuthenticatedContext(adminSiteUrl, spoAdminAccessToken)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { // Creat the Tenant object for the current SPO Admin Site context TenantAdmin.Tenant tenant = new TenantAdmin.Tenant(pnpTenantContext); // Run the CanProvision rules against the current tenant canProvisionResult = CanProvisionRulesManager.CanProvision(tenant, hierarchy, null, ptai); } } } else { // Otherwise we run the Site level CanProvision rules // Connect to SPO Root Site and evaluate the CanProvision rules for the hierarchy using (var clientContext = authManager.GetAzureADAccessTokenAuthenticatedContext(rootSiteUrl, spoAccessToken)) { using (var pnpContext = PnPClientContext.ConvertFrom(clientContext)) { // Run the CanProvision rules against the root site canProvisionResult = CanProvisionRulesManager.CanProvision(pnpContext.Web, hierarchy.Templates[0], ptai); } } } } } } else { throw new ApplicationException("Invalid request, the requested package/template is not available!"); } } return(canProvisionResult); }
public static async Task <IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log, ExecutionContext context) { log.LogInformation("C# HTTP trigger function processed a request."); try { string siteUrl = ""; string userName = ""; string password = ""; var securePassword = new SecureString(); foreach (char c in password) { securePassword.AppendChar(c); } securePassword.MakeReadOnly(); var authManager = new AuthenticationManager(userName, securePassword); using (ClientContext clientContext = authManager.GetContext(siteUrl)) { var web = clientContext.Web; var templateFileName = "SitePagesTemplate.pnp"; FileConnectorBase fileConnector = new FileSystemConnector(context.FunctionAppDirectory, ""); var openXmlConnector = new OpenXMLConnector(templateFileName, fileConnector); var provider = new XMLOpenXMLTemplateProvider(openXmlConnector); templateFileName = templateFileName.Substring(0, templateFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; ProvisioningTemplate provisioningTemplate = provider.GetTemplate(templateFileName); provisioningTemplate.Connector = provider.Connector; var applyingInformation = new ProvisioningTemplateApplyingInformation() { ProgressDelegate = (message, progress, total) => { log.LogInformation(string.Format("{0:00}/{1:00} - {2}", progress, total, message)); }, MessagesDelegate = (message, messageType) => { log.LogInformation(string.Format("{0} - {1}", messageType, message)); }, IgnoreDuplicateDataRowErrors = true }; web.ApplyProvisioningTemplate(provisioningTemplate, applyingInformation); return(new OkObjectResult("Done")); } } catch (ServerException e) { log.LogError($"Message: {e.Message}"); log.LogError($"ServerErrorCode: {e.ServerErrorCode}"); log.LogError($"ServerErrorDetails: {e.ServerErrorDetails}"); log.LogError($"ServerErrorTraceCorrelationId: {e.ServerErrorTraceCorrelationId}"); log.LogError($"ServerErrorTypeName: {e.ServerErrorTypeName}"); log.LogError($"ServerErrorValue: {e.ServerErrorValue}"); log.LogError($"ServerStackTrace: {e.ServerStackTrace}"); log.LogError($"Source: {e.Source}"); log.LogError($"StackTrace: {e.StackTrace}"); throw; } catch (Exception e) { log.LogError($"Error while processing dossier: {e.Message}\n\n{e.StackTrace}"); throw; } }
protected override void ExecuteCmdlet() { SelectedWeb.EnsureProperty(w => w.Url); if (!System.IO.Path.IsPathRooted(Path)) { Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); } if (!string.IsNullOrEmpty(ResourceFolder)) { if (System.IO.Path.IsPathRooted(ResourceFolder)) { ResourceFolder = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, ResourceFolder); } } FileInfo fileInfo = new FileInfo(Path); XMLTemplateProvider provider = new XMLFileSystemTemplateProvider(fileInfo.DirectoryName, ""); var provisioningTemplate = provider.GetTemplate(fileInfo.Name); if (provisioningTemplate != null) { FileSystemConnector fileSystemConnector = null; if (string.IsNullOrEmpty(ResourceFolder)) { fileSystemConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); } else { fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); } provisioningTemplate.Connector = fileSystemConnector; if (Parameters != null) { foreach (var parameter in Parameters.Keys) { if (provisioningTemplate.Parameters.ContainsKey(parameter.ToString())) { provisioningTemplate.Parameters[parameter.ToString()] = Parameters[parameter].ToString(); } else { provisioningTemplate.Parameters.Add(parameter.ToString(), Parameters[parameter].ToString()); } } } var applyingInformation = new ProvisioningTemplateApplyingInformation(); if (this.MyInvocation.BoundParameters.ContainsKey("Handlers")) { applyingInformation.HandlersToProcess = Handlers; } if (this.MyInvocation.BoundParameters.ContainsKey("ExcludeHandlers")) { foreach (var handler in (OfficeDevPnP.Core.Framework.Provisioning.Model.Handlers[])Enum.GetValues(typeof(Handlers))) { if (!ExcludeHandlers.Has(handler) && handler != Handlers.All) { Handlers = Handlers | handler; } } applyingInformation.HandlersToProcess = Handlers; } applyingInformation.ProgressDelegate = (message, step, total) => { WriteProgress(new ProgressRecord(0, string.Format("Applying template to {0}", SelectedWeb.Url), message) { PercentComplete = (100 / total) * step }); }; applyingInformation.MessagesDelegate = (message, type) => { if (type == ProvisioningMessageType.Warning) { WriteWarning(message); } }; applyingInformation.OverwriteSystemPropertyBagValues = OverwriteSystemPropertyBagValues; SelectedWeb.ApplyProvisioningTemplate(provisioningTemplate, applyingInformation); } }
private static void applyTemplate(SiteInformation siteInformation, ExecutionContext functionContext, ClientCredentials credentials, TraceWriter log) { try { using (var ctx = new AuthenticationManager().GetAppOnlyAuthenticatedContext(siteInformation.SiteUrl, credentials.ClientID, credentials.ClientSecret)) { Web web = ctx.Web; ctx.Load(web, w => w.Title, w => w.Navigation.QuickLaunch); ctx.ExecuteQueryRetry(); string groupID = GetSiteGroupID(ctx); // UpdateSubscriptionItemProperties(credentials, siteInformation, web.Title ,log); var rootSiteUrl = ConfigurationManager.AppSettings["RootSiteUrl"]; log.Info($"Successfully connected to site: {web.Title}"); var navigationcolls = web.Navigation.QuickLaunch; foreach (var nav in navigationcolls) { if (nav.Title == "Key Dates & Deliverables") { string url = nav.Url.Split('?')[0] + "?groupId=" + groupID + "&planId=" + siteInformation.PlanId; nav.Url = url; nav.Update(); ctx.ExecuteQueryRetry(); } } string currentDirectory = functionContext.FunctionDirectory; DirectoryInfo dInfo = new DirectoryInfo(currentDirectory); var schemaDir = dInfo.Parent.FullName + "\\Templates"; XMLTemplateProvider sitesProvider = new XMLFileSystemTemplateProvider(schemaDir, ""); log.Info($"About to get template with with filename '{PNP_TEMPLATE_FILE}'"); ProvisioningTemplate template = sitesProvider.GetTemplate(PNP_TEMPLATE_FILE); var getHomeClientPage = template.ClientSidePages.Find(i => i.Title == "Home"); if (getHomeClientPage != null) { UpdateControlsDataDynamic(siteInformation, getHomeClientPage); } log.Info($"Successfully found template with ID '{template.Id}'"); ProvisioningTemplateApplyingInformation ptai = new ProvisioningTemplateApplyingInformation { ProgressDelegate = (message, progress, total) => { log.Info(string.Format("{0:00}/{1:00} - {2}", progress, total, message)); } }; // Associate file connector for assets.. FileSystemConnector connector = new FileSystemConnector(Path.Combine(currentDirectory, "Files"), ""); template.Connector = connector; web.ApplyProvisioningTemplate(template, ptai); if (siteInformation.IsTopNavigation) { // Add top navigation bar. web.AddNavigationNode("SharePoint Main Menu", new Uri(rootSiteUrl + "/SitePages/Home.aspx"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); web.AddNavigationNode("Document Centre", new Uri(rootSiteUrl + "/Document%20Centre/SitePages/Home.aspx"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); web.AddNavigationNode("Project Centre", new Uri(rootSiteUrl + "/Project%20Centre/SitePages/Home.aspx"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); web.AddNavigationNode("WHS Centre", new Uri(rootSiteUrl + "/WHS%20Centre/"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); web.AddNavigationNode("Training Centre", new Uri(rootSiteUrl + "/Training%20Centre"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); web.AddNavigationNode("Proposal Hub", new Uri(rootSiteUrl + "/Proposal%20Hub"), "", OfficeDevPnP.Core.Enums.NavigationType.TopNavigationBar); } UpdateListTitle(ctx, web, "01. Project Management"); } } catch (Exception e) { log.Error("Error when applying PnP template!", e); throw; } }
protected override void ExecuteCmdlet() { SelectedWeb.EnsureProperty(w => w.Url); bool templateFromFileSystem = !Path.ToLower().StartsWith("http"); FileConnectorBase fileConnector; string templateFileName = System.IO.Path.GetFileName(Path); if (templateFromFileSystem) { if (!System.IO.Path.IsPathRooted(Path)) { Path = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, Path); } if (!string.IsNullOrEmpty(ResourceFolder)) { if (!System.IO.Path.IsPathRooted(ResourceFolder)) { ResourceFolder = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, ResourceFolder); } } var fileInfo = new FileInfo(Path); fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); } else { Uri fileUri = new Uri(Path); var webUrl = Microsoft.SharePoint.Client.Web.WebUrlFromFolderUrlDirect(ClientContext, fileUri); var templateContext = ClientContext.Clone(webUrl.ToString()); var library = Path.ToLower().Replace(templateContext.Url.ToLower(), "").TrimStart('/'); var idx = library.IndexOf("/", StringComparison.Ordinal); library = library.Substring(0, idx); // This syntax creates a SharePoint connector regardless we have the -InputInstance argument or not fileConnector = new SharePointConnector(templateContext, templateContext.Url, library); } ProvisioningTemplate provisioningTemplate; // If we don't have the -InputInstance parameter, we load the template from the source connector if (InputInstance == null) { Stream stream = fileConnector.GetFileStream(templateFileName); var isOpenOfficeFile = IsOpenOfficeFile(stream); XMLTemplateProvider provider; if (isOpenOfficeFile) { provider = new XMLOpenXMLTemplateProvider(new OpenXMLConnector(templateFileName, fileConnector)); templateFileName = templateFileName.Substring(0, templateFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; } else { if (templateFromFileSystem) { provider = new XMLFileSystemTemplateProvider(fileConnector.Parameters[FileConnectorBase.CONNECTIONSTRING] + "", ""); } else { throw new NotSupportedException("Only .pnp package files are supported from a SharePoint library"); } } provisioningTemplate = provider.GetTemplate(templateFileName, TemplateProviderExtensions); if (provisioningTemplate == null) { // If we don't have the template, raise an error and exit WriteError(new ErrorRecord(new Exception("The -Path parameter targets an invalid repository or template object."), "WRONG_PATH", ErrorCategory.SyntaxError, null)); return; } if (isOpenOfficeFile) { provisioningTemplate.Connector = provider.Connector; } else { if (ResourceFolder != null) { var fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); provisioningTemplate.Connector = fileSystemConnector; } else { provisioningTemplate.Connector = provider.Connector; } } } // Otherwise we use the provisioning template instance provided through the -InputInstance parameter else { provisioningTemplate = InputInstance; if (ResourceFolder != null) { var fileSystemConnector = new FileSystemConnector(ResourceFolder, ""); provisioningTemplate.Connector = fileSystemConnector; } else { provisioningTemplate.Connector = fileConnector; } } if (Parameters != null) { foreach (var parameter in Parameters.Keys) { if (provisioningTemplate.Parameters.ContainsKey(parameter.ToString())) { provisioningTemplate.Parameters[parameter.ToString()] = Parameters[parameter].ToString(); } else { provisioningTemplate.Parameters.Add(parameter.ToString(), Parameters[parameter].ToString()); } } } var applyingInformation = new ProvisioningTemplateApplyingInformation(); if (MyInvocation.BoundParameters.ContainsKey("Handlers")) { applyingInformation.HandlersToProcess = Handlers; } if (MyInvocation.BoundParameters.ContainsKey("ExcludeHandlers")) { foreach (var handler in (Handlers[])Enum.GetValues(typeof(Handlers))) { if (!ExcludeHandlers.Has(handler) && handler != Handlers.All) { Handlers = Handlers | handler; } } applyingInformation.HandlersToProcess = Handlers; } if (ExtensibilityHandlers != null) { applyingInformation.ExtensibilityHandlers = ExtensibilityHandlers.ToList(); } applyingInformation.ProgressDelegate = (message, step, total) => { WriteProgress(new ProgressRecord(0, $"Applying template to {SelectedWeb.Url}", message) { PercentComplete = (100 / total) * step }); }; applyingInformation.MessagesDelegate = (message, type) => { if (type == ProvisioningMessageType.Warning) { WriteWarning(message); } }; applyingInformation.OverwriteSystemPropertyBagValues = OverwriteSystemPropertyBagValues; SelectedWeb.ApplyProvisioningTemplate(provisioningTemplate, applyingInformation); WriteProgress(new ProgressRecord(0, $"Applying template to {SelectedWeb.Url}", " ") { RecordType = ProgressRecordType.Completed }); }
public ProvisioningTemplateApplyingInformation ToApplyingInformation() { var ai = new ProvisioningTemplateApplyingInformation { ApplyConfiguration = this }; if (this.AccessTokens != null && this.AccessTokens.Any()) { ai.AccessTokens = this.AccessTokens; } ai.ProvisionContentTypesToSubWebs = this.ContentTypes.ProvisionContentTypesToSubWebs; ai.OverwriteSystemPropertyBagValues = this.PropertyBag.OverwriteSystemValues; ai.IgnoreDuplicateDataRowErrors = this.Lists.IgnoreDuplicateDataRowErrors; ai.ClearNavigation = this.Navigation.ClearNavigation; ai.ProvisionFieldsToSubWebs = this.Fields.ProvisionFieldsToSubWebs; if (Handlers.Any()) { ai.HandlersToProcess = Model.Handlers.None; foreach (var handler in Handlers) { Model.Handlers handlerEnumValue = Model.Handlers.None; switch (handler) { case ConfigurationHandler.Pages: handlerEnumValue = Model.Handlers.Pages | Model.Handlers.PageContents; break; case ConfigurationHandler.Taxonomy: handlerEnumValue = Model.Handlers.TermGroups; break; default: handlerEnumValue = (Model.Handlers)Enum.Parse(typeof(Model.Handlers), handler.ToString()); break; } ai.HandlersToProcess |= handlerEnumValue; } } else { ai.HandlersToProcess = Model.Handlers.All; } if (this.ProgressDelegate != null) { ai.ProgressDelegate = (message, step, total) => { ProgressDelegate(message, step, total); }; } if (this.MessagesDelegate != null) { ai.MessagesDelegate = (message, type) => { MessagesDelegate(message, type); }; } if (this.SiteProvisionedDelegate != null) { ai.SiteProvisionedDelegate = (title, siteUrl) => { SiteProvisionedDelegate(title, siteUrl); }; } return(ai); }
/// <summary> /// Method to Invoke Custom Provisioning Handlers. /// </summary> /// <remarks> /// Ensure the ClientContext is not disposed in the custom provider. /// </remarks> /// <param name="ctx">Authenticated ClientContext that is passed to the custom provider.</param> /// <param name="handler">A custom Extensibility Provisioning Provider</param> /// <param name="template">ProvisioningTemplate that is passed to the custom provider</param> /// <param name="applyingInformation">The Provisioning Template application information object</param> /// <param name="tokenParser">The Token Parser used by the engine during template provisioning</param> /// <param name="scope">The PnPMonitoredScope of the current step in the pipeline</param> /// <exception cref="ExtensiblityPipelineException"></exception> /// <exception cref="ArgumentException">Provider.Assembly or Provider.Type is NullOrWhiteSpace></exception> /// <exception cref="ArgumentNullException">ClientContext is Null></exception> public void ExecuteExtensibilityProvisionCallOut(ClientContext ctx, ExtensibilityHandler handler, ProvisioningTemplate template, ProvisioningTemplateApplyingInformation applyingInformation, TokenParser tokenParser, PnPMonitoredScope scope) { var _loggingSource = "OfficeDevPnP.Core.Framework.Provisioning.Extensibility.ExtensibilityManager.ExecuteCallout"; if (ctx == null) { throw new ArgumentNullException(nameof(ctx), CoreResources.Provisioning_Extensibility_Pipeline_ClientCtxNull); } if (string.IsNullOrWhiteSpace(handler.Assembly)) { throw new ArgumentException(String.Format("{0}.{1}", nameof(handler), nameof(handler.Type)), CoreResources.Provisioning_Extensibility_Pipeline_Missing_AssemblyName); } if (string.IsNullOrWhiteSpace(handler.Type)) { throw new ArgumentException(String.Format("{0}.{1}", nameof(handler), nameof(handler.Type)), CoreResources.Provisioning_Extensibility_Pipeline_Missing_TypeName); } try { var _instance = GetProviderInstance(handler); #pragma warning disable 618 if (_instance is IProvisioningExtensibilityProvider) #pragma warning restore 618 { Log.Info(_loggingSource, CoreResources.Provisioning_Extensibility_Pipeline_BeforeInvocation, handler.Assembly, handler.Type); #pragma warning disable 618 (_instance as IProvisioningExtensibilityProvider).ProcessRequest(ctx, template, handler.Configuration); #pragma warning restore 618 Log.Info(_loggingSource, CoreResources.Provisioning_Extensibility_Pipeline_Success, handler.Assembly, handler.Type); } else if (_instance is IProvisioningExtensibilityHandler) { Log.Info(_loggingSource, CoreResources.Provisioning_Extensibility_Pipeline_BeforeInvocation, handler.Assembly, handler.Type); (_instance as IProvisioningExtensibilityHandler).Provision(ctx, template, applyingInformation, tokenParser, scope, handler.Configuration); Log.Info(_loggingSource, CoreResources.Provisioning_Extensibility_Pipeline_Success, handler.Assembly, handler.Type); } else if (_instance != null && !(_instance is IProvisioningExtensibilityTokenProvider)) { throw new ArgumentOutOfRangeException(nameof(handler), string.Format(CoreResources.Provisioning_Extensibility_Invalid_Handler_Implementation, this.GetType().Assembly.GetName().Version.ToString(), handler.Assembly, handler.Type)); } } catch (Exception ex) { Log.Error(_loggingSource, CoreResources.Provisioning_Extensibility_Pipeline_Exception, handler.Assembly, handler.Type, ex); string _message = string.Format( CoreResources.Provisioning_Extensibility_Pipeline_Exception, handler.Assembly, handler.Type, ex); throw new ExtensiblityPipelineException(_message, ex); } }
public static async Task RunAsync([ServiceBusTrigger("actions", IsSessionsEnabled = false)] ProvisioningActionModel action, ILogger logger) { var startProvisioning = DateTime.Now; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; logger.LogInformationWithPnPCorrelation("Processing queue trigger function for tenant {TenantId}", action.CorrelationId, action.TenantId); // Instantiate and use the telemetry model TelemetryUtility telemetry = new TelemetryUtility((s) => { logger.LogInformationWithPnPCorrelation(s, action.CorrelationId); }); Dictionary <string, string> telemetryProperties = new Dictionary <string, string>(); // Configure telemetry properties // telemetryProperties.Add("UserPrincipalName", action.UserPrincipalName); telemetryProperties.Add("TenantId", action.TenantId); telemetryProperties.Add("PnPCorrelationId", action.CorrelationId.ToString()); telemetryProperties.Add("TargetSiteAlreadyExists", action.TargetSiteAlreadyExists.ToString()); telemetryProperties.Add("TargetSiteBaseTemplateId", action.TargetSiteBaseTemplateId); // Get a reference to the data context ProvisioningAppDBContext dbContext = new ProvisioningAppDBContext(); try { // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.Start"); if (CheckIfActionIsAlreadyRunning(action, dbContext)) { throw new ConcurrentProvisioningException("The requested package is currently provisioning in the selected target tenant and cannot be applied in parallel. Please wait for the previous provisioning action to complete."); } var tokenId = $"{action.TenantId}-{action.UserPrincipalName.ToLower().GetHashCode()}-{action.ActionType.ToString().ToLower()}-{provisioningEnvironment}"; // Retrieve the SPO target tenant via Microsoft Graph var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, "https://graph.microsoft.com/", ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target Microsoft Graph Access Token.", action.CorrelationId); if (!String.IsNullOrEmpty(graphAccessToken)) { #region Get current context data (User, SPO Tenant, SPO Access Token) // Get the currently connected user name and email (UPN) var jwtAccessToken = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(graphAccessToken); String delegatedUPN = String.Empty; var upnClaim = jwtAccessToken.Claims.FirstOrDefault(c => c.Type == "upn"); if (upnClaim != null && !String.IsNullOrEmpty(upnClaim.Value)) { delegatedUPN = upnClaim.Value; } String delegatedUserName = String.Empty; var nameClaim = jwtAccessToken.Claims.FirstOrDefault(c => c.Type == "name"); if (nameClaim != null && !String.IsNullOrEmpty(nameClaim.Value)) { delegatedUserName = nameClaim.Value; } // Determine the URL of the root SPO site for the current tenant var rootSiteJson = HttpHelper.MakeGetRequestForString("https://graph.microsoft.com/v1.0/sites/root", graphAccessToken); SharePointSite rootSite = JsonConvert.DeserializeObject <SharePointSite>(rootSiteJson); String spoTenant = rootSite.WebUrl; logger.LogInformationWithPnPCorrelation("Target SharePoint Online Tenant: {SPOTenant}", action.CorrelationId, spoTenant); // Configure telemetry properties telemetryProperties.Add("SPOTenant", spoTenant); // Retrieve the SPO Access Token var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, rootSite.WebUrl, ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target SharePoint Online Access Token.", action.CorrelationId); #endregion // Connect to SPO, create and provision site AuthenticationManager authManager = new AuthenticationManager(); using (ClientContext context = authManager.GetAzureADAccessTokenAuthenticatedContext(spoTenant, spoAccessToken)) { // Telemetry and startup var web = context.Web; context.ClientTag = $"SPDev:ProvisioningPortal-{provisioningEnvironment}"; context.Load(web, w => w.Title, w => w.Id); await context.ExecuteQueryAsync(); // Save the current SPO Correlation ID telemetryProperties.Add("SPOCorrelationId", context.TraceCorrelationId); logger.LogInformationWithPnPCorrelation("SharePoint Online Root Site Collection title: {WebTitle}", action.CorrelationId, web.Title); #region Store the main site URL in KeyVault // Store the main site URL in the Vault var vault = ProvisioningAppManager.SecurityTokensServiceProvider; // Read any existing properties for the current tenantId var properties = await vault.GetAsync(tokenId); if (properties == null) { // If there are no properties, create a new dictionary properties = new Dictionary <String, String>(); } // Set/Update the RefreshToken value properties["SPORootSite"] = spoTenant; // Add or Update the Key Vault accordingly await vault.AddOrUpdateAsync(tokenId, properties); #endregion #region Provision the package var package = dbContext.Packages.FirstOrDefault(p => p.Id == new Guid(action.PackageId)); if (package != null) { // Update the Popularity of the package package.TimesApplied++; dbContext.SaveChanges(); #region Get the Provisioning Hierarchy file // Determine reference path variables var blobConnectionString = ConfigurationManager.AppSettings["BlobTemplatesProvider:ConnectionString"]; var blobContainerName = ConfigurationManager.AppSettings["BlobTemplatesProvider:ContainerName"]; var packageFileName = package.PackageUrl.Substring(package.PackageUrl.LastIndexOf('/') + 1); var packageFileUri = new Uri(package.PackageUrl); var packageFileRelativePath = packageFileUri.AbsolutePath.Substring(2 + blobContainerName.Length); var packageFileRelativeFolder = packageFileRelativePath.Substring(0, packageFileRelativePath.LastIndexOf('/')); // Configure telemetry properties telemetryProperties.Add("PackageFileName", packageFileName); telemetryProperties.Add("PackageFileUri", packageFileUri.ToString()); // Read the main provisioning file from the Blob Storage CloudStorageAccount csa; if (!CloudStorageAccount.TryParse(blobConnectionString, out csa)) { throw new ArgumentException("Cannot create cloud storage account from given connection string."); } CloudBlobClient blobClient = csa.CreateCloudBlobClient(); CloudBlobContainer blobContainer = blobClient.GetContainerReference(blobContainerName); var blockBlob = blobContainer.GetBlockBlobReference(packageFileRelativePath); // Crate an in-memory copy of the source stream MemoryStream mem = new MemoryStream(); await blockBlob.DownloadToStreamAsync(mem); mem.Position = 0; // Prepare the output hierarchy ProvisioningHierarchy hierarchy = null; if (packageFileName.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { // That's an XML Provisioning Template file XDocument xml = XDocument.Load(mem); mem.Position = 0; // Deserialize the stream into a provisioning hierarchy reading any // dependecy with the Azure Blob Storage connector var formatter = XMLPnPSchemaFormatter.GetSpecificFormatter(xml.Root.Name.NamespaceName); var templateLocalFolder = $"{blobContainerName}/{packageFileRelativeFolder}"; var provider = new XMLAzureStorageTemplateProvider( blobConnectionString, templateLocalFolder); formatter.Initialize(provider); // Get the full hierarchy hierarchy = ((IProvisioningHierarchyFormatter)formatter).ToProvisioningHierarchy(mem); hierarchy.Connector = provider.Connector; } else if (packageFileName.EndsWith(".pnp", StringComparison.InvariantCultureIgnoreCase)) { // That's a PnP Package file // Get a provider based on the in-memory .PNP Open XML file OpenXMLConnector openXmlConnector = new OpenXMLConnector(mem); XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( openXmlConnector); // Get the .xml provisioning template file name var xmlTemplateFileName = openXmlConnector.Info?.Properties?.TemplateFileName ?? packageFileName.Substring(packageFileName.LastIndexOf('/') + 1) .ToLower().Replace(".pnp", ".xml"); // Get the full hierarchy hierarchy = provider.GetHierarchy(xmlTemplateFileName); hierarchy.Connector = provider.Connector; } #endregion #region Apply the template // Prepare variable to collect provisioned sites var provisionedSites = new List <Tuple <String, String> >(); // If we have a hierarchy with at least one Sequence if (hierarchy != null) // && hierarchy.Sequences != null && hierarchy.Sequences.Count > 0) { Console.WriteLine($"Provisioning hierarchy \"{hierarchy.DisplayName}\""); var tenantUrl = UrlUtilities.GetTenantAdministrationUrl(context.Url); // Retrieve the SPO Access Token var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, tenantUrl, ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); logger.LogInformationWithPnPCorrelation("Retrieved target SharePoint Online Admin Center Access Token.", action.CorrelationId); using (var tenantContext = authManager.GetAzureADAccessTokenAuthenticatedContext(tenantUrl, spoAdminAccessToken)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { var tenant = new Microsoft.Online.SharePoint.TenantAdministration.Tenant(pnpTenantContext); // Prepare a dictionary to hold the access tokens var accessTokens = new Dictionary <String, String>(); // Prepare logging for hierarchy application var ptai = new ProvisioningTemplateApplyingInformation(); ptai.MessagesDelegate += delegate(string message, ProvisioningMessageType messageType) { logger.LogInformationWithPnPCorrelation($"{messageType} - {message.Replace("{", "{{").Replace("}", "}}")}", action.CorrelationId); }; ptai.ProgressDelegate += delegate(string message, int step, int total) { logger.LogInformationWithPnPCorrelation($"{step:00}/{total:00} - {message.Replace("{", "{{").Replace("}", "}}")}", action.CorrelationId); }; ptai.SiteProvisionedDelegate += delegate(string title, string url) { logger.LogInformationWithPnPCorrelation("Fully provisioned site '{SiteTitle}' with URL: {SiteUrl}", action.CorrelationId, title, url); var provisionedSite = new Tuple <string, string>(title, url); if (!provisionedSites.Contains(provisionedSite)) { provisionedSites.Add(provisionedSite); } }; // Configure the OAuth Access Tokens for the client context accessTokens.Add(new Uri(tenantUrl).Authority, spoAdminAccessToken); accessTokens.Add(new Uri(spoTenant).Authority, spoAccessToken); // Configure the OAuth Access Tokens for the PnPClientContext, too pnpTenantContext.PropertyBag["AccessTokens"] = accessTokens; ptai.AccessTokens = accessTokens; #region Theme handling // Process the graphical Theme if (action.ApplyTheme) { // If we don't have any custom Theme if (!action.ApplyCustomTheme) { // Associate the selected already existing Theme to all the sites of the hierarchy foreach (var sc in hierarchy.Sequences[0].SiteCollections) { sc.Theme = action.SelectedTheme; foreach (var s in sc.Sites) { UpdateChildrenSitesTheme(s, action.SelectedTheme); } } } } #endregion // Configure provisioning parameters if (action.PackageProperties != null) { foreach (var key in action.PackageProperties.Keys) { if (hierarchy.Parameters.ContainsKey(key.ToString())) { hierarchy.Parameters[key.ToString()] = action.PackageProperties[key].ToString(); } else { hierarchy.Parameters.Add(key.ToString(), action.PackageProperties[key].ToString()); } // Configure telemetry properties telemetryProperties.Add($"PackageProperty.{key}", action.PackageProperties[key].ToString()); } } // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.BeginProvisioning", telemetryProperties); // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.ContainsKey(r)) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour return(await Task.FromResult(accessTokens[r])); } else { // Try to get a fresh new Access Token var token = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, $"https://{r}", ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"], ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"], ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]); accessTokens.Add(r, token); return(token); } })) { // Configure the webhooks, if any if (action.Webhooks != null && action.Webhooks.Count > 0) { foreach (var t in hierarchy.Templates) { foreach (var wh in action.Webhooks) { AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ProvisioningTemplateStarted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ObjectHandlerProvisioningStarted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ObjectHandlerProvisioningCompleted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ProvisioningTemplateCompleted); AddProvisioningTemplateWebhook(t, wh, ProvisioningTemplateWebhookKind.ExceptionOccurred); } } foreach (var wh in action.Webhooks) { AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningStarted); AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningCompleted); AddProvisioningWebhook(hierarchy, wh, ProvisioningTemplateWebhookKind.ProvisioningExceptionOccurred); } } // Disable the WebSettings handler for non-admin users if (!TenantExtensions.IsCurrentUserTenantAdmin(tenantContext)) { ptai.HandlersToProcess &= ~Handlers.WebSettings; } // Apply the hierarchy logger.LogInformationWithPnPCorrelation("Hierarchy Provisioning Started: {ProvisioningStartDateTime}", action.CorrelationId, DateTime.Now.ToString("hh.mm.ss")); tenant.ApplyProvisionHierarchy(hierarchy, (hierarchy.Sequences != null && hierarchy.Sequences.Count > 0) ? hierarchy.Sequences[0].ID : null, ptai); logger.LogInformationWithPnPCorrelation("Hierarchy Provisioning Completed: {ProvisioningEndDateTime}", action.CorrelationId, DateTime.Now.ToString("hh.mm.ss")); } if (action.ApplyTheme && action.ApplyCustomTheme) { if (!String.IsNullOrEmpty(action.ThemePrimaryColor) && !String.IsNullOrEmpty(action.ThemeBodyTextColor) && !String.IsNullOrEmpty(action.ThemeBodyBackgroundColor)) { logger.LogInformationWithPnPCorrelation("Applying custom Theme to provisioned sites", action.CorrelationId); #region Palette generation for Theme var jsonPalette = ThemeUtility.GetThemeAsJSON( action.ThemePrimaryColor, action.ThemeBodyTextColor, action.ThemeBodyBackgroundColor); #endregion // Apply the custom theme to all of the provisioned sites foreach (var ps in provisionedSites) { using (var provisionedSiteContext = authManager.GetAzureADAccessTokenAuthenticatedContext(ps.Item2, spoAccessToken)) { if (provisionedSiteContext.Web.ApplyTheme(jsonPalette)) { logger.LogInformationWithPnPCorrelation($"Custom Theme applied on site '{ps.Item1}' with URL: {ps.Item2}", action.CorrelationId); } else { logger.LogInformationWithPnPCorrelation($"Failed to apply custom Theme on site '{ps.Item1}' with URL: {ps.Item2}", action.CorrelationId); } } } } } // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.EndProvisioning", telemetryProperties); // Notify user about the provisioning outcome if (!String.IsNullOrEmpty(action.NotificationEmail)) { var appOnlyAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAppOnlyAccessTokenAsync( "https://graph.microsoft.com/", ConfigurationManager.AppSettings["OfficeDevPnP:TenantId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientSecret"], ConfigurationManager.AppSettings["OfficeDevPnP:AppUrl"]); MailHandler.SendMailNotification( "ProvisioningCompleted", action.NotificationEmail, null, new { TemplateName = action.DisplayName, ProvisionedSites = provisionedSites, }, appOnlyAccessToken); } // Log reporting event (1 = Success) LogReporting(action, provisioningEnvironment, startProvisioning, package, 1); // Log source tracking for provisioned sites LogSourceTrackingProvisionedSites(action, spoAccessToken, authManager, provisionedSites); } } } else { throw new ApplicationException($"The requested package does not contain a valid PnP Hierarchy!"); } #endregion } else { throw new ApplicationException($"Cannot find the package with ID: {action.PackageId}"); } #endregion #region Process any children items // If there are children items if (action.ChildrenItems != null && action.ChildrenItems.Count > 0) { // Prepare any further child provisioning request action.PackageId = action.ChildrenItems[0].PackageId; action.PackageProperties = action.ChildrenItems[0].Parameters; action.ChildrenItems.RemoveAt(0); // Enqueue any further child provisioning request await ProvisioningAppManager.EnqueueProvisioningRequest(action); } #endregion logger.LogInformationWithPnPCorrelation("Function successfully executed!", action.CorrelationId); // Log telemetry event telemetry?.LogEvent("ProvisioningFunction.End", telemetryProperties); } } else { var noTokensErrorMessage = $"Cannot retrieve Refresh Token or Access Token for action {action.CorrelationId} in tenant {action.TenantId}!"; logger.LogInformationWithPnPCorrelation(noTokensErrorMessage, action.CorrelationId); throw new ApplicationException(noTokensErrorMessage); } } catch (Exception ex) { // Skip logging exception for Recycled Site if (ex is RecycledSiteException) { // Log reporting event (3 = RecycledSite) LogReporting(action, provisioningEnvironment, startProvisioning, null, 3, ex.ToDetailedString()); // rather log an event telemetry?.LogEvent("ProvisioningFunction.RecycledSite", telemetryProperties); } // Skip logging exception for Concurrent Provisioning else if (ex is ConcurrentProvisioningException) { // Log reporting event (4 = ConcurrentProvisioningException) LogReporting(action, provisioningEnvironment, startProvisioning, null, 4, ex.ToDetailedString()); // rather log an event telemetry?.LogEvent("ProvisioningFunction.ConcurrentProvisioning", telemetryProperties); } else { // Log reporting event (2 = Failed) LogReporting(action, provisioningEnvironment, startProvisioning, null, 2, ex.ToDetailedString()); // Log telemetry event telemetry?.LogException(ex, "ProvisioningFunction.RunAsync", telemetryProperties); } if (!String.IsNullOrEmpty(action.NotificationEmail)) { var appOnlyAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAppOnlyAccessTokenAsync( "https://graph.microsoft.com/", ConfigurationManager.AppSettings["OfficeDevPnP:TenantId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientId"], ConfigurationManager.AppSettings["OfficeDevPnP:ClientSecret"], ConfigurationManager.AppSettings["OfficeDevPnP:AppUrl"]); // Notify user about the provisioning outcome MailHandler.SendMailNotification( "ProvisioningFailed", action.NotificationEmail, null, new { TemplateName = action.DisplayName, ExceptionDetails = SimplifyException(ex), PnPCorrelationId = action.CorrelationId.ToString(), }, appOnlyAccessToken); } ProcessWebhooksExceptionNotification(action, ex); // Track the failure in the local action log MarkCurrentActionItemAsFailed(action, dbContext); throw ex; } finally { // Try to cleanup the pending action item, if any CleanupCurrentActionItem(action, dbContext); telemetry?.Flush(); } }
public override CanProvisionResult CanProvision(Web web, ProvisioningTemplate template, ProvisioningTemplateApplyingInformation applyingInformation) { // Prepare the default output var result = new CanProvisionResult(); Model.ProvisioningTemplate targetTemplate = null; if (template.ParentHierarchy != null) { // If we have a hierarchy, search for a template with Taxonomy settings, if any targetTemplate = template.ParentHierarchy.Templates.FirstOrDefault(t => t.TermGroups.Count > 0); if (targetTemplate == null) { // or use the first in the hierarchy targetTemplate = template.ParentHierarchy.Templates[0]; } } else { // Otherwise, use the provided template targetTemplate = template; } // Verify if we need the Term Store permissions (i.e. the template contains term groups to provision, or sequences with TermStore settings) if ((targetTemplate.TermGroups != null && targetTemplate.TermGroups?.Count > 0) || targetTemplate.ParentHierarchy.Sequences.Any( s => s.TermStore?.TermGroups != null && s.TermStore?.TermGroups?.Count > 0)) { using (var scope = new PnPMonitoredScope(this.Name)) { try { // Try to access the Term Store TaxonomySession taxSession = TaxonomySession.GetTaxonomySession(web.Context); TermStore termStore = taxSession.GetDefaultKeywordsTermStore(); web.Context.Load(termStore, ts => ts.Languages, ts => ts.DefaultLanguage, ts => ts.Groups.Include( tg => tg.Name, tg => tg.Id, tg => tg.TermSets.Include( tset => tset.Name, tset => tset.Id))); var siteCollectionTermGroup = termStore.GetSiteCollectionGroup((web.Context as ClientContext).Site, false); web.Context.Load(siteCollectionTermGroup); web.Context.ExecuteQueryRetry(); var termGroupId = Guid.NewGuid(); var group = termStore.CreateGroup($"Temp-{termGroupId.ToString()}", termGroupId); termStore.CommitAll(); web.Context.Load(group); web.Context.ExecuteQueryRetry(); // Now delete the just created termGroup, to cleanup the Term Store group.DeleteObject(); web.Context.ExecuteQueryRetry(); } catch (Exception ex) { // And if we fail, raise a CanProvisionIssue result.CanProvision = false; result.Issues.Add(new CanProvisionIssue() { Source = this.Name, Tag = CanProvisionIssueTags.MISSING_TERMSTORE_PERMISSIONS, Message = CanProvisionIssuesMessages.Term_Store_Not_Admin, ExceptionMessage = ex.Message, // Here we have a specific exception ExceptionStackTrace = ex.StackTrace, // Here we have a specific exception }); } } } return(result); }
/// <summary> /// Can be used to apply custom remote provisioning template on top of existing site. /// </summary> /// <param name="web"></param> /// <param name="template">ProvisioningTemplate with the settings to be applied</param> /// <param name="applyingInformation">Specified additional settings and or properties</param> public static void ApplyProvisioningTemplate(this Web web, ProvisioningTemplate template, ProvisioningTemplateApplyingInformation applyingInformation = null) { // Call actual handler new SiteToTemplateConversion().ApplyRemoteTemplate(web, template, applyingInformation); }