private static void LogReporting(ProvisioningActionModel action, string provisioningEnvironment, DateTime startProvisioning, DomainModel.Package package, Int32 outcome, String details = null)
        {
            // Prepare the reporting event
            var provisioningEvent = new
            {
                EventId             = action.CorrelationId,
                EventStartDateTime  = startProvisioning,
                EventEndDateTime    = DateTime.Now,
                EventOutcome        = outcome,
                EventDetails        = details,
                EventFromProduction = provisioningEnvironment.ToUpper() == "PROD" ? 1 : 0,
                TemplateId          = action.PackageId,
                TemplateDisplayName = package?.DisplayName,
            };

            try
            {
                // Make the Azure Function call for reporting
                HttpHelper.MakePostRequest(ConfigurationManager.AppSettings["SPPA:ReportingFunctionUrl"],
                                           provisioningEvent, "application/json", null);
            }
            catch
            {
                // Intentionally ignore any reporting issue
            }
        }
        private static async Task LoadThemesFromTenant(ProvisioningActionModel model, string tokenId, SharePointSite rootSite, string graphAccessToken)
        {
            // Retrieve the SPO URL for the Admin Site
            var adminSiteUrl = rootSite.WebUrl.Replace(".sharepoint.com", "-admin.sharepoint.com");

            // Retrieve the SPO Access Token
            var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                tokenId, adminSiteUrl,
                ConfigurationManager.AppSettings["ida:ClientId"],
                ConfigurationManager.AppSettings["ida:ClientSecret"],
                ConfigurationManager.AppSettings["ida:AppUrl"]);

            // Connect to SPO and retrieve the list of available Themes
            AuthenticationManager authManager = new AuthenticationManager();

            using (ClientContext spoContext = authManager.GetAzureADAccessTokenAuthenticatedContext(adminSiteUrl, spoAccessToken))
            {
                TenantAdmin.Tenant tenant = new TenantAdmin.Tenant(spoContext);
                var themes = tenant.GetAllTenantThemes();
                spoContext.Load(themes);
                spoContext.ExecuteQueryRetry();

                model.Themes = themes.Select(t => t.Name).ToList();
            }
        }
        public async Task <ActionResult> Provision(ProvisioningActionModel model)
        {
            CheckBetaFlag();
            PrepareHeaderData(model.ReturnUrl);

            if (model != null && ModelState.IsValid)
            {
                // Enqueue the provisioning request
                await ProvisioningAppManager.EnqueueProvisioningRequest(
                    model,
                    (Request.Files != null && Request.Files.Count > 0 && Request.Files[0].ContentLength > 0)?Request.Files[0].FileName : null,
                    (Request.Files != null && Request.Files.Count > 0 && Request.Files[0].ContentLength > 0)?Request.Files[0].InputStream : null
                    );

                // Get the service description content
                var context     = GetDataContext();
                var contentPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/ProvisioningScheduled.md");

                if (contentPage != null)
                {
                    model.ProvisionDescription = contentPage.Content;
                }
            }

            return(View("ProvisionQueued", model));
        }
        private static async Task LoadThemesFromTenant(ProvisioningActionModel model, string tokenId, string graphAccessToken)
        {
            // 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);

            // Store the SPO Root Site URL in the Model
            model.SPORootSiteUrl = rootSite.WebUrl;

            // Retrieve the SPO URL for the Admin Site
            var adminSiteUrl = rootSite.WebUrl.Replace(".sharepoint.com", "-admin.sharepoint.com");

            // Retrieve the SPO Access Token
            var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                tokenId, adminSiteUrl,
                ConfigurationManager.AppSettings["ida:ClientId"],
                ConfigurationManager.AppSettings["ida:ClientSecret"],
                ConfigurationManager.AppSettings["ida:AppUrl"]);

            // Connect to SPO and retrieve the list of available Themes
            AuthenticationManager authManager = new AuthenticationManager();

            using (ClientContext spoContext = authManager.GetAzureADAccessTokenAuthenticatedContext(adminSiteUrl, spoAccessToken))
            {
                TenantAdmin.Tenant tenant = new TenantAdmin.Tenant(spoContext);
                var themes = tenant.GetAllTenantThemes();
                spoContext.Load(themes);
                spoContext.ExecuteQueryRetry();

                model.Themes = themes.Select(t => t.Name).ToList();
            }
        }
        public async Task <ActionResult> Provision(String packageId = null)
        {
            if (String.IsNullOrEmpty(packageId))
            {
                throw new ArgumentNullException("packageId");
            }

            ProvisioningActionModel model = new ProvisioningActionModel();

            if (IsValidUser())
            {
                var issuer = (System.Threading.Thread.CurrentPrincipal as System.Security.Claims.ClaimsPrincipal)?.FindFirst("iss");
                if (issuer != null && !String.IsNullOrEmpty(issuer.Value))
                {
                    var issuerValue = issuer.Value.Substring(0, issuer.Value.Length - 1);
                    var tenantId    = issuerValue.Substring(issuerValue.LastIndexOf("/") + 1);
                    var upn         = (System.Threading.Thread.CurrentPrincipal as System.Security.Claims.ClaimsPrincipal)?.FindFirst(ClaimTypes.Upn)?.Value;

                    if (this.IsAllowedUpnTenant(upn))
                    {
                        #region Prepare model generic context data

                        // Prepare the model data
                        model.TenantId          = tenantId;
                        model.UserPrincipalName = upn;
                        model.PackageId         = packageId;
                        model.ApplyTheme        = false;
                        model.ApplyCustomTheme  = false;

                        String provisioningScope       = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"];
                        String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"];

                        var tokenId          = $"{model.TenantId}-{model.UserPrincipalName.GetHashCode()}-{provisioningScope}-{provisioningEnvironment}";
                        var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                            tokenId, "https://graph.microsoft.com/");

                        model.UserIsTenantAdmin = Utilities.UserIsTenantGlobalAdmin(graphAccessToken);
                        model.UserIsSPOAdmin    = Utilities.UserIsSPOAdmin(graphAccessToken);
                        model.NotificationEmail = upn;

                        #endregion

                        // If the current user is an admin, we can get the available Themes
                        if (model.UserIsTenantAdmin || model.UserIsSPOAdmin)
                        {
                            await LoadThemesFromTenant(model, tokenId, graphAccessToken);
                        }

                        LoadPackageDataIntoModel(packageId, model);
                    }
                    else
                    {
                        throw new ApplicationException("Invalid request, the current tenant is not allowed to use this solution!");
                    }
                }
            }

            return(View("Provision", model));
        }
        public async Task <ActionResult> Provision(ProvisioningActionModel model)
        {
            if (model != null && ModelState.IsValid)
            {
                if (!String.IsNullOrEmpty(model.MetadataPropertiesJson))
                {
                    model.MetadataProperties = JsonConvert.DeserializeObject <Dictionary <String, MetadataProperty> >(model.MetadataPropertiesJson);
                }

                // If there is an input file for the logo
                if (Request.Files != null && Request.Files.Count > 0 && Request.Files[0].ContentLength > 0)
                {
                    // Generate a random file name
                    model.CustomLogo = $"{Guid.NewGuid()}-{Request.Files[0].FileName}";

                    // Get a reference to the blob storage account
                    var blobLogosConnectionString = ConfigurationManager.AppSettings["BlobLogosProvider:ConnectionString"];
                    var blobLogosContainerName    = ConfigurationManager.AppSettings["BlobLogosProvider:ContainerName"];

                    CloudStorageAccount csaLogos;
                    if (!CloudStorageAccount.TryParse(blobLogosConnectionString, out csaLogos))
                    {
                        throw new ArgumentException("Cannot create cloud storage account from given connection string.");
                    }

                    CloudBlobClient    blobLogosClient = csaLogos.CreateCloudBlobClient();
                    CloudBlobContainer containerLogos  = blobLogosClient.GetContainerReference(blobLogosContainerName);

                    // Store the file in the blob storage
                    CloudBlockBlob blobLogo = containerLogos.GetBlockBlobReference(model.CustomLogo);
                    await blobLogo.UploadFromStreamAsync(Request.Files[0].InputStream);
                }

                // Get a reference to the blob storage queue
                CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
                    CloudConfigurationManager.GetSetting("SPPA:StorageConnectionString"));

                // Get queue... create if does not exist.
                CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
                CloudQueue       queue       = queueClient.GetQueueReference(
                    CloudConfigurationManager.GetSetting("SPPA:StorageQueueName"));
                queue.CreateIfNotExists();

                // add message to the queue
                queue.AddMessage(new CloudQueueMessage(JsonConvert.SerializeObject(model)));
            }

            // Get the service description content
            var context     = GetDataContext();
            var contentPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/ProvisioningScheduled.md");

            if (contentPage != null)
            {
                model.ProvisionDescription = contentPage.Content;
            }

            return(View("ProvisionQueued", model));
        }
        private static void CleanupCurrentActionItem(ProvisioningActionModel action, ProvisioningAppDBContext dbContext)
        {
            // Check if there is the action item for the current action
            var existingItem = dbContext.ProvisioningActionItems.FirstOrDefault(i => i.Id == action.CorrelationId);

            // And in case it does exist and it is not failed
            if (existingItem != null && !existingItem.FailedOn.HasValue)
            {
                // Delete it
                dbContext.ProvisioningActionItems.Remove(existingItem);
                dbContext.SaveChanges();
            }
        }
        public static async Task EnqueueProvisioningRequest(ProvisioningActionModel model, String logoFileName = null, Stream logoFile = null)
        {
            if (!String.IsNullOrEmpty(model.MetadataPropertiesJson))
            {
                model.MetadataProperties = JsonConvert.DeserializeObject <Dictionary <String, MetadataProperty> >(model.MetadataPropertiesJson);
            }

            // If there is an input file for the logo
            if (logoFile != null)
            {
                // Generate a random file name
                model.CustomLogo = $"{Guid.NewGuid()}-{logoFileName}";

                // Get a reference to the blob storage account
                var blobLogosConnectionString = ConfigurationManager.AppSettings["BlobLogosProvider:ConnectionString"];
                var blobLogosContainerName    = ConfigurationManager.AppSettings["BlobLogosProvider:ContainerName"];

                CloudStorageAccount csaLogos;
                if (!CloudStorageAccount.TryParse(blobLogosConnectionString, out csaLogos))
                {
                    throw new ArgumentException("Cannot create cloud storage account from given connection string.");
                }

                CloudBlobClient    blobLogosClient = csaLogos.CreateCloudBlobClient();
                CloudBlobContainer containerLogos  = blobLogosClient.GetContainerReference(blobLogosContainerName);

                // Store the file in the blob storage
                CloudBlockBlob blobLogo = containerLogos.GetBlockBlobReference(model.CustomLogo);
                await blobLogo.UploadFromStreamAsync(logoFile);
            }

            var queueTarget = CloudConfigurationManager.GetSetting("SPPA:QueueTarget")?.ToUpper() ?? "BLOB";

            switch (queueTarget)
            {
            case "SERVICEBUS":
                var sbConnectionString = CloudConfigurationManager.GetSetting("SPPA:ServiceBusConnectionString");
                var sbQueueName        = CloudConfigurationManager.GetSetting("SPPA:ServiceBusQueueName");
                await ServiceBusQueueUtility.EnqueueMessageAsync(sbConnectionString, sbQueueName, model);

                break;

            case "BLOB":
            default:
                var blobConnectionString = CloudConfigurationManager.GetSetting("SPPA:StorageConnectionString");
                var blobQueueName        = CloudConfigurationManager.GetSetting("SPPA:StorageQueueName");
                await BlobStorageQueueUtility.EnqueueMessageAsync(blobConnectionString, blobQueueName, model);

                break;
            }
        }
        private static void MarkCurrentActionItemAsFailed(ProvisioningActionModel action, ProvisioningAppDBContext dbContext)
        {
            // Check if there is the action item for the current action
            var existingItem = dbContext.ProvisioningActionItems.FirstOrDefault(i => i.Id == action.CorrelationId);

            // And in case it does exist
            if (existingItem != null)
            {
                // Set the failure date and time
                existingItem.FailedOn = DateTime.Now;

                // Update the persistence storage
                dbContext.SaveChanges();
            }
        }
