public static string AcquireTokenAsync(string resource, string scope = null) { var tenantId = TenantExtensions.GetTenantIdByUrl(TestCommon.AppSetting("SPOTenantUrl")); //var tenantId = GetTenantIdByUrl(TestCommon.AppSetting("SPOTenantUrl")); if (tenantId == null) { return(null); } var clientId = TestCommon.AppSetting("AppId"); var username = UserName; var password = EncryptionUtility.ToInsecureString(Password); string body; string response; if (scope == null) // use v1 endpoint { body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); } else // use v2 endpoint { body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&scope={scope}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", body, "application/x-www-form-urlencoded"); } var json = JToken.Parse(response); return(json["access_token"].ToString()); }
internal static string AcquireToken(string resource, string scope = null) { var tenantId = TenantExtensions.GetTenantIdByUrl(SPOnlineConnection.CurrentConnection.Url); if (tenantId == null) { return(null); } var clientId = "31359c7f-bd7e-475c-86db-fdb8c937548e"; var username = SPOnlineConnection.CurrentConnection.PSCredential.UserName; var password = EncryptionUtility.ToInsecureString(SPOnlineConnection.CurrentConnection.PSCredential.Password); var body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; var response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); try { var json = JToken.Parse(response); return(json["access_token"].ToString()); } catch { return(null); } }
/// <inheritdoc/> public async Task DeleteTenantAsync(string tenantId) { string parentTenantId = TenantExtensions.GetRequiredParentId(tenantId); (_, BlobContainerClient parentContainer) = await this.GetContainerAndTenantForChildTenantsOfAsync(parentTenantId).ConfigureAwait(false); (_, BlobContainerClient tenantContainer) = await this.GetContainerAndTenantForChildTenantsOfAsync(tenantId).ConfigureAwait(false); // Check it's not empty first. AsyncPageable <BlobItem> pageable = tenantContainer.GetBlobsAsync( prefix: LiveTenantsPrefix); IAsyncEnumerable <Page <BlobItem> > pages = pageable.AsPages(pageSizeHint: 1); await using IAsyncEnumerator <Page <BlobItem> > page = pages.GetAsyncEnumerator(); if (await page.MoveNextAsync() && page.Current.Values.Count > 0) { throw new ArgumentException($"Cannot delete tenant {tenantId} because it has children"); } BlockBlobClient blob = GetLiveTenantBlockBlobReference(tenantId, parentContainer); Response <BlobDownloadResult> response = await blob.DownloadContentAsync().ConfigureAwait(false); using var blobContent = response.Value.Content.ToStream(); BlockBlobClient?deletedBlob = parentContainer.GetBlockBlobClient(DeletedTenantsPrefix + tenantId); await deletedBlob.UploadAsync(blobContent).ConfigureAwait(false); await blob.DeleteIfExistsAsync().ConfigureAwait(false); await tenantContainer.DeleteAsync().ConfigureAwait(false); }
protected override void ProcessRecord() { try { if (string.IsNullOrEmpty(TenantUrl) && PnPConnection.CurrentConnection != null) { WriteObject(TenantExtensions.GetTenantIdByUrl(PnPConnection.CurrentConnection.Url)); } else if (!string.IsNullOrEmpty(TenantUrl)) { WriteObject(TenantExtensions.GetTenantIdByUrl(TenantUrl)); } else { throw new InvalidOperationException("Either a connection needs to be made by Connect-PnPOnline or TenantUrl needs to be specified"); } } catch (Exception ex) { if (ex.InnerException != null) { if (ex.InnerException is HttpRequestException) { var message = ex.InnerException.Message; using (var jdoc = JsonDocument.Parse(message)) { var errorDescription = jdoc.RootElement.GetProperty("error_description").GetString(); WriteObject(errorDescription); } } } throw; } }
public void ThenGetTenantShouldHaveTheTenantId(string detailsToInspect, string wellKnownGuidLabel) { Guid wellKnownGuid = this.WellKnownGuids[wellKnownGuidLabel]; string expectedId = TenantExtensions.EncodeGuid(wellKnownGuid); ITenant tenantToInspect = this.Tenants[detailsToInspect]; tenantToInspect.Id.Should().Be(expectedId); }
public void ThenTheTenantDetailsLabelledShouldHaveTenantIdThatIsTheConcatenatedHashesOfTheGuidsLabelledAnd( string detailsToInspect, string wellKnownGuidLabel1, string wellKnownGuidLabel2) { Guid wellKnownGuid1 = this.WellKnownGuids[wellKnownGuidLabel1]; Guid wellKnownGuid2 = this.WellKnownGuids[wellKnownGuidLabel2]; string expectedId = TenantExtensions.EncodeGuid(wellKnownGuid1) + TenantExtensions.EncodeGuid(wellKnownGuid2); ITenant tenantToInspect = this.Tenants[detailsToInspect]; tenantToInspect.Id.Should().Be(expectedId); }
public static string AcquireTokenAsync(string resource, string scope = null) { var tenantId = TenantExtensions.GetTenantIdByUrl(TestCommon.AppSetting("SPOTenantUrl")); //var tenantId = GetTenantIdByUrl(TestCommon.AppSetting("SPOTenantUrl")); if (tenantId == null) { return(null); } var clientId = TestCommon.AppSetting("AzureADClientId"); if (string.IsNullOrEmpty(clientId) || Password == null || string.IsNullOrEmpty(UserName)) { return(null); } var username = UserName; var password = EncryptionUtility.ToInsecureString(Password); string body; string response; if (scope == null) // use v1 endpoint { body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; // TODO: If your app is a public client, then the client_secret or client_assertion cannot be included. If the app is a confidential client, then it must be included. // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc //body = $"grant_type=password&client_id={clientId}&client_secret={clientSecret}&username={username}&password={password}&resource={resource}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); } else // use v2 endpoint { body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&scope={scope}"; // TODO: If your app is a public client, then the client_secret or client_assertion cannot be included. If the app is a confidential client, then it must be included. // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc //body = $"grant_type=password&client_id={clientId}&client_secret={clientSecret}&username={username}&password={password}&scope={scope}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", body, "application/x-www-form-urlencoded"); } var json = JToken.Parse(response); return(json["access_token"].ToString()); }
internal static string AcquireToken(string resource, string scope = null) { if (PnPConnection.CurrentConnection == null) { return(null); } var tenantId = TenantExtensions.GetTenantIdByUrl(PnPConnection.CurrentConnection.Url); if (tenantId == null) { return(null); } string body = ""; if (PnPConnection.CurrentConnection.PSCredential != null) { var clientId = "31359c7f-bd7e-475c-86db-fdb8c937548e"; var username = PnPConnection.CurrentConnection.PSCredential.UserName; var password = EncryptionUtility.ToInsecureString(PnPConnection.CurrentConnection.PSCredential.Password); body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; } else if (!string.IsNullOrEmpty(PnPConnection.CurrentConnection.ClientId) && !string.IsNullOrEmpty(PnPConnection.CurrentConnection.ClientSecret)) { var clientId = PnPConnection.CurrentConnection.ClientId; var clientSecret = HttpUtility.UrlEncode(PnPConnection.CurrentConnection.ClientSecret); body = $"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource={resource}"; } else { throw new System.UnauthorizedAccessException("Specify PowerShell Credentials or AppId and AppSecret"); } var response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); try { var json = JToken.Parse(response); return(json["access_token"].ToString()); } catch { return(null); } }
internal static string AcquireToken(string resource, string scope = null) { GenericToken token = null; if (PnPConnection.CurrentConnection == null) { return(null); } var tenantId = TenantExtensions.GetTenantIdByUrl(PnPConnection.CurrentConnection.Url); if (PnPConnection.CurrentConnection.PSCredential != null) { if (scope == null) { // SharePoint or Graph V1 resource var scopes = new[] { $"https://{resource}//.default" }; token = GenericToken.AcquireDelegatedTokenWithCredentials(PnPConnection.PnPManagementShellClientId, scopes, "https://login.microsoftonline.com/organizations/", PnPConnection.CurrentConnection.PSCredential.UserName, PnPConnection.CurrentConnection.PSCredential.Password); } else { token = GenericToken.AcquireDelegatedTokenWithCredentials(PnPConnection.PnPManagementShellClientId, new[] { scope }, "https://login.microsoftonline.com/organizations/", PnPConnection.CurrentConnection.PSCredential.UserName, PnPConnection.CurrentConnection.PSCredential.Password); } } else if (!string.IsNullOrEmpty(PnPConnection.CurrentConnection.ClientId) && !string.IsNullOrEmpty(PnPConnection.CurrentConnection.ClientSecret)) { var clientId = PnPConnection.CurrentConnection.ClientId; var clientSecret = HttpUtility.UrlEncode(PnPConnection.CurrentConnection.ClientSecret); if (scope == null && !resource.Equals("graph.microsoft.com", System.StringComparison.OrdinalIgnoreCase)) { // SharePoint token var scopes = new[] { $"https://{resource}//.default" }; token = GenericToken.AcquireApplicationToken(tenantId, clientId, "https://login.microsoftonline/organizations/", scopes, clientSecret); } else { token = GenericToken.AcquireApplicationToken(tenantId, clientId, "https://login.microsoftonline.com/organizations/", new[] { scope }, clientSecret); } } if (token != null) { return(token.AccessToken); } return(null); }
protected override void ProcessRecord() { try { if (string.IsNullOrEmpty(TenantUrl) && SPOnlineConnection.CurrentConnection != null) { WriteObject(TenantExtensions.GetTenantIdByUrl(SPOnlineConnection.CurrentConnection.Url)); } else if (!string.IsNullOrEmpty(TenantUrl)) { WriteObject(TenantExtensions.GetTenantIdByUrl(TenantUrl)); } else { throw new InvalidOperationException("Either a connection needs to be made by Connect-PnPOnline or TenantUrl needs to be specified"); } } catch (Exception ex) { #if !NETSTANDARD2_1 if (ex.InnerException != null) { if (ex.InnerException is HttpException) { var message = ex.InnerException.Message; var obj = JObject.Parse(message); WriteObject(obj["error_description"].ToString()); } else { throw ex; } } else { throw ex; } #else throw ex; #endif } }
public override ProvisioningTemplate ExtractObjects(Web web, ProvisioningTemplate template, ProvisioningTemplateCreationInformation creationInfo) { using (var scope = new PnPMonitoredScope(this.Name)) { web.EnsureProperties( #if !SP2013 && !SP2016 w => w.NoCrawl, w => w.CommentsOnSitePagesDisabled, w => w.ExcludeFromOfflineClient, w => w.MembersCanShare, w => w.DisableFlows, w => w.DisableAppViews, w => w.HorizontalQuickLaunch, w => w.QuickLaunchEnabled, #if !SP2019 w => w.SearchScope, w => w.SearchBoxInNavBar, #endif #endif //w => w.Title, //w => w.Description, w => w.MasterUrl, w => w.CustomMasterUrl, w => w.SiteLogoUrl, w => w.RequestAccessEmail, w => w.RootFolder, w => w.AlternateCssUrl, w => w.ServerRelativeUrl, w => w.Url ); var webSettings = new WebSettings(); #if !SP2013 && !SP2016 webSettings.NoCrawl = web.NoCrawl; webSettings.CommentsOnSitePagesDisabled = web.CommentsOnSitePagesDisabled; webSettings.ExcludeFromOfflineClient = web.ExcludeFromOfflineClient; webSettings.MembersCanShare = web.MembersCanShare; webSettings.DisableFlows = web.DisableFlows; webSettings.DisableAppViews = web.DisableAppViews; webSettings.HorizontalQuickLaunch = web.HorizontalQuickLaunch; webSettings.QuickLaunchEnabled = web.QuickLaunchEnabled; #if !SP2019 webSettings.SearchScope = (SearchScopes)Enum.Parse(typeof(SearchScopes), web.SearchScope.ToString(), true); webSettings.SearchBoxInNavBar = (SearchBoxInNavBar)Enum.Parse(typeof(SearchBoxInNavBar), web.SearchBoxInNavBar.ToString(), true); webSettings.SearchCenterUrl = web.GetWebSearchCenterUrl(true); #endif #endif // We're not extracting Title and Description //webSettings.Title = Tokenize(web.Title, web.Url); //webSettings.Description = Tokenize(web.Description, web.Url); webSettings.MasterPageUrl = Tokenize(web.MasterUrl, web.Url); webSettings.CustomMasterPageUrl = Tokenize(web.CustomMasterUrl, web.Url); webSettings.SiteLogo = TokenizeHost(web, Tokenize(web.SiteLogoUrl, web.Url)); // Notice. No tokenization needed for the welcome page, it's always relative for the site webSettings.WelcomePage = web.RootFolder.WelcomePage; webSettings.AlternateCSS = Tokenize(web.AlternateCssUrl, web.Url); webSettings.RequestAccessEmail = web.RequestAccessEmail; #if !ONPREMISES // Can we get the hubsite url? This requires Tenant Admin rights try { var site = ((ClientContext)web.Context).Site; site.EnsureProperties(s => s.HubSiteId, s => s.Id); if (site.HubSiteId != Guid.Empty && site.HubSiteId != site.Id) { if (TenantExtensions.IsCurrentUserTenantAdmin(web.Context as ClientContext)) { using (var tenantContext = web.Context.Clone((web.Context as ClientContext).Web.GetTenantAdministrationUrl())) { var tenant = new Tenant(tenantContext); var hubsiteProperties = tenant.GetHubSitePropertiesById(site.HubSiteId); tenantContext.Load(hubsiteProperties); tenantContext.ExecuteQueryRetry(); webSettings.HubSiteUrl = hubsiteProperties.SiteUrl; } } else { WriteMessage("You need to be a SharePoint admin to extract Hub site Url.", ProvisioningMessageType.Warning); } } } catch { } #endif if (creationInfo.PersistBrandingFiles) { if (!string.IsNullOrEmpty(web.MasterUrl)) { var masterUrl = web.MasterUrl.ToLower(); if (!masterUrl.EndsWith("default.master") && !masterUrl.EndsWith("custom.master") && !masterUrl.EndsWith("v4.master") && !masterUrl.EndsWith("seattle.master") && !masterUrl.EndsWith("oslo.master")) { if (PersistFile(web, creationInfo, scope, web.MasterUrl)) { template.Files.Add(GetTemplateFile(web, web.MasterUrl)); } } } if (!string.IsNullOrEmpty(web.CustomMasterUrl)) { var customMasterUrl = web.CustomMasterUrl.ToLower(); if (!customMasterUrl.EndsWith("default.master") && !customMasterUrl.EndsWith("custom.master") && !customMasterUrl.EndsWith("v4.master") && !customMasterUrl.EndsWith("seattle.master") && !customMasterUrl.EndsWith("oslo.master")) { if (PersistFile(web, creationInfo, scope, web.CustomMasterUrl)) { template.Files.Add(GetTemplateFile(web, web.CustomMasterUrl)); } } } // Extract site logo if property has been set and it's not dynamic image from _api URL if (!string.IsNullOrEmpty(web.SiteLogoUrl) && (!web.SiteLogoUrl.ToLowerInvariant().Contains("_api/"))) { // Convert to server relative URL if needed (web.SiteLogoUrl can be set to FQDN URL of a file hosted in the site (e.g. for communication sites)) Uri webUri = new Uri(web.Url); string webUrl = $"{webUri.Scheme}://{webUri.DnsSafeHost}"; string siteLogoServerRelativeUrl = web.SiteLogoUrl.Replace(webUrl, ""); if (PersistFile(web, creationInfo, scope, siteLogoServerRelativeUrl)) { template.Files.Add(GetTemplateFile(web, HttpUtility.UrlDecode(siteLogoServerRelativeUrl))); } } if (!string.IsNullOrEmpty(web.AlternateCssUrl)) { if (PersistFile(web, creationInfo, scope, web.AlternateCssUrl)) { template.Files.Add(GetTemplateFile(web, web.AlternateCssUrl)); } } var files = template.Files.Distinct().ToList(); template.Files.Clear(); template.Files.AddRange(files); } if (!creationInfo.PersistBrandingFiles) { if (creationInfo.BaseTemplate != null) { if (!webSettings.Equals(creationInfo.BaseTemplate.WebSettings)) { template.WebSettings = webSettings; } } else { template.WebSettings = webSettings; } } else { template.WebSettings = webSettings; } } return(template); }
public async Task WhenIAttemptToDeleteATenantWithTheIdThatLooksLikeAChildOfButWhichDoesNotExistAsync(string parentTenantLabel) { ITenant parent = this.Tenants[parentTenantLabel]; await this.AttemptToDelete(TenantExtensions.CreateChildId(parent.Id, Guid.NewGuid())); }
public async Task WhenIAttemptToDeleteATenantWithTheIdThatLooksLikeAChildOfAChildOfTheRootTenantWhereNeitherExistsAsync() { string nonExistentParent = TenantExtensions.CreateChildId(RootTenant.RootTenantId, Guid.NewGuid()); await this.AttemptToDelete(TenantExtensions.CreateChildId(nonExistentParent, Guid.NewGuid())); }
public async Task WhenIAttemptToDeleteATenantWithTheIdThatLooksLikeAChildOfTheRootTenantButWhichDoesNotExist() { await this.AttemptToDelete(TenantExtensions.CreateChildId(RootTenant.RootTenantId, Guid.NewGuid())); }
public override CanProvisionResult CanProvision(Web web, ProvisioningTemplate template, ProvisioningTemplateApplyingInformation applyingInformation) { // Prepare the default output var result = new CanProvisionResult(); #if !ONPREMISES Model.ProvisioningTemplate targetTemplate = null; if (template.ParentHierarchy != null) { // If we have a hierarchy, search for a template with ALM settings, if any targetTemplate = template.ParentHierarchy.Templates.FirstOrDefault(t => t.ApplicationLifecycleManagement.Apps.Count > 0 || (t.ApplicationLifecycleManagement.AppCatalog != null && t.ApplicationLifecycleManagement.AppCatalog.Packages.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 App Catalog (i.e. the template contains apps or packages) if ((targetTemplate.ApplicationLifecycleManagement?.Apps != null && targetTemplate.ApplicationLifecycleManagement?.Apps?.Count > 0) || (targetTemplate.ApplicationLifecycleManagement?.AppCatalog != null && targetTemplate.ApplicationLifecycleManagement?.AppCatalog?.Packages != null && targetTemplate.ApplicationLifecycleManagement?.AppCatalog?.Packages.Count > 0) || (targetTemplate.ParentHierarchy != null && targetTemplate.ParentHierarchy?.Tenant?.AppCatalog != null && targetTemplate.ParentHierarchy?.Tenant?.AppCatalog?.Packages != null && targetTemplate.ParentHierarchy?.Tenant?.AppCatalog?.Packages.Count > 0)) { // First of all check if the currently connected user is a Tenant Admin if (!TenantExtensions.IsCurrentUserTenantAdmin(web.Context as ClientContext)) { result.CanProvision = false; result.Issues.Add(new CanProvisionIssue() { Source = this.Name, Tag = CanProvisionIssueTags.USER_IS_NOT_TENANT_ADMIN, Message = CanProvisionIssuesMessages.User_Is_Not_Tenant_Admin, ExceptionMessage = null, // Here we don't have any specific exception ExceptionStackTrace = null, // Here we don't have any specific exception }); } using (var scope = new PnPMonitoredScope(this.Name)) { // Try to access the AppCatalog var appCatalogUri = web.GetAppCatalog(); if (appCatalogUri == null) { // And if we fail, raise a CanProvisionIssue result.CanProvision = false; result.Issues.Add(new CanProvisionIssue() { Source = this.Name, Tag = CanProvisionIssueTags.MISSING_APP_CATALOG, Message = CanProvisionIssuesMessages.Missing_App_Catalog, ExceptionMessage = null, // Here we don't have any specific exception ExceptionStackTrace = null, // Here we don't have any specific exception }); } else { // Try to access the AppCatalog with the current user try { using (var appCatalogContext = web.Context.Clone(appCatalogUri)) { // Get a reference to the "Apps for SharePoint" library var appCatalogLibrary = appCatalogContext.Web.GetListByUrl("AppCatalog"); // Check its permissions appCatalogContext.Web.CurrentUser.EnsureProperty(u => u.LoginName); var userEffectivePermissions = appCatalogLibrary.GetUserEffectivePermissions( appCatalogContext.Web.CurrentUser.LoginName); appCatalogContext.ExecuteQueryRetry(); if (!userEffectivePermissions.Value.Has(PermissionKind.EditListItems)) { throw new SecurityException("Invalid user's permissions for the AppCatalog"); } // we seem to have access, but is it done fully provisioning? var rootFolder = appCatalogContext.Web.EnsureProperty(w => w.RootFolder); var timeCreated = rootFolder.TimeCreated; if (DateTime.UtcNow.Subtract(timeCreated).Hours < 2) { result.CanProvision = false; result.Issues.Add(new CanProvisionIssue() { Source = this.Name, Tag = CanProvisionIssueTags.APP_CATALOG_NOT_YEY_FULLY_PROVISIONED, Message = CanProvisionIssuesMessages.App_Catalog_Not_Yet_Fully_Provisioned, ExceptionMessage = null, // Here we don't have any specific exception ExceptionStackTrace = null, // Here we don't have any specific exception }); } } } catch (Exception ex) { // And if we fail, raise a CanProvisionIssue result.CanProvision = false; result.Issues.Add(new CanProvisionIssue() { Source = this.Name, Tag = CanProvisionIssueTags.MISSING_APP_CATALOG_PERMISSIONS, Message = CanProvisionIssuesMessages.Missing_Permissions_for_App_Catalog, ExceptionMessage = ex.Message, ExceptionStackTrace = ex.StackTrace, }); } } } } #else result.CanProvision = false; #endif return(result); }
public override TokenParser ProvisionObjects(Web web, ProvisioningTemplate template, TokenParser parser, ProvisioningTemplateApplyingInformation applyingInformation) { using (var scope = new PnPMonitoredScope(this.Name)) { if (template.WebSettings != null) { // Check if this is not a noscript site as we're not allowed to update some properties bool isNoScriptSite = web.IsNoScriptSite(); web.EnsureProperties( #if !SP2013 && !SP2016 w => w.NoCrawl, w => w.CommentsOnSitePagesDisabled, w => w.ExcludeFromOfflineClient, w => w.MembersCanShare, w => w.DisableFlows, w => w.DisableAppViews, w => w.HorizontalQuickLaunch, #if !SP2019 w => w.SearchScope, w => w.SearchBoxInNavBar, #endif #endif w => w.RootFolder, w => w.Title, w => w.Description, w => w.AlternateCssUrl, w => w.WebTemplate, w => w.HasUniqueRoleAssignments); var webSettings = template.WebSettings; // Since the IsSubSite function can trigger an executequery ensure it's called before any updates to the web object are done. if (!web.IsSubSite() || (web.IsSubSite() && web.HasUniqueRoleAssignments)) { String requestAccessEmailValue = parser.ParseString(webSettings.RequestAccessEmail); if (!String.IsNullOrEmpty(requestAccessEmailValue) && requestAccessEmailValue.Length >= 255) { requestAccessEmailValue = requestAccessEmailValue.Substring(0, 255); } if (!String.IsNullOrEmpty(requestAccessEmailValue)) { web.RequestAccessEmail = requestAccessEmailValue; web.Update(); web.Context.ExecuteQueryRetry(); } } #if !SP2013 && !SP2016 if (!isNoScriptSite) { web.NoCrawl = webSettings.NoCrawl; } else { scope.LogWarning(CoreResources.Provisioning_ObjectHandlers_WebSettings_SkipNoCrawlUpdate); } if (web.CommentsOnSitePagesDisabled != webSettings.CommentsOnSitePagesDisabled) { web.CommentsOnSitePagesDisabled = webSettings.CommentsOnSitePagesDisabled; } if (web.ExcludeFromOfflineClient != webSettings.ExcludeFromOfflineClient) { web.ExcludeFromOfflineClient = webSettings.ExcludeFromOfflineClient; } if (web.MembersCanShare != webSettings.MembersCanShare) { web.MembersCanShare = webSettings.MembersCanShare; } if (web.DisableFlows != webSettings.DisableFlows) { web.DisableFlows = webSettings.DisableFlows; } if (web.DisableAppViews != webSettings.DisableAppViews) { web.DisableAppViews = webSettings.DisableAppViews; } if (web.HorizontalQuickLaunch != webSettings.HorizontalQuickLaunch) { web.HorizontalQuickLaunch = webSettings.HorizontalQuickLaunch; } #if !SP2019 if (web.SearchScope.ToString() != webSettings.SearchScope.ToString()) { web.SearchScope = (SearchScopeType)Enum.Parse(typeof(SearchScopeType), webSettings.SearchScope.ToString(), true); } if (web.SearchBoxInNavBar.ToString() != webSettings.SearchBoxInNavBar.ToString()) { web.SearchBoxInNavBar = (SearchBoxInNavBarType)Enum.Parse(typeof(SearchBoxInNavBarType), webSettings.SearchBoxInNavBar.ToString(), true); } string searchCenterUrl = parser.ParseString(webSettings.SearchCenterUrl); if (!string.IsNullOrEmpty(searchCenterUrl) && web.GetWebSearchCenterUrl(true) != webSettings.SearchCenterUrl) { web.SetWebSearchCenterUrl(webSettings.SearchCenterUrl); } #endif #endif var masterUrl = parser.ParseString(webSettings.MasterPageUrl); if (!string.IsNullOrEmpty(masterUrl)) { if (!isNoScriptSite) { web.MasterUrl = masterUrl; } else { scope.LogWarning(CoreResources.Provisioning_ObjectHandlers_WebSettings_SkipMasterPageUpdate); } } var customMasterUrl = parser.ParseString(webSettings.CustomMasterPageUrl); if (!string.IsNullOrEmpty(customMasterUrl)) { if (!isNoScriptSite) { web.CustomMasterUrl = customMasterUrl; } else { scope.LogWarning(CoreResources.Provisioning_ObjectHandlers_WebSettings_SkipCustomMasterPageUpdate); } } if (!String.IsNullOrEmpty(webSettings.Title)) { var newTitle = parser.ParseString(webSettings.Title); if (newTitle != web.Title) { web.Title = newTitle; } } if (!String.IsNullOrEmpty(webSettings.Description)) { var newDescription = parser.ParseString(webSettings.Description); if (newDescription != web.Description) { web.Description = newDescription; } } if (webSettings.SiteLogo != null) { var logoUrl = parser.ParseString(webSettings.SiteLogo); if (template.BaseSiteTemplate == "SITEPAGEPUBLISHING#0" && web.WebTemplate == "GROUP") { // logo provisioning throws when applying across base template IDs; provisioning fails in this case // this is the error that is already (rightly so) shown beforehand in the console: WARNING: The source site from which the template was generated had a base template ID value of SITEPAGEPUBLISHING#0, while the current target site has a base template ID value of GROUP#0. This could cause potential issues while applying the template. WriteMessage("Applying site logo across base template IDs is not possible. Skipping site logo provisioning.", ProvisioningMessageType.Warning); } else { // Modern site? Then we assume the SiteLogo is actually a filepath if (web.WebTemplate == "GROUP") { #if !ONPREMISES if (!string.IsNullOrEmpty(logoUrl) && !logoUrl.ToLower().Contains("_api/groupservice/getgroupimage")) { var fileBytes = ConnectorFileHelper.GetFileBytes(template.Connector, logoUrl); if (fileBytes != null && fileBytes.Length > 0) { #if !NETSTANDARD2_0 var mimeType = MimeMapping.GetMimeMapping(logoUrl); #else var mimeType = ""; var imgUrl = logoUrl; if (imgUrl.Contains("?")) { imgUrl = imgUrl.Split(new[] { '?' })[0]; } if (imgUrl.EndsWith(".gif", StringComparison.InvariantCultureIgnoreCase)) { mimeType = "image/gif"; } if (imgUrl.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) { mimeType = "image/png"; } if (imgUrl.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase)) { mimeType = "image/jpeg"; } #endif Sites.SiteCollection.SetGroupImageAsync((ClientContext)web.Context, fileBytes, mimeType).GetAwaiter().GetResult(); } } #endif } else { web.SiteLogoUrl = logoUrl; } } } var welcomePage = parser.ParseString(webSettings.WelcomePage); if (!string.IsNullOrEmpty(welcomePage)) { if (welcomePage != web.RootFolder.WelcomePage) { web.RootFolder.WelcomePage = welcomePage; web.RootFolder.Update(); } } if (!string.IsNullOrEmpty(webSettings.AlternateCSS)) { var newAlternateCssUrl = parser.ParseString(webSettings.AlternateCSS); if (newAlternateCssUrl != web.AlternateCssUrl) { web.AlternateCssUrl = newAlternateCssUrl; } } // Temporary disabled as this change is a breaking change for folks that have not set this property in their provisioning templates //web.QuickLaunchEnabled = webSettings.QuickLaunchEnabled; web.Update(); web.Context.ExecuteQueryRetry(); #if !ONPREMISES if (webSettings.HubSiteUrl != null) { if (TenantExtensions.IsCurrentUserTenantAdmin(web.Context as ClientContext)) { var hubsiteUrl = parser.ParseString(webSettings.HubSiteUrl); try { using (var tenantContext = web.Context.Clone(web.GetTenantAdministrationUrl(), applyingInformation.AccessTokens)) { var tenant = new Tenant(tenantContext); tenant.ConnectSiteToHubSite(web.Url, hubsiteUrl); tenantContext.ExecuteQueryRetry(); } } catch (Exception ex) { WriteMessage($"Hub site association failed: {ex.Message}", ProvisioningMessageType.Warning); } } else { WriteMessage("You need to be a SharePoint admin when associating to a Hub site.", ProvisioningMessageType.Warning); } } #endif } } return(parser); }
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); logger.LogInformationWithPnPCorrelation("Updated Azure KeyVault with SPORootSite property", action.CorrelationId); #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 logger.LogInformationWithPnPCorrelation("Reading the Provisioning Hierarchy file {PackageUrl}", action.CorrelationId, package.PackageUrl); // 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; logger.LogInformationWithPnPCorrelation("Read the Provisioning Hierarchy file {PackageUrl}", action.CorrelationId, package.PackageUrl); // 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; } logger.LogInformationWithPnPCorrelation("Provisioning Hierarchy ready for processing", action.CorrelationId); #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.Failed", 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 TokenParser ProvisionObjects(Tenant tenant, Model.ProvisioningHierarchy hierarchy, string sequenceId, TokenParser tokenParser, ApplyConfiguration configuration) { using (var scope = new PnPMonitoredScope(CoreResources.Provisioning_ObjectHandlers_Provisioning)) { bool nowait = false; if (configuration != null) { nowait = configuration.Tenant.DoNotWaitForSitesToBeFullyCreated; } var sequence = hierarchy.Sequences.FirstOrDefault(s => s.ID == sequenceId); if (sequence != null) { var siteUrls = new Dictionary <Guid, string>(); TokenParser siteTokenParser = null; // CHANGED: To avoid issues with low privilege users ClientObjectList <Microsoft.Online.SharePoint.TenantManagement.ThemeProperties> tenantThemes = null; if (TenantExtensions.IsCurrentUserTenantAdmin((ClientContext)tenant.Context)) { tenantThemes = tenant.GetAllTenantThemes(); tenant.Context.Load(tenantThemes); tenant.Context.ExecuteQueryRetry(); } foreach (var sitecollection in sequence.SiteCollections) { var rootSiteUrl = tenant.Context.Url.Replace("-admin", ""); ClientContext rootSiteContext = tenant.Context.Clone(rootSiteUrl, configuration.AccessTokens); ClientContext siteContext = null; switch (sitecollection) { case TeamSiteCollection t: { TeamSiteCollectionCreationInformation siteInfo = new TeamSiteCollectionCreationInformation() { Alias = tokenParser.ParseString(t.Alias), DisplayName = tokenParser.ParseString(t.Title), Description = tokenParser.ParseString(t.Description), Classification = tokenParser.ParseString(t.Classification), IsPublic = t.IsPublic, Lcid = (uint)t.Language }; if (Guid.TryParse(t.SiteDesign, out Guid siteDesignId)) { siteInfo.SiteDesignId = siteDesignId; } var groupSiteInfo = Sites.SiteCollection.GetGroupInfoAsync(rootSiteContext, siteInfo.Alias).GetAwaiter().GetResult(); string graphAccessToken = null; if (groupSiteInfo == null) { if (PnPProvisioningContext.Current != null) { try { graphAccessToken = PnPProvisioningContext.Current.AcquireCookie(PnP.Framework.Utilities.Graph.GraphHelper.MicrosoftGraphBaseURI); } catch { graphAccessToken = PnPProvisioningContext.Current.AcquireToken(new Uri(PnP.Framework.Utilities.Graph.GraphHelper.MicrosoftGraphBaseURI).Authority, null); } } WriteMessage($"Creating Team Site {siteInfo.Alias}", ProvisioningMessageType.Progress); #pragma warning disable CS0618 siteContext = Sites.SiteCollection.Create(rootSiteContext, siteInfo, configuration.Tenant.DelayAfterModernSiteCreation, noWait: nowait, graphAccessToken: graphAccessToken); #pragma warning restore CS0618 } else { if (groupSiteInfo.ContainsKey("siteUrl")) { WriteMessage($"Using existing Team Site {siteInfo.Alias}", ProvisioningMessageType.Progress); siteContext = (tenant.Context as ClientContext).Clone(groupSiteInfo["siteUrl"], configuration.AccessTokens); } } if (t.IsHubSite) { siteContext.Load(siteContext.Site, s => s.Id); siteContext.ExecuteQueryRetry(); RegisterAsHubSite(tenant, siteContext.Url, siteContext.Site.Id, t.HubSiteLogoUrl, t.HubSiteTitle, tokenParser); } if (!string.IsNullOrEmpty(t.Theme) && tenantThemes != null) { var parsedTheme = tokenParser.ParseString(t.Theme); if (tenantThemes.FirstOrDefault(th => th.Name == parsedTheme) != null) { tenant.SetWebTheme(parsedTheme, siteContext.Url); tenant.Context.ExecuteQueryRetry(); } else { WriteMessage($"Theme {parsedTheme} doesn't exist in the tenant, will not be applied", ProvisioningMessageType.Warning); } } if (siteContext.IsAppOnly() && string.IsNullOrEmpty(graphAccessToken) && (t.Teamify || t.HideTeamify)) { WriteMessage("Teamify and HideTeamify operation is not supported in App-only context", ProvisioningMessageType.Warning); } else if (siteContext.IsAppOnly() && !string.IsNullOrEmpty(graphAccessToken) && (t.Teamify || t.HideTeamify)) { try { if (t.Teamify) { siteContext.Site.EnsureProperty(s => s.GroupId); Graph.UnifiedGroupsUtility.CreateTeam(siteContext.Site.GroupId.ToString(), graphAccessToken).GetAwaiter().GetResult(); WriteMessage($"Teamifying the O365 group connected site at URL - {siteContext.Url}", ProvisioningMessageType.Progress); } } catch (Exception ex) { WriteMessage($"Teamifying site at URL - {siteContext.Url} failed due to an exception:- {ex.Message}", ProvisioningMessageType.Warning); } if (t.HideTeamify) { WriteMessage($"Teamifying prompt couldn't be hidden at site at URL - {siteContext.Url} in App-only context", ProvisioningMessageType.Warning); } } else { if (t.Teamify) { try { siteContext.TeamifyAsync().GetAwaiter().GetResult(); WriteMessage($"Teamifying the O365 group connected site at URL - {siteContext.Url}", ProvisioningMessageType.Progress); } catch (Exception ex) { WriteMessage($"Teamifying site at URL - {siteContext.Url} failed due to an exception:- {ex.Message}", ProvisioningMessageType.Warning); } } if (t.HideTeamify) { try { siteContext.HideTeamifyPromptAsync().GetAwaiter().GetResult(); WriteMessage($"Teamify prompt is now hidden for site at URL - {siteContext.Url}", ProvisioningMessageType.Progress); } catch (Exception ex) { WriteMessage($"Teamify prompt couldn't be hidden for site at URL - {siteContext.Url} due to an exception:- {ex.Message}", ProvisioningMessageType.Warning); } } } siteUrls.Add(t.Id, siteContext.Url); if (!string.IsNullOrEmpty(t.ProvisioningId)) { _additionalTokens.Add(new SequenceSiteUrlUrlToken(null, t.ProvisioningId, siteContext.Url)); siteContext.Web.EnsureProperty(w => w.Id); _additionalTokens.Add(new SequenceSiteIdToken(null, t.ProvisioningId, siteContext.Web.Id)); siteContext.Site.EnsureProperties(s => s.Id, s => s.GroupId); _additionalTokens.Add(new SequenceSiteCollectionIdToken(null, t.ProvisioningId, siteContext.Site.Id)); _additionalTokens.Add(new SequenceSiteGroupIdToken(null, t.ProvisioningId, siteContext.Site.GroupId)); } break; } case CommunicationSiteCollection c: { var siteUrl = tokenParser.ParseString(c.Url); if (!siteUrl.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) { // CHANGED: Modified to support low privilege users siteUrl = UrlUtility.Combine(rootSiteUrl, siteUrl); } CommunicationSiteCollectionCreationInformation siteInfo = new CommunicationSiteCollectionCreationInformation() { ShareByEmailEnabled = c.AllowFileSharingForGuestUsers, Classification = tokenParser.ParseString(c.Classification), Description = tokenParser.ParseString(c.Description), Lcid = (uint)c.Language, Owner = tokenParser.ParseString(c.Owner), Title = tokenParser.ParseString(c.Title), Url = siteUrl }; Guid siteDesignId; if (Guid.TryParse(c.SiteDesign, out siteDesignId)) { siteInfo.SiteDesignId = siteDesignId; } else if (Guid.TryParse(tokenParser.ParseString(c.SiteDesign), out siteDesignId)) { siteInfo.SiteDesignId = siteDesignId; } else { if (!string.IsNullOrEmpty(c.SiteDesign)) { siteInfo.SiteDesign = (CommunicationSiteDesign)Enum.Parse(typeof(CommunicationSiteDesign), c.SiteDesign); } else { siteInfo.SiteDesign = CommunicationSiteDesign.Showcase; } } // check if site exists var siteExistence = tenant.SiteExistsAnywhere(siteInfo.Url); if (siteExistence == SiteExistence.Yes) { WriteMessage($"Using existing Communications Site at {siteInfo.Url}", ProvisioningMessageType.Progress); siteContext = (tenant.Context as ClientContext).Clone(siteInfo.Url, configuration.AccessTokens); } else if (siteExistence == SiteExistence.Recycled) { var errorMessage = $"The requested Communications Site at {siteInfo.Url} is in the Recycle Bin and cannot be created"; WriteMessage(errorMessage, ProvisioningMessageType.Error); throw new RecycledSiteException(errorMessage); } else { WriteMessage($"Creating Communications Site at {siteInfo.Url}", ProvisioningMessageType.Progress); #pragma warning disable CS0618 siteContext = Sites.SiteCollection.Create(rootSiteContext, siteInfo, configuration.Tenant.DelayAfterModernSiteCreation, noWait: nowait); #pragma warning restore CS0618 } if (c.IsHubSite) { siteContext.Load(siteContext.Site, s => s.Id); siteContext.ExecuteQueryRetry(); RegisterAsHubSite(tenant, siteInfo.Url, siteContext.Site.Id, c.HubSiteLogoUrl, c.HubSiteTitle, tokenParser); } if (!string.IsNullOrEmpty(c.Theme) && tenantThemes != null) { var parsedTheme = tokenParser.ParseString(c.Theme); if (tenantThemes.FirstOrDefault(th => th.Name == parsedTheme) != null) { tenant.SetWebTheme(parsedTheme, siteInfo.Url); tenant.Context.ExecuteQueryRetry(); } else { WriteMessage($"Theme {parsedTheme} doesn't exist in the tenant, will not be applied", ProvisioningMessageType.Warning); } } siteUrls.Add(c.Id, siteInfo.Url); if (!string.IsNullOrEmpty(c.ProvisioningId)) { _additionalTokens.Add(new SequenceSiteUrlUrlToken(null, c.ProvisioningId, siteInfo.Url)); siteContext.Web.EnsureProperty(w => w.Id); _additionalTokens.Add(new SequenceSiteIdToken(null, c.ProvisioningId, siteContext.Web.Id)); siteContext.Site.EnsureProperties(s => s.Id, s => s.GroupId); _additionalTokens.Add(new SequenceSiteCollectionIdToken(null, c.ProvisioningId, siteContext.Site.Id)); _additionalTokens.Add(new SequenceSiteGroupIdToken(null, c.ProvisioningId, siteContext.Site.GroupId)); } break; } case TeamNoGroupSiteCollection t: { var siteUrl = tokenParser.ParseString(t.Url); TeamNoGroupSiteCollectionCreationInformation siteInfo = new TeamNoGroupSiteCollectionCreationInformation() { Lcid = (uint)t.Language, Url = siteUrl, Title = tokenParser.ParseString(t.Title), Description = tokenParser.ParseString(t.Description), Owner = tokenParser.ParseString(t.Owner) }; // check if site exists var siteExistence = tenant.SiteExistsAnywhere(siteUrl); if (siteExistence == SiteExistence.Yes) { WriteMessage($"Using existing Team Site at {siteUrl}", ProvisioningMessageType.Progress); siteContext = (tenant.Context as ClientContext).Clone(siteUrl, configuration.AccessTokens); } else if (siteExistence == SiteExistence.Recycled) { var errorMessage = $"The requested Team Site at {siteUrl} is in the Recycle Bin and cannot be created"; WriteMessage(errorMessage, ProvisioningMessageType.Error); throw new RecycledSiteException(errorMessage); } else { WriteMessage($"Creating Team Site with no Office 365 group at {siteUrl}", ProvisioningMessageType.Progress); #pragma warning disable CS0618 siteContext = Sites.SiteCollection.Create(rootSiteContext, siteInfo, configuration.Tenant.DelayAfterModernSiteCreation, noWait: nowait); #pragma warning restore CS0618 } if (t.Groupify) { if (string.IsNullOrEmpty(t.Alias)) { // We generate the alias, if it is missing t.Alias = t.Title.Replace(" ", string.Empty).ToLower(); } // In case we need to groupify the just created site var groupifyInformation = new TeamSiteCollectionGroupifyInformation { Alias = t.Alias, // Mandatory Classification = t.Classification, // Optional Description = t.Description, DisplayName = t.Title, HubSiteId = Guid.Empty, // Optional, so far we skip it IsPublic = t.IsPublic, // Mandatory KeepOldHomePage = t.KeepOldHomePage, // Optional, but we provide it Lcid = (uint)t.Language, Owners = new string[] { t.Owner }, }; tenant.GroupifySite(siteUrl, groupifyInformation); } if (t.IsHubSite) { siteContext.Load(siteContext.Site, s => s.Id); siteContext.ExecuteQueryRetry(); RegisterAsHubSite(tenant, siteContext.Url, siteContext.Site.Id, t.HubSiteLogoUrl, t.HubSiteTitle, tokenParser); } if (!string.IsNullOrEmpty(t.Theme) && tenantThemes != null) { var parsedTheme = tokenParser.ParseString(t.Theme); if (tenantThemes.FirstOrDefault(th => th.Name == parsedTheme) != null) { tenant.SetWebTheme(parsedTheme, siteContext.Url); tenant.Context.ExecuteQueryRetry(); } else { WriteMessage($"Theme {parsedTheme} doesn't exist in the tenant, will not be applied", ProvisioningMessageType.Warning); } } siteUrls.Add(t.Id, siteContext.Url); if (!string.IsNullOrEmpty(t.ProvisioningId)) { _additionalTokens.Add(new SequenceSiteUrlUrlToken(null, t.ProvisioningId, siteContext.Url)); siteContext.Web.EnsureProperty(w => w.Id); _additionalTokens.Add(new SequenceSiteIdToken(null, t.ProvisioningId, siteContext.Web.Id)); siteContext.Site.EnsureProperties(s => s.Id, s => s.GroupId); _additionalTokens.Add(new SequenceSiteCollectionIdToken(null, t.ProvisioningId, siteContext.Site.Id)); _additionalTokens.Add(new SequenceSiteGroupIdToken(null, t.ProvisioningId, siteContext.Site.GroupId)); } break; } } var web = siteContext.Web; if (siteTokenParser == null) { siteTokenParser = new TokenParser(tenant, hierarchy, configuration.ToApplyingInformation()); foreach (var token in _additionalTokens) { siteTokenParser.AddToken(token); // Add the token to the global token parser, too tokenParser.AddToken(token); } } foreach (var subsite in sitecollection.Sites) { var subSiteObject = (TeamNoGroupSubSite)subsite; web.EnsureProperties(w => w.Webs.IncludeWithDefaultProperties(), w => w.ServerRelativeUrl); siteTokenParser = CreateSubSites(hierarchy, siteTokenParser, sitecollection, siteContext, web, subSiteObject); } siteTokenParser = null; } // System.Threading.Thread.Sleep(TimeSpan.FromMinutes(10)); WriteMessage("Applying templates", ProvisioningMessageType.Progress); var currentSite = ""; var provisioningTemplateApplyingInformation = configuration.ToApplyingInformation(); provisioningTemplateApplyingInformation.ProgressDelegate = (string message, int step, int total) => { configuration.ProgressDelegate?.Invoke($"{currentSite} : {message}", step, total); }; foreach (var sitecollection in sequence.SiteCollections) { currentSite = sitecollection.ProvisioningId != null ? sitecollection.ProvisioningId : sitecollection.Title; siteUrls.TryGetValue(sitecollection.Id, out string siteUrl); if (siteUrl != null) { using (var clonedContext = tenant.Context.Clone(siteUrl, configuration.AccessTokens)) { var web = clonedContext.Web; foreach (var templateRef in sitecollection.Templates) { var provisioningTemplate = hierarchy.Templates.FirstOrDefault(t => t.Id == templateRef); if (provisioningTemplate != null) { provisioningTemplate.Connector = hierarchy.Connector; //if (siteTokenParser == null) //{ siteTokenParser = new TokenParser(web, provisioningTemplate, configuration.ToApplyingInformation()); foreach (var token in _additionalTokens) { siteTokenParser.AddToken(token); } //} //else //{ // siteTokenParser.Rebase(web, provisioningTemplate); //} WriteMessage($"Applying Template", ProvisioningMessageType.Progress); new SiteToTemplateConversion().ApplyRemoteTemplate(web, provisioningTemplate, provisioningTemplateApplyingInformation, true, siteTokenParser); } else { WriteMessage($"Referenced template ID {templateRef} not found", ProvisioningMessageType.Error); } } if (siteTokenParser == null) { siteTokenParser = new TokenParser(tenant, hierarchy, configuration.ToApplyingInformation()); foreach (var token in _additionalTokens) { siteTokenParser.AddToken(token); } } foreach (var subsite in sitecollection.Sites) { var subSiteObject = (TeamNoGroupSubSite)subsite; web.EnsureProperties(w => w.Webs.IncludeWithDefaultProperties(), w => w.ServerRelativeUrl); siteTokenParser = ApplySubSiteTemplates(hierarchy, siteTokenParser, sitecollection, clonedContext, web, subSiteObject, provisioningTemplateApplyingInformation); } if (sitecollection.IsHubSite) { RESTUtilities.ExecuteGetAsync(web, "/_api/web/hubsitedata(true)").GetAwaiter().GetResult(); } } } } } return(tokenParser); } }