Example #1
0
        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());
        }
Example #2
0
        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);
        }
Example #4
0
        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;
            }
        }
Example #5
0
        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);
        }
Example #6
0
        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);
        }
Example #7
0
        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());
        }
Example #8
0
        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);
            }
        }
Example #9
0
        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);
        }
Example #10
0
        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
            }
        }
Example #11
0
        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);
        }
Example #12
0
        public async Task WhenIAttemptToDeleteATenantWithTheIdThatLooksLikeAChildOfButWhichDoesNotExistAsync(string parentTenantLabel)
        {
            ITenant parent = this.Tenants[parentTenantLabel];

            await this.AttemptToDelete(TenantExtensions.CreateChildId(parent.Id, Guid.NewGuid()));
        }
Example #13
0
        public async Task WhenIAttemptToDeleteATenantWithTheIdThatLooksLikeAChildOfAChildOfTheRootTenantWhereNeitherExistsAsync()
        {
            string nonExistentParent = TenantExtensions.CreateChildId(RootTenant.RootTenantId, Guid.NewGuid());

            await this.AttemptToDelete(TenantExtensions.CreateChildId(nonExistentParent, Guid.NewGuid()));
        }
Example #14
0
 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);
        }
Example #16
0
        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);
        }
Example #17
0
        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();
            }
        }
Example #18
0
        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);
            }
        }