Exemple #10
0
        public static async Task EnqueueProvisioningRequest(ProvisioningActionModel model, String logoFileName = null, Stream logoFile = null)
        {
            if (!String.IsNullOrEmpty(model.MetadataPropertiesJson))
            {
                model.MetadataProperties = JsonConvert.DeserializeObject <Dictionary <String, MetadataProperty> >(model.MetadataPropertiesJson);
            }

            // If there is an input file for the logo
            if (logoFile != null)
            {
                // Generate a random file name
                model.CustomLogo = $"{Guid.NewGuid()}-{logoFileName}";

                // Get a reference to the blob storage account
                var blobLogosConnectionString = ConfigurationManager.AppSettings["BlobLogosProvider:ConnectionString"];
                var blobLogosContainerName    = ConfigurationManager.AppSettings["BlobLogosProvider:ContainerName"];

                CloudStorageAccount csaLogos;
                if (!CloudStorageAccount.TryParse(blobLogosConnectionString, out csaLogos))
                {
                    throw new ArgumentException("Cannot create cloud storage account from given connection string.");
                }

                CloudBlobClient    blobLogosClient = csaLogos.CreateCloudBlobClient();
                CloudBlobContainer containerLogos  = blobLogosClient.GetContainerReference(blobLogosContainerName);

                // Store the file in the blob storage
                CloudBlockBlob blobLogo = containerLogos.GetBlockBlobReference(model.CustomLogo);
                await blobLogo.UploadFromStreamAsync(logoFile);
            }

            // Get a reference to the blob storage queue
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
                CloudConfigurationManager.GetSetting("SPPA:StorageConnectionString"));

            // Get queue... create if does not exist.
            CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
            CloudQueue       queue       = queueClient.GetQueueReference(
                CloudConfigurationManager.GetSetting("SPPA:StorageQueueName"));

            queue.CreateIfNotExists();

            // add message to the queue
            queue.AddMessage(new CloudQueueMessage(JsonConvert.SerializeObject(model)));
        }
        private static Boolean CheckIfActionIsAlreadyRunning(ProvisioningActionModel action, ProvisioningAppDBContext dbContext)
        {
            var result = false;

            var tenantId  = Guid.Parse(action.TenantId);
            var packageId = Guid.Parse(action.PackageId);

            // Check if there is already a pending action item with the same settings and not yet expired
            var alreadyExistingItems = from i in dbContext.ProvisioningActionItems
                                       where i.TenantId == tenantId && i.PackageId == packageId &&
                                       i.ExpiresOn > DateTime.Now &&
                                       i.FailedOn == null
                                       select i;

            // Prepare the action properties as JSON
            var currentActionProperties = action.PackageProperties != null?JsonConvert.SerializeObject(action.PackageProperties) : null;

            // Verify if the same package, with the same properties is already running in the same tenant
            foreach (var item in alreadyExistingItems)
            {
                if (item.PackageProperties == currentActionProperties)
                {
                    result = true;
                    break;
                }
            }

            if (!result)
            {
                // Add a ProvisioningActionItem record for tracking purposes
                dbContext.ProvisioningActionItems.Add(new ProvisioningActionItem
                {
                    Id                = action.CorrelationId,
                    PackageId         = packageId,
                    TenantId          = tenantId,
                    PackageProperties = action.PackageProperties != null ? JsonConvert.SerializeObject(action.PackageProperties) : null,
                    CreatedOn         = DateTime.Now,
                    ExpiresOn         = DateTime.Now.AddHours(2),
                });
                dbContext.SaveChanges();
            }

            return(result);
        }
Exemple #12
0
        private static void LogSourceTrackingProvisionedSites(ProvisioningActionModel action, string spoAccessToken, AuthenticationManager authManager, List <Tuple <string, string> > provisionedSites)
        {
            // Log source tracking (2 = Provisioned) for every provisioned site
            foreach (var provisionedSite in provisionedSites)
            {
                // Get the provisioned site URL
                var provisionedSiteUrl = provisionedSite.Item2;

                // Connect to the provisioned site
                using (var provisionedSiteContext = authManager.GetAzureADAccessTokenAuthenticatedContext(provisionedSiteUrl, spoAccessToken))
                {
                    // Retrieve the Site ID
                    var provisionedSiteId = provisionedSiteContext.Site.EnsureProperty(s => s.Id);

                    // Log the source tracking event
                    LogSourceTracking(action.Source, 2, null, action.PackageId, action.TenantId, provisionedSiteId.ToString());
                }
            }
        }
        /// <summary>
        /// Notifies an exception through the configured webhooks
        /// </summary>
        /// <param name="action">The provisioning action</param>
        /// <param name="ex">The exception that occurred</param>
        private static void ProcessWebhooksExceptionNotification(ProvisioningActionModel action, Exception ex)
        {
            if (action.Webhooks != null && action.Webhooks.Count > 0)
            {
                foreach (var wh in action.Webhooks)
                {
                    var provisioningWebhook = new OfficeDevPnP.Core.Framework.Provisioning.Model.ProvisioningWebhook
                    {
                        Kind       = ProvisioningTemplateWebhookKind.ExceptionOccurred,
                        Url        = wh.Url,
                        Method     = (ProvisioningTemplateWebhookMethod)Enum.Parse(typeof(ProvisioningTemplateWebhookMethod), wh.Method.ToString(), true),
                        BodyFormat = ProvisioningTemplateWebhookBodyFormat.Json, // force JSON format
                        Async      = false,                                      // force sync webhooks
                        Parameters = wh.Parameters,
                    };

                    var httpClient = new HttpClient();

                    WebhookSender.InvokeWebhook(provisioningWebhook, httpClient,
                                                ProvisioningTemplateWebhookKind.ExceptionOccurred,
                                                exception: ex);
                }
            }
        }
Exemple #14
0
        private static async Task <Dictionary <string, string> > PrepareAccessTokensAsync(ProvisioningActionModel model)
        {
            // Prepare the variable to hold the result
            var accessTokens = new Dictionary <string, string>();

            // Retrieve the Microsoft Graph Access Token
            var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                AuthenticationConfig.GetGraphScopes());

            // Retrieve the SPO Access Token
            var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                AuthenticationConfig.ClientId,
                AuthenticationConfig.ClientSecret,
                AuthenticationConfig.RedirectUri,
                AuthenticationConfig.GetSpoScopes(model.SPORootSiteUrl));

            // Retrieve the SPO URL for the Admin Site
            var adminSiteUrl = model.SPORootSiteUrl.Replace(".sharepoint.com", "-admin.sharepoint.com");

            // Retrieve the SPO Access Token
            var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                AuthenticationConfig.ClientId,
                AuthenticationConfig.ClientSecret,
                AuthenticationConfig.RedirectUri,
                AuthenticationConfig.GetSpoScopes(adminSiteUrl));

            // Configure the resulting dictionary
            accessTokens.Add(new Uri(AuthenticationConfig.GraphBaseUrl).Authority, graphAccessToken);
            accessTokens.Add(new Uri(model.SPORootSiteUrl).Authority, spoAccessToken);
            accessTokens.Add(new Uri(adminSiteUrl).Authority, spoAdminAccessToken);

            return(accessTokens);
        }
        private void LoadPackageDataIntoModel(string packageId, ProvisioningActionModel model)
        {
            var context = GetDataContext();

            DomainModel.Package package = null;

            // Get the package
            if (Boolean.Parse(ConfigurationManager.AppSettings["TestEnvironment"]))
            {
                // Process all packages in the test environment
                package = context.Packages.FirstOrDefault(p => p.Id == new Guid(packageId));
            }
            else
            {
                // Process not-preview packages in the production environment
                package = context.Packages.FirstOrDefault(p => p.Id == new Guid(packageId) && p.Preview == false);
            }

            if (package != null)
            {
                if ((package.PackageType == PackageType.Tenant &&
                     !this.Request.Url.AbsolutePath.Contains("/tenant/")) ||
                    (package.PackageType == PackageType.SiteCollection &&
                     !this.Request.Url.AbsolutePath.Contains("/site/")))
                {
                    throw new ApplicationException("Invalid request, the requested package/template is not valid for the current request!");
                }

                model.DisplayName = package.DisplayName;
                model.ActionType  = package.PackageType == PackageType.SiteCollection ? ActionType.Site : ActionType.Tenant;

                // Configure content for instructions
                model.Instructions = package.Instructions;

                // If we don't have specific instructions
                if (model.Instructions == null)
                {
                    // Get the default instructions
                    var instructionsPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/GenericInstructions.md");

                    if (instructionsPage != null)
                    {
                        model.Instructions = instructionsPage.Content;
                    }
                }

                // Configure content for provisioning recap
                model.ProvisionRecap = package.ProvisionRecap;

                // If we don't have specific provisioning recap
                if (String.IsNullOrEmpty(model.ProvisionRecap))
                {
                    // Get the default provisioning recap
                    var provisionRecapPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/GenericProvisioning.md");

                    if (provisionRecapPage != null)
                    {
                        model.ProvisionRecap = provisionRecapPage.Content;
                    }
                }

                // Retrieve parameters from the package/template definition
                var packageFileUrl     = new Uri(package.PackageUrl);
                var packageLocalFolder = packageFileUrl.AbsolutePath.Substring(1,
                                                                               packageFileUrl.AbsolutePath.LastIndexOf('/') - 1);
                var packageFileName = packageFileUrl.AbsolutePath.Substring(packageLocalFolder.Length + 2);

                ProvisioningHierarchy hierarchy = GetHierarchyFromStorage(packageLocalFolder, packageFileName);

                // If we have the hierarchy and its parameters
                if (hierarchy != null && hierarchy.Parameters != null)
                {
                    // Use them
                    model.PackageProperties = hierarchy.Parameters;
                }
                else
                {
                    // Otherwise, use an empty list of parameters
                    model.PackageProperties = new Dictionary <string, string>();
                }

                // Configure the metadata properties
                var metadata = new
                {
                    properties = new[] {
                        new {
                            name           = "",
                            caption        = "",
                            description    = "",
                            editor         = "",
                            editorSettings = "",
                        }
                    }
                };

                var metadataProperties = JsonConvert.DeserializeAnonymousType(package.PropertiesMetadata, metadata);
                model.MetadataProperties = metadataProperties.properties.ToDictionary(
                    i => i.name,
                    i => new MetadataProperty
                {
                    Name           = i.name,
                    Caption        = i.caption,
                    Description    = i.description,
                    Editor         = i.editor,
                    EditorSettings = i.editorSettings
                });

                model.MetadataPropertiesJson = JsonConvert.SerializeObject(model.MetadataProperties);

                // Get the service description content
                var contentPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/ProvisioningIntro.md");

                if (contentPage != null)
                {
                    model.ProvisionDescription = contentPage.Content;
                }

                // Get the pre-reqs Header content
                var preReqsHeaderPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/CanProvisionPreReqsHeader.md");
                if (preReqsHeaderPage != null)
                {
                    model.MissingPreReqsHeader = preReqsHeaderPage.Content;
                }

                // Get the pre-reqs Footer content
                var preReqsFooterPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/CanProvisionPreReqsFooter.md");
                if (preReqsFooterPage != null)
                {
                    model.MissingPreReqsFooter = preReqsFooterPage.Content;
                }
            }
            else
            {
                throw new ApplicationException("Invalid request, the requested package/template is not available!");
            }
        }
        public static async Task RunAsync([QueueTrigger("actions")] ProvisioningActionModel action, TextWriter log)
        {
            var startProvisioning = DateTime.Now;

            String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"];

            log.WriteLine($"Processing queue trigger function for tenant {action.TenantId}");
            log.WriteLine($"PnP Correlation ID: {action.CorrelationId.ToString()}");

            // Instantiate and use the telemetry model
            TelemetryUtility telemetry = new TelemetryUtility((s) => {
                log.WriteLine(s);
            });
            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"]);

                log.WriteLine($"Retrieved target Microsoft Graph Access Token.");

                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;

                    log.WriteLine($"Target SharePoint Online Tenant: {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"]);

                    log.WriteLine($"Retrieved target SharePoint Online Access Token.");

                    #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);

                        log.WriteLine($"SharePoint Online Root Site Collection title: {web.Title}");

                        #region Store the main site URL in KeyVault

                        // Store the main site URL in the vault
                        var vault = ProvisioningAppManager.SecurityTokensServiceProvider;

                        // Read any existing properties for the current tenantId
                        var properties = await vault.GetAsync(tokenId);

                        if (properties == null)
                        {
                            // If there are no properties, create a new dictionary
                            properties = new Dictionary <String, String>();
                        }

                        // Set/Update the RefreshToken value
                        properties["SPORootSite"] = spoTenant;

                        // Add or Update the Key Vault accordingly
                        await vault.AddOrUpdateAsync(tokenId, properties);

                        #endregion

                        #region Provision the package

                        var package = dbContext.Packages.FirstOrDefault(p => p.Id == new Guid(action.PackageId));

                        if (package != null)
                        {
                            // Update the Popularity of the package
                            package.TimesApplied++;
                            dbContext.SaveChanges();

                            #region Get the Provisioning Hierarchy file

                            // Determine reference path variables
                            var blobConnectionString = ConfigurationManager.AppSettings["BlobTemplatesProvider:ConnectionString"];
                            var blobContainerName    = ConfigurationManager.AppSettings["BlobTemplatesProvider:ContainerName"];

                            var packageFileName           = package.PackageUrl.Substring(package.PackageUrl.LastIndexOf('/') + 1);
                            var packageFileUri            = new Uri(package.PackageUrl);
                            var packageFileRelativePath   = packageFileUri.AbsolutePath.Substring(2 + blobContainerName.Length);
                            var packageFileRelativeFolder = packageFileRelativePath.Substring(0, packageFileRelativePath.LastIndexOf('/'));

                            // Configure telemetry properties
                            telemetryProperties.Add("PackageFileName", packageFileName);
                            telemetryProperties.Add("PackageFileUri", packageFileUri.ToString());

                            // Read the main provisioning file from the Blob Storage
                            CloudStorageAccount csa;
                            if (!CloudStorageAccount.TryParse(blobConnectionString, out csa))
                            {
                                throw new ArgumentException("Cannot create cloud storage account from given connection string.");
                            }

                            CloudBlobClient    blobClient    = csa.CreateCloudBlobClient();
                            CloudBlobContainer blobContainer = blobClient.GetContainerReference(blobContainerName);

                            var blockBlob = blobContainer.GetBlockBlobReference(packageFileRelativePath);

                            // Crate an in-memory copy of the source stream
                            MemoryStream mem = new MemoryStream();
                            await blockBlob.DownloadToStreamAsync(mem);

                            mem.Position = 0;

                            // Prepare the output hierarchy
                            ProvisioningHierarchy hierarchy = null;

                            if (packageFileName.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase))
                            {
                                // That's an XML Provisioning Template file

                                XDocument xml = XDocument.Load(mem);
                                mem.Position = 0;

                                // Deserialize the stream into a provisioning hierarchy reading any
                                // dependecy with the Azure Blob Storage connector
                                var formatter           = XMLPnPSchemaFormatter.GetSpecificFormatter(xml.Root.Name.NamespaceName);
                                var templateLocalFolder = $"{blobContainerName}/{packageFileRelativeFolder}";

                                var provider = new XMLAzureStorageTemplateProvider(
                                    blobConnectionString,
                                    templateLocalFolder);
                                formatter.Initialize(provider);

                                // Get the full hierarchy
                                hierarchy           = ((IProvisioningHierarchyFormatter)formatter).ToProvisioningHierarchy(mem);
                                hierarchy.Connector = provider.Connector;
                            }
                            else if (packageFileName.EndsWith(".pnp", StringComparison.InvariantCultureIgnoreCase))
                            {
                                // That's a PnP Package file

                                // Get a provider based on the in-memory .PNP Open XML file
                                OpenXMLConnector    openXmlConnector = new OpenXMLConnector(mem);
                                XMLTemplateProvider provider         = new XMLOpenXMLTemplateProvider(
                                    openXmlConnector);

                                // Get the .xml provisioning template file name
                                var xmlTemplateFileName = openXmlConnector.Info?.Properties?.TemplateFileName ??
                                                          packageFileName.Substring(packageFileName.LastIndexOf('/') + 1)
                                                          .ToLower().Replace(".pnp", ".xml");

                                // Get the full hierarchy
                                hierarchy           = provider.GetHierarchy(xmlTemplateFileName);
                                hierarchy.Connector = provider.Connector;
                            }

                            #endregion

                            #region Apply the template

                            // Prepare variable to collect provisioned sites
                            var provisionedSites = new List <Tuple <String, String> >();

                            // If we have a hierarchy with at least one Sequence
                            if (hierarchy != null) // && hierarchy.Sequences != null && hierarchy.Sequences.Count > 0)
                            {
                                Console.WriteLine($"Provisioning hierarchy \"{hierarchy.DisplayName}\"");

                                var tenantUrl = UrlUtilities.GetTenantAdministrationUrl(context.Url);

                                // Retrieve the SPO Access Token
                                var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                                    tokenId, tenantUrl,
                                    ConfigurationManager.AppSettings[$"{action.ActionType}:ClientId"],
                                    ConfigurationManager.AppSettings[$"{action.ActionType}:ClientSecret"],
                                    ConfigurationManager.AppSettings[$"{action.ActionType}:AppUrl"]);

                                log.WriteLine($"Retrieved target SharePoint Online Admin Center Access Token.");

                                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)
                                        {
                                            log.WriteLine($"{messageType} - {message}");
                                        };
                                        ptai.ProgressDelegate += delegate(string message, int step, int total)
                                        {
                                            log.WriteLine($"{step:00}/{total:00} - {message}");
                                        };
                                        ptai.SiteProvisionedDelegate += delegate(string title, string url)
                                        {
                                            log.WriteLine($"Fully provisioned site '{title}' with URL: {url}");
                                            var provisionedSite = new Tuple <string, string>(title, url);
                                            if (!provisionedSites.Contains(provisionedSite))
                                            {
                                                provisionedSites.Add(provisionedSite);
                                            }
                                        };

//#if !DEBUG
//                                        // Set the default delay for sites creations to 5 mins
//                                        ptai.DelayAfterModernSiteCreation = 60 * 5;
//#endif

                                        // 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);
                                                }
                                            }

                                            // Apply the hierarchy
                                            log.WriteLine($"Hierarchy Provisioning Started: {DateTime.Now:hh.mm.ss}");
                                            tenant.ApplyProvisionHierarchy(hierarchy,
                                                                           (hierarchy.Sequences != null && hierarchy.Sequences.Count > 0) ?
                                                                           hierarchy.Sequences[0].ID : null,
                                                                           ptai);
                                            log.WriteLine($"Hierarchy Provisioning Completed: {DateTime.Now:hh.mm.ss}");
                                        }

                                        if (action.ApplyTheme && action.ApplyCustomTheme)
                                        {
                                            if (!String.IsNullOrEmpty(action.ThemePrimaryColor) &&
                                                !String.IsNullOrEmpty(action.ThemeBodyTextColor) &&
                                                !String.IsNullOrEmpty(action.ThemeBodyBackgroundColor))
                                            {
                                                log.WriteLine($"Applying custom Theme to provisioned sites");

                                                #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))
                                                        {
                                                            log.WriteLine($"Custom Theme applied on site '{ps.Item1}' with URL: {ps.Item2}");
                                                        }
                                                        else
                                                        {
                                                            log.WriteLine($"Failed to apply custom Theme on site '{ps.Item1}' with URL: {ps.Item2}");
                                                        }
                                                    }
                                                }
                                            }
                                        }

                                        // 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);
                                    }
                                }
                            }
                            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

                        log.WriteLine($"Function successfully executed!");
                        // Log telemetry event
                        telemetry?.LogEvent("ProvisioningFunction.End", telemetryProperties);
                    }
                }
                else
                {
                    var noTokensErrorMessage = $"Cannot retrieve Refresh Token or Access Token for {action.CorrelationId} in tenant {action.TenantId}!";
                    log.WriteLine(noTokensErrorMessage);
                    throw new ApplicationException(noTokensErrorMessage);
                }
            }
            catch (Exception ex)
            {
                // Skip logging exception for Recycled Site
                if (ex is RecycledSiteException)
                {
                    // rather log an event
                    telemetry?.LogEvent("ProvisioningFunction.RecycledSite", telemetryProperties);

                    // Log reporting event (3 = RecycledSite)
                    LogReporting(action, provisioningEnvironment, startProvisioning, null, 3, ex.ToDetailedString());
                }
                // Skip logging exception for Concurrent Provisioning
                else if (ex is ConcurrentProvisioningException)
                {
                    // rather log an event
                    telemetry?.LogEvent("ProvisioningFunction.ConcurrentProvisioning", telemetryProperties);

                    // Log reporting event (4 = ConcurrentProvisioningException)
                    LogReporting(action, provisioningEnvironment, startProvisioning, null, 4, ex.ToDetailedString());
                }
                else
                {
                    // Log telemetry event
                    telemetry?.LogException(ex, "ProvisioningFunction.RunAsync", telemetryProperties);

                    // Log reporting event (2 = Failed)
                    LogReporting(action, provisioningEnvironment, startProvisioning, null, 2, ex.ToDetailedString());
                }

                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();
            }
        }
Exemple #17
0
        private static void GetProvisionTextMessages(SharePointPnP.ProvisioningApp.DomainModel.Package package, ProvisioningActionModel model)
        {
            var settings = Newtonsoft.Json.JsonConvert.DeserializeObject <SharePointPnP.ProvisioningApp.DomainModel.TemplateSettingsMetadata>(package.PropertiesMetadata);

            if (settings?.displayInfo?.provisionMessages != null)
            {
                model.ProvisionPageTitle    = settings.displayInfo.provisionMessages.provisionPageTitle;
                model.ProvisionPageSubTitle = settings.displayInfo.provisionMessages.provisionPageSubTitle;
                model.ProvisionPageText     = settings.displayInfo.provisionMessages.provisionPageText;
            }
        }
Exemple #18
0
        public async Task <ProvisionContentPackResponse> ProvisionContentPack(ProvisionContentPackRequest provisionRequest)
        {
            var provisionResponse = new ProvisionContentPackResponse();

            // If the input paramenters are missing, raise a BadRequest response
            if (provisionRequest == null)
            {
                ThrowEmptyRequest();
            }

            // If the TenantId input argument is missing, raise a BadRequest response
            if (String.IsNullOrEmpty(provisionRequest.TenantId))
            {
                ThrowMissingArgument("TenantId");
            }

            // If the UserPrincipalName input argument is missing, raise a BadRequest response
            if (String.IsNullOrEmpty(provisionRequest.UserPrincipalName))
            {
                ThrowMissingArgument("UserPrincipalName");
            }

            // If the PackageIds input argument is missing, raise a BadRequest response
            if (provisionRequest.Packages == null || provisionRequest.Packages.Count == 0)
            {
                ThrowMissingArgument("Packages");
            }

            if (provisionRequest != null &&
                !String.IsNullOrEmpty(provisionRequest.TenantId) &&
                !String.IsNullOrEmpty(provisionRequest.UserPrincipalName) &&
                provisionRequest.Packages != null &&
                provisionRequest.Packages.Count > 0)
            {
                try
                {
                    // Process the AuthorizationCode request
                    String provisioningScope       = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"];
                    String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"];

                    var tokenId = $"{provisionRequest.TenantId}-{provisionRequest.UserPrincipalName.ToLower().GetHashCode()}-{provisioningScope}-{provisioningEnvironment}";

                    try
                    {
                        // Retrieve the refresh token and store it in the KeyVault
                        await ProvisioningAppManager.AccessTokenProvider.SetupSecurityFromAuthorizationCodeAsync(
                            tokenId,
                            provisionRequest.AuthorizationCode,
                            provisionRequest.TenantId,
                            ConfigurationManager.AppSettings["ida:ClientId"],
                            ConfigurationManager.AppSettings["ida:ClientSecret"],
                            "https://graph.microsoft.com/",
                            provisionRequest.RedirectUri);
                    }
                    catch (Exception ex)
                    {
                        // In case of any authorization exception, raise an Unauthorized exception
                        ThrowUnauthorized(ex);
                    }

                    // Validate the Package IDs
                    var context = dbContext;
                    DomainModel.Package package = null;

                    // Get the first item to provision
                    var item = provisionRequest.Packages.First();

                    // And remove it from the whole list of items
                    provisionRequest.Packages.RemoveAt(0);
                    var childrenItems = provisionRequest.Packages;

                    // Get the package
                    if (ProvisioningAppManager.IsTestingEnvironment)
                    {
                        // Process all packages in the test environment
                        package = context.Packages.FirstOrDefault(p => p.Id == new Guid(item.PackageId));
                    }
                    else
                    {
                        // Process not-preview packages in the production environment
                        package = context.Packages.FirstOrDefault(p => p.Id == new Guid(item.PackageId) && p.Preview == false);
                    }

                    // If the package is not valid
                    if (package == null)
                    {
                        // Throw an exception accordingly
                        throw new ArgumentException("Invalid Package Id!");
                    }

                    // First of all, validate the provisioning request for pre-requirements
                    provisionResponse.CanProvisionResult = await CanProvisionInternal(
                        new CanProvisionModel
                    {
                        PackageId         = item.PackageId,
                        TenantId          = provisionRequest.TenantId,
                        UserPrincipalName = provisionRequest.UserPrincipalName,
                        SPORootSiteUrl    = provisionRequest.SPORootSiteUrl,
                        UserIsSPOAdmin    = true,  // We assume that the request comes from an Admin
                        UserIsTenantAdmin = true,  // We assume that the request comes from an Admin
                    });

                    // If the package can be provisioned onto the target
                    if (provisionResponse.CanProvisionResult.CanProvision)
                    {
                        // Prepare the provisioning request
                        var request = new ProvisioningActionModel();
                        request.ActionType               = ActionType.Tenant; // Do we want to support site/tenant or just one?
                        request.ApplyCustomTheme         = false;
                        request.ApplyTheme               = false;             // Do we need to apply any special theme?
                        request.CorrelationId            = Guid.NewGuid();
                        request.CustomLogo               = null;
                        request.DisplayName              = $"Provision Content Pack {item.PackageId}";
                        request.PackageId                = item.PackageId;
                        request.TargetSiteAlreadyExists  = false; // Do we want to check this?
                        request.TargetSiteBaseTemplateId = null;
                        request.TenantId          = provisionRequest.TenantId;
                        request.UserIsSPOAdmin    = true; // We don't use this in the job
                        request.UserIsTenantAdmin = true; // We don't use this in the job
                        request.UserPrincipalName = provisionRequest.UserPrincipalName.ToLower();
                        request.NotificationEmail = provisionRequest.NotificationEmail;
                        request.PackageProperties = item.Parameters;
                        request.ChildrenItems     = childrenItems;
                        request.Webhooks          = provisionRequest.Webhooks;

                        // Enqueue the provisioning request
                        await ProvisioningAppManager.EnqueueProvisioningRequest(request);
                    }

                    // Set the status of the provisioning request
                    provisionResponse.ProvisioningStarted = true;
                }
                catch (HttpResponseException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    // In case of any other exception, raise an InternalServerError exception
                    ThrowInternalServerError(ex);
                }
            }

            // Return to the requested URL
            return(provisionResponse);
        }
Exemple #19
0
        public async Task <ActionResult> Provision(String packageId = null, String returnUrl = null, String source = null)
        {
            if (String.IsNullOrEmpty(packageId))
            {
                throw new ArgumentNullException("packageId");
            }

            if (String.IsNullOrEmpty(source))
            {
                source = "default";
            }

            CheckBetaFlag();
            PrepareHeaderData(returnUrl);
            LogSourceTracking(source, 0, Request.Url.ToString(), packageId); // 0 = PageView

            ProvisioningActionModel model = new ProvisioningActionModel();

            try
            {
                if (IsValidUser())
                {
                    var issuer = (System.Threading.Thread.CurrentPrincipal as System.Security.Claims.ClaimsPrincipal)?.FindFirst("iss");
                    if (issuer != null && !String.IsNullOrEmpty(issuer.Value))
                    {
                        var issuerValue = issuer.Value.Substring(0, issuer.Value.Length - 1);
                        var tenantId    = issuerValue.Substring(issuerValue.LastIndexOf("/") + 1);
                        var upn         = (System.Threading.Thread.CurrentPrincipal as System.Security.Claims.ClaimsPrincipal)?.FindFirst(ClaimTypes.Upn)?.Value;

                        if (this.IsAllowedUpnTenant(upn))
                        {
                            #region Prepare model generic context data

                            // Prepare the model data
                            model.TenantId          = tenantId;
                            model.UserPrincipalName = upn;
                            model.PackageId         = packageId;
                            model.ApplyTheme        = false;
                            model.ApplyCustomTheme  = false;

                            String provisioningScope       = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"];
                            String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"];

                            var tokenId          = $"{model.TenantId}-{model.UserPrincipalName.ToLower().GetHashCode()}-{provisioningScope}-{provisioningEnvironment}";
                            var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync(
                                tokenId, "https://graph.microsoft.com/");

                            if (string.IsNullOrEmpty(graphAccessToken))
                            {
                                throw new ApplicationException($"Cannot retrieve a valid Access Token for user {model.UserPrincipalName.ToLower()} in tenant {model.TenantId}");
                            }

                            model.UserIsTenantAdmin = Utilities.UserIsTenantGlobalAdmin(graphAccessToken);
                            model.UserIsSPOAdmin    = Utilities.UserIsSPOAdmin(graphAccessToken);
                            model.NotificationEmail = upn;

                            model.ReturnUrl = returnUrl;
                            model.Source    = source;

                            #endregion

                            // 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);

                            // Store the SPO Root Site URL in the Model
                            model.SPORootSiteUrl = rootSite.WebUrl;

                            // If the current user is an admin, we can get the available Themes
                            if (model.UserIsTenantAdmin || model.UserIsSPOAdmin)
                            {
                                await LoadThemesFromTenant(model, tokenId, rootSite, graphAccessToken);
                            }

                            LoadPackageDataIntoModel(packageId, model);
                        }
                        else
                        {
                            throw new ApplicationException("Invalid request, the current tenant is not allowed to use this solution!");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

            return(View("Provision", model));
        }