Esempio n. 1
0
        public ActionResult Details(String packageId)
        {
            DetailsViewModel model = new DetailsViewModel();

            ProvisioningAppDBContext context = new ProvisioningAppDBContext();

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

            if (model.Package == null)
            {
                throw new ApplicationException("There is no Package with the provided packageId!");
            }

            return(View("Details", model));
        }
        public ActionResult DetailsByPath(String packageUrl)
        {
            ProvisioningAppDBContext context = new ProvisioningAppDBContext();
            Package targetPackage            = null;

            packageUrl.Replace("-", "/");

            // Get the package
            if (Boolean.Parse(ConfigurationManager.AppSettings["TestEnvironment"]))
            {
                // Show any package in the test environment
                targetPackage = context.Packages.FirstOrDefault(p => p.PackageUrl == packageUrl);
            }
            else
            {
                // Show not-preview packages in the production environment
                targetPackage = context.Packages.FirstOrDefault(p => p.PackageUrl == packageUrl && p.Preview == false);
            }

            if (targetPackage != null)
            {
                return(RedirectToAction("Details", targetPackage.Id));
            }
            else
            {
                throw new ApplicationException("There is no Package with the provided packageUrl!");
            }
        }
        private async Task SyncCategories()
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                // Find the category file
                ITemplateFile file = (await _cloneProvider.GetAsync(SYSTEM_PATH, WriteLog)).FindFile(CATEGORIES_NAME);
                if (file == null)
                {
                    throw new InvalidOperationException($"Cannot find file {CATEGORIES_NAME}");
                }

                // Deserialize the json
                var categories = await file.DownloadAsJsonAsync(new
                {
                    categories = new[]
                    {
                        new { id = "", displayName = "" }
                    }
                });

                var existingDbCategories = context.Categories.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
                foreach (var category in categories.categories)
                {
                    // Update category, if already exists
                    if (existingDbCategories.TryGetValue(category.id, out Category dbCategory))
                    {
                        dbCategory.DisplayName          = category.displayName;
                        context.Entry(dbCategory).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new category
                        dbCategory = new Category
                        {
                            Id          = category.id,
                            DisplayName = category.displayName
                        };
                        context.Entry(dbCategory).State = EntityState.Added;
                    }

                    existingDbCategories.Remove(category.id);
                }

                // Remove exceed categories
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbCategory in existingDbCategories)
                {
                    await context.Entry(dbCategory.Value).Collection(d => d.Packages).LoadAsync();

                    foreach (var dbPackage in dbCategory.Value.Packages.ToArray())
                    {
                        objectStateManager.ChangeRelationshipState(dbCategory.Value, dbPackage, d => d.Packages, EntityState.Deleted);
                    }

                    context.Entry(dbCategory.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
Esempio n. 4
0
        public ActionResult Index()
        {
            IndexViewModel model = new IndexViewModel();

            ProvisioningAppDBContext context = new ProvisioningAppDBContext();

            // Get the packages
            if (Boolean.Parse(ConfigurationManager.AppSettings["TestEnvironment"]))
            {
                // Show all packages in the test environment
                model.Packages = context.Packages.Include("Categories").ToList();
            }
            else
            {
                // Show not-preview packages in the production environment
                model.Packages = context.Packages.Include("Categories").Where(p => p.Preview == false).ToList();
            }

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

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

            // Get the categories
            model.Categories = context.Categories.ToDictionary(c => c.Id, c => c.DisplayName);

            return(View(model));
        }
Esempio n. 5
0
        private async Task SyncPlatforms()
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                // Find the platforms file
                ITemplateFile file = (await _cloneProvider.GetAsync(SYSTEM_PATH, WriteLog)).FindFile(PLATFORMS_NAME);
                if (file == null)
                {
                    throw new InvalidOperationException($"Cannot find file {PLATFORMS_NAME}");
                }

                // Deserialize the json
                var platforms = await file.DownloadAsJsonAsync(new
                {
                    platforms = new[]
                    {
                        new { id = "", displayName = "" }
                    }
                });

                var existingDbPlatforms = context.Platforms.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
                foreach (var platform in platforms.platforms)
                {
                    // Update platform, if already exists
                    if (existingDbPlatforms.TryGetValue(platform.id, out Platform dbPlatform))
                    {
                        dbPlatform.DisplayName          = platform.displayName;
                        context.Entry(dbPlatform).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new platform
                        dbPlatform = new Platform
                        {
                            Id          = platform.id,
                            DisplayName = platform.displayName,
                        };
                        context.Entry(dbPlatform).State = EntityState.Added;
                    }

                    existingDbPlatforms.Remove(platform.id);
                }

                // Remove exceed platforms
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbPlatform in existingDbPlatforms)
                {
                    await context.Entry(dbPlatform.Value).Collection(p => p.Packages).LoadAsync();

                    foreach (var dbPackage in dbPlatform.Value.Packages.ToArray())
                    {
                        objectStateManager.ChangeRelationshipState(dbPlatform.Value, dbPackage, p => p.Packages, EntityState.Deleted);
                    }

                    context.Entry(dbPlatform.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
        /// <summary>
        /// Checks the Authorization for an API request
        /// </summary>
        /// <param name="allowAppOnly">Defines whether an app-only request is allowed or not</param>
        public static void CheckRequestAuthorization(Boolean allowAppOnly)
        {
            // Read the current audience from the configuration
            var audience = AuthenticationConfig.Audience;

            // Read the permission scopes from the configuration
            var scope = AuthenticationConfig.ApiScope;

            // If there isn't a current user identity, there is a security issue
            if (ClaimsPrincipal.Current == null || ClaimsPrincipal.Current.Identity == null)
            {
                ThrowAuthorizationException();
            }

            // Otherwise try to get the permission scope and the appid claims
            var scopeClaim    = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope");
            var appIdClaim    = ClaimsPrincipal.Current.FindFirst("appid");
            var audienceClaim = ClaimsPrincipal.Current.FindFirst("aud");

            // Check if the audience claim matches the current audience, otherwise there is a security issue
            if (audienceClaim == null ||
                audienceClaim.Value != audience)
            {
                ThrowAuthorizationException();
            }

            // If we have the AppId, check it against the ConsumerApps collection
            if (appIdClaim != null)
            {
                var dbContext     = new ProvisioningAppDBContext();
                var consumerAppId = Guid.Parse(appIdClaim.Value);
                var consumerApp   = dbContext.ConsumerApps.FirstOrDefault(a => a.Id == consumerAppId);
                if (consumerApp == null)
                {
                    // If the AppId is not "supported", there is a security issue
                    ThrowAuthorizationException();
                }
            }
            else
            {
                // If we don't have the AppId, there is a security issue
                ThrowAuthorizationException();
            }

            // If we're not allowed to accept an app-only request
            if (!allowAppOnly)
            {
                // and there is no scope claim or the scope claim is wrong, there is a security issue
                if (scopeClaim == null ||
                    (scopeClaim != null &&
                     scopeClaim.Value != scope))
                {
                    ThrowAuthorizationException();
                }
            }
        }
Esempio n. 7
0
        private ProvisioningAppDBContext GetContext()
        {
            var context = new ProvisioningAppDBContext();

            context.Configuration.ProxyCreationEnabled     = false;
            context.Configuration.LazyLoadingEnabled       = false;
            context.Configuration.AutoDetectChangesEnabled = false;

            return(context);
        }
        private async Task SyncFirstReleaseTenants()
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                // Find the category file
                ITemplateFile file = (await _cloneProvider.GetAsync(SYSTEM_PATH, WriteLog)).FindFile(TENANTS_NAME);
                if (file == null)
                {
                    throw new InvalidOperationException($"Cannot find file {TENANTS_NAME}");
                }

                // Deserialize the json
                var tenantsList = await file.DownloadAsJsonAsync(new
                {
                    tenants = new[]
                    {
                        new { id = "", tenantName = "", referenceOwner = "" }
                    }
                });

                var existingDbTenants = context.Tenants.ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase);
                foreach (var tenant in tenantsList.tenants)
                {
                    // Update tenant, if already exists
                    if (existingDbTenants.TryGetValue(tenant.id, out Tenant dbTenant))
                    {
                        dbTenant.TenantName           = tenant.tenantName;
                        dbTenant.ReferenceOwner       = tenant.referenceOwner;
                        context.Entry(dbTenant).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new tenant
                        dbTenant = new Tenant
                        {
                            Id             = tenant.id,
                            TenantName     = tenant.tenantName,
                            ReferenceOwner = tenant.referenceOwner
                        };
                        context.Entry(dbTenant).State = EntityState.Added;
                    }

                    existingDbTenants.Remove(tenant.id);
                }

                // Remove exceed categories
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbTenant in existingDbTenants)
                {
                    context.Entry(dbTenant.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
Esempio n. 9
0
        private async Task SyncContentPages()
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                // Find the content pages files
                var files = await _cloneProvider.GetAsync(CONTENT_PATH, WriteLog);

                if (files == null || files.Count() == 0)
                {
                    throw new InvalidOperationException($"Cannot find files in folder {CONTENT_PATH}");
                }

                // Consider system pages only
                var existingDbContentPages = context.ContentPages
                                             .ToList()
                                             .Where(cp => cp.Id.StartsWith("system", StringComparison.InvariantCultureIgnoreCase))
                                             .ToDictionary(cp => cp.Id, StringComparer.OrdinalIgnoreCase);
                foreach (ITemplateFile file in files)
                {
                    // Get the file content
                    String fileContent = await GetHtmlContentAsync(CONTENT_PATH, file.Path.Substring(file.Path.LastIndexOf('/') + 1));

                    // Update Content Page, if already exists
                    if (existingDbContentPages.TryGetValue(file.Path, out ContentPage dbContentPage))
                    {
                        dbContentPage.Content = fileContent;
                        context.Entry(dbContentPage).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new Content Page
                        dbContentPage = new ContentPage
                        {
                            Id      = file.Path,
                            Content = fileContent,
                        };
                        context.Entry(dbContentPage).State = EntityState.Added;
                    }

                    existingDbContentPages.Remove(file.Path);
                }

                // Remove leftover pages
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbContentPage in existingDbContentPages)
                {
                    context.Entry(dbContentPage.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
        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();
            }
        }
Esempio n. 11
0
        public ActionResult ContentPage(String contentPageId)
        {
            ContentPageViewModel model = new ContentPageViewModel();

            ProvisioningAppDBContext context = new ProvisioningAppDBContext();
            var targetContentPageId          = $"system/pages/{contentPageId}.md";
            var contentPage = context.ContentPages.FirstOrDefault(cp => cp.Id == targetContentPageId);

            if (contentPage == null)
            {
                throw new ApplicationException("There is no Content Page with the provided contentPageId!");
            }

            model.Content = contentPage.Content;

            return(View("ContentPage", model));
        }
        //private static async Task CleanupKeyVault()
        //{
        //    // Get the reference date for removal of keys (expired at least 2 hours ago)
        //    var referenceDateTime = DateTime.Now.AddHours(-2);

        //    // This job cleans up the expired keys from the Azure Key Vault
        //    var vault = new KeyVaultService();

        //    // Get all the keys stored in Key Vault
        //    var allKeys = await vault.ListKeysAsync();

        //    // Check for expired keys
        //    foreach (var key in allKeys)
        //    {
        //        var currentKey = await vault.GetFullKeyAsync(key);
        //        if (currentKey.Attributes.Expires <= referenceDateTime)
        //        {
        //            // The key is expired, let's remove it
        //            await vault.RemoveKeyAsync(key);

        //            // Log the action
        //            Console.WriteLine($"Deleted key {key} that expired on {currentKey.Attributes.Expires}");
        //        }
        //        else
        //        {
        //            Console.WriteLine($"Key {key} is not yet expired");
        //        }

        //        // Delay to avoid throttling
        //        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
        //    }
        //}

        //private static async Task CleanupSqlVault()
        //{
        //    Console.WriteLine("Cleaning up SQL Vault");

        //    // This job cleans up the expired keys from the Sql Azure Database
        //    var sqlVault = new SqlServerVaultService();
        //    await sqlVault.CleanupTokensAsync();

        //    Console.WriteLine("Cleaned SQL Vault");
        //}

        private static async Task CleanupSqlServer()
        {
            // Get the expired actions
            var dbContext    = new ProvisioningAppDBContext();
            var now          = DateTime.Now;
            var expiredItems = dbContext.ProvisioningActionItems.Where(i => i.ExpiresOn <= now);

            // And delete any expired item
            foreach (var i in expiredItems)
            {
                Console.WriteLine($"Deleting action item {i.Id} because it is expired");

                // Delete it
                dbContext.ProvisioningActionItems.Remove(i);
            }

            await dbContext.SaveChangesAsync();
        }
        public ActionResult CategoriesMenu(String returnUrl = null)
        {
            CategoriesMenuViewModel model = new CategoriesMenuViewModel();

            // Let's see if we need to filter the output categories
            var slbHost         = System.Configuration.ConfigurationManager.AppSettings["SPLBSiteHost"];
            var testEnvironment = Boolean.Parse(ConfigurationManager.AppSettings["TestEnvironment"]);

            string targetPlatform = null;

            if (!String.IsNullOrEmpty(returnUrl) &&
                !String.IsNullOrEmpty(slbHost) &&
                returnUrl.Contains(slbHost))
            {
                model.BaseLinksUrl = returnUrl.Substring(0, returnUrl.LastIndexOf(@"/") + 1);
                targetPlatform     = "LOOKBOOK";
            }
            else
            {
                model.BaseLinksUrl = String.Empty;
                targetPlatform     = "SPPNP";
            }

            // Get all the Categories together with the Packages
            ProvisioningAppDBContext context = new ProvisioningAppDBContext();

            var tempCategories = context.Categories
                                 .AsNoTracking()
                                 .Where(c => c.Packages.Any(
                                            p => p.Visible &&
                                            (testEnvironment || !p.Preview) &&
                                            p.TargetPlatforms.Any(pf => pf.Id == targetPlatform)
                                            ))
                                 .OrderBy(c => c.Order)
                                 .Include("Packages")
                                 .ToList();

            model.Categories = tempCategories;

            return(PartialView("CategoriesMenu", model));
        }
Esempio n. 14
0
        private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context)
        {
            var items = await _cloneProvider.GetAsync(folder.Path, WriteLog);

            // Read settings file
            ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME);

            if (settingsFile == null)
            {
                WriteLog($"Cannot find file {SETTINGS_NAME}");
                return(null);
            }

            #region Prepare the settings file and its outline

            var settings = await settingsFile.DownloadAsJsonAsync(new TemplateSettings());

            #endregion

            // Read the package file
            ITemplateFile packageFile = items.FindFile(settings.packageFile);
            if (packageFile == null)
            {
                WriteLog($"Cannot find file {settings.packageFile}");
                return(null);
            }

            #region Fix the Preview Image URLs

            // Fix the URLs in the metadata settings
            if (settings?.metadata?.displayInfo?.previewImages != null)
            {
                int previewImagesCount = settings.metadata.displayInfo.previewImages.Length;
                for (var n = 0; n < previewImagesCount; n++)
                {
                    var previewImageUrl = settings.metadata.displayInfo.previewImages[n].url;
                    settings.metadata.displayInfo.previewImages[n].url =
                        ChangeUri(packageFile.DownloadUri, previewImageUrl);
                }
            }

            if (settings?.metadata?.displayInfo?.detailItemCategories != null)
            {
                int detailItemCategoriesCount = settings.metadata.displayInfo.detailItemCategories.Length;
                for (var n = 0; n < detailItemCategoriesCount; n++)
                {
                    if (settings.metadata.displayInfo.detailItemCategories[n].items != null)
                    {
                        var detailItemCategoryItemsCount = settings.metadata.displayInfo.detailItemCategories[n].items.Length;
                        for (var m = 0; m < detailItemCategoryItemsCount; m++)
                        {
                            var detailItemCategoryItemPreviewImageUrl = settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage;
                            if (!String.IsNullOrEmpty(detailItemCategoryItemPreviewImageUrl))
                            {
                                settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage =
                                    ChangeUri(packageFile.DownloadUri, detailItemCategoryItemPreviewImageUrl);
                            }
                        }
                    }
                }
            }

            #endregion

            var package = new DomainModel.Package
            {
                Id         = !string.IsNullOrEmpty(settings.templateId) ? new Guid(settings.templateId) : Guid.NewGuid(),
                PackageUrl = packageFile.DownloadUri.ToString(),
                // New properties for Wave2
                Promoted           = settings.promoted,
                Preview            = settings.preview,
                TimesApplied       = 0,
                PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata),
                // New properties for Wave 5
                Abstract                   = settings.@abstract,
                SortOrder                  = settings.sortOrder,
                SortOrderPromoted          = settings.sortOrderPromoted,
                RepositoryRelativeUrl      = folder.Path,
                MatchingSiteBaseTemplateId = settings.matchingSiteBaseTemplateId,
                ForceNewSite               = settings.forceNewSite,
                Visible        = settings.visible,
                PageTemplateId = settings.metadata?.displayInfo?.pageTemplateId,
                // New properties for Wave 12
                DisplayName       = settings.metadata?.displayInfo?.siteTitle,
                ForceExistingSite = settings.forceExistingSite,
            };

            // Read the instructions.md and the provisioning.md files
            String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME);

            String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME);

            package.Instructions   = instructionsContent;
            package.ProvisionRecap = provisioningContent;

            // Read any pre-requirement content files
            var preRequirementContents = items.FindFiles(i =>
                                                         i.Path.ToLower().Contains("prerequirement-") &&
                                                         i.Path.EndsWith(".md", StringComparison.InvariantCultureIgnoreCase));

            // Process content for pre-requirements, if any
            if (preRequirementContents != null)
            {
                // Get the existing content pages for the current folder
                var existingDbContentPages = context.ContentPages
                                             .ToList()
                                             .Where(cp => cp.Id.StartsWith(folder.Path, StringComparison.InvariantCultureIgnoreCase))
                                             .ToDictionary(cp => cp.Id, StringComparer.OrdinalIgnoreCase);

                foreach (var prc in preRequirementContents)
                {
                    // Get the file content
                    String fileContent = await GetHtmlContentAsync(folder.Path, prc.Path.Substring(prc.Path.LastIndexOf('/') + 1));

                    // Update Content Page, if already exists
                    if (existingDbContentPages.TryGetValue(prc.Path, out ContentPage dbContentPage))
                    {
                        dbContentPage.Content = fileContent;
                        context.Entry(dbContentPage).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new Content Page
                        dbContentPage = new ContentPage
                        {
                            Id      = prc.Path,
                            Content = fileContent,
                        };
                        context.Entry(dbContentPage).State = EntityState.Added;
                    }

                    existingDbContentPages.Remove(prc.Path);
                }

                // Remove leftover content pages, if any
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbContentPage in existingDbContentPages)
                {
                    context.Entry(dbContentPage.Value).State = EntityState.Deleted;
                }
            }

            // Find the categories to apply
            if (settings.categories != null && settings.categories.Length > 0)
            {
                var dbCategories = settings.categories.Select(c =>
                {
                    Category dbCategory = context.Categories.Find(c);
                    if (dbCategory == null)
                    {
                        WriteLog($"Cannot find category with id {c}");
                    }

                    return(dbCategory);
                }).ToArray();
                package.Categories.AddRange(dbCategories);
            }

            // Find the platforms to apply
            if (settings.platforms != null && settings.platforms.Length > 0)
            {
                var dbPlatforms = settings.platforms.Select(p =>
                {
                    Platform dbPlatform = context.Platforms.Find(p);
                    if (dbPlatform == null)
                    {
                        WriteLog($"Cannot find platform with id {p}");
                    }

                    return(dbPlatform);
                }).ToArray();
                package.TargetPlatforms.AddRange(dbPlatforms);
            }

            // Find then author and fill her/his information
            await FillAuthorAsync(package, packageFile.Path);

            // Open the package and set info
            await FillPackageAsync(package, packageFile);

            return(package);
        }
        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);
        }
Esempio n. 16
0
        private async Task SyncTemplatesAsync(string path, PackageType type)
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;

                // Find packages inside folder
                IEnumerable <ITemplateItem> items = await _cloneProvider.GetAsync(path, WriteLog);

                var packages = await FindPackagesAsync(items, context);

                var existingDbPackages = context.Packages
                                         .Include(c => c.Categories)
                                         .Include(c => c.TargetPlatforms)
                                         // .Where(p => p.PackageType == type)
                                         .ToDictionary(c => c.Id);
                foreach (DomainModel.Package package in packages)
                {
                    package.PackageType = type;

                    // Update package, if already exists
                    if (existingDbPackages.TryGetValue(package.Id, out DomainModel.Package dbPackage))
                    {
                        // Copy info into existing item
                        dbPackage.DisplayName     = package.DisplayName;
                        dbPackage.Author          = package.Author;
                        dbPackage.AuthorLink      = package.AuthorLink;
                        dbPackage.Description     = package.Description;
                        dbPackage.ImagePreviewUrl = package.ImagePreviewUrl;
                        dbPackage.PackageUrl      = package.PackageUrl;
                        dbPackage.Version         = package.Version;

                        // New properties for Wave2
                        dbPackage.Promoted           = package.Promoted;
                        dbPackage.Preview            = package.Preview;
                        dbPackage.PropertiesMetadata = package.PropertiesMetadata;

                        // Keep times applied from the DB
                        // dbPackage.TimesApplied = package.TimesApplied;

                        // New properties for Wave 4
                        dbPackage.Instructions   = package.Instructions;
                        dbPackage.ProvisionRecap = package.ProvisionRecap;

                        // New properties for Wave 5
                        dbPackage.SortOrder             = package.SortOrder;
                        dbPackage.SortOrderPromoted     = package.SortOrderPromoted;
                        dbPackage.RepositoryRelativeUrl = package.RepositoryRelativeUrl;
                        dbPackage.Abstract = package.Abstract;

                        // New properties for Wave 6
                        dbPackage.ForceNewSite = package.ForceNewSite;
                        dbPackage.MatchingSiteBaseTemplateId = package.MatchingSiteBaseTemplateId;

                        // New properties for Wave 7
                        dbPackage.Visible = package.Visible;

                        // New properties for Wave 8
                        dbPackage.PackageProperties = package.PackageProperties;

                        // Wave 12 - Allowed to move from one type to another
                        dbPackage.PackageType       = package.PackageType;
                        dbPackage.ForceExistingSite = package.ForceExistingSite;

                        context.Entry(dbPackage).State = EntityState.Modified;

                        // Add new categories
                        foreach (var c in package.Categories.Except(dbPackage.Categories).ToArray())
                        {
                            objectStateManager.ChangeRelationshipState(dbPackage, c, d => d.Categories, EntityState.Added);
                        }
                        // Remove old categories
                        foreach (var c in dbPackage.Categories.Except(package.Categories).ToArray())
                        {
                            objectStateManager.ChangeRelationshipState(dbPackage, c, d => d.Categories, EntityState.Deleted);
                        }

                        // Add new platforms
                        foreach (var c in package.TargetPlatforms.Except(dbPackage.TargetPlatforms).ToArray())
                        {
                            objectStateManager.ChangeRelationshipState(dbPackage, c, d => d.TargetPlatforms, EntityState.Added);
                        }
                        // Remove old platforms
                        foreach (var c in dbPackage.TargetPlatforms.Except(package.TargetPlatforms).ToArray())
                        {
                            objectStateManager.ChangeRelationshipState(dbPackage, c, d => d.TargetPlatforms, EntityState.Deleted);
                        }

                        existingDbPackages.Remove(dbPackage.Id);
                    }
                    else
                    {
                        // Add new package
                        context.Entry(package).State = EntityState.Added;
                    }
                }

                // Remove leftover packages from current category
                foreach (var dbPackage in existingDbPackages.Where(p => p.Value.PackageType == type))
                {
                    await context.Entry(dbPackage.Value).Collection(d => d.Categories).LoadAsync();

                    foreach (var dbCategory in dbPackage.Value.Categories.ToArray())
                    {
                        objectStateManager.ChangeRelationshipState(dbPackage.Value, dbCategory, d => d.Categories, EntityState.Deleted);
                    }

                    context.Entry(dbPackage.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
Esempio n. 17
0
        private async Task <IReadOnlyList <DomainModel.Package> > FindPackagesAsync(IEnumerable <ITemplateItem> items, ProvisioningAppDBContext context)
        {
            var packages = new List <DomainModel.Package>();

            foreach (ITemplateFolder folder in items.OfType <ITemplateFolder>())
            {
                WriteLog($"Processing folder {folder.Path}...");

                try
                {
                    DomainModel.Package package = await GetPackageAsync(folder, context);

                    if (package == null)
                    {
                        continue;
                    }

                    if (!String.IsNullOrEmpty(package.DisplayName))
                    {
                        packages.Add(package);
                    }
                }
                catch (Exception ex)
                {
                    WriteLog($"Error processing folder {folder.Path}: {ex.Message} - {ex.StackTrace}");
                }
            }

            return(packages);
        }
        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();
            }
        }
Esempio n. 19
0
        private async Task SyncPageTemplates()
        {
            using (ProvisioningAppDBContext context = GetContext())
            {
                // Find the category file
                var pageTemplatesFolders = await _cloneProvider.GetAsync(PAGE_TEMPLATES_PATH, WriteLog);

                if (pageTemplatesFolders == null || pageTemplatesFolders.Count() == 0)
                {
                    throw new InvalidOperationException($"Cannot find Page Template folders in folder {PAGE_TEMPLATES_PATH}");
                }

                var existingDbPageTemplates = context.PageTemplates.ToDictionary(cp => cp.Id, StringComparer.OrdinalIgnoreCase);
                foreach (ITemplateFolder pageTemplateFolder in pageTemplatesFolders)
                {
                    // Prepare content variables
                    String htmlContent = null;
                    String cssContent  = null;

                    // Get the page template folder content files
                    var pageTemplateFiles = await _cloneProvider.GetAsync(pageTemplateFolder.Path, WriteLog);

                    if (pageTemplateFiles == null || pageTemplateFiles.Count() == 0)
                    {
                        throw new InvalidOperationException($"Cannot find template files in Page Template folder {pageTemplateFolder.Path}");
                    }

                    foreach (ITemplateFile pageTemplateFile in pageTemplateFiles)
                    {
                        // If the content file is an HTML file
                        if (pageTemplateFile.Path.EndsWith(".html", StringComparison.InvariantCultureIgnoreCase))
                        {
                            // Set the HTML content
                            htmlContent = await GetFileContentAsync(pageTemplateFolder.Path, pageTemplateFile.Path.Substring(pageTemplateFile.Path.LastIndexOf('/') + 1));
                        }
                        else if (pageTemplateFile.Path.EndsWith(".css", StringComparison.InvariantCultureIgnoreCase))
                        {
                            // Set the CSS content
                            cssContent = await GetFileContentAsync(pageTemplateFolder.Path, pageTemplateFile.Path.Substring(pageTemplateFile.Path.LastIndexOf('/') + 1));
                        }

                        if (!String.IsNullOrEmpty(htmlContent) && !String.IsNullOrEmpty(cssContent))
                        {
                            // If we have both HTML and CSS content, just break the foreach loop
                            break;
                        }
                    }

                    // Update Page Template, if already exists
                    if (existingDbPageTemplates.TryGetValue(pageTemplateFolder.Path, out PageTemplate dbPageTemplate))
                    {
                        dbPageTemplate.Html = htmlContent;
                        dbPageTemplate.Css  = cssContent;
                        context.Entry(dbPageTemplate).State = EntityState.Modified;
                    }
                    else
                    {
                        // Add new Content Page
                        dbPageTemplate = new PageTemplate
                        {
                            Id   = pageTemplateFolder.Path,
                            Html = htmlContent,
                            Css  = cssContent,
                        };
                        context.Entry(dbPageTemplate).State = EntityState.Added;
                    }

                    existingDbPageTemplates.Remove(pageTemplateFolder.Path);
                }

                // Remove exceed categories
                var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
                foreach (var dbPageTemplate in existingDbPageTemplates)
                {
                    context.Entry(dbPageTemplate.Value).State = EntityState.Deleted;
                }

                await context.SaveChangesAsync();
            }
        }
        private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context)
        {
            var items = await _cloneProvider.GetAsync(folder.Path, WriteLog);

            // Read settings file
            ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME);

            if (settingsFile == null)
            {
                WriteLog($"Cannot find file {SETTINGS_NAME}");
                return(null);
            }

            var settings = await settingsFile.DownloadAsJsonAsync(new
            {
                @abstract         = "",
                sortOrder         = 0,
                categories        = new string[0],
                packageFile       = "",
                promoted          = false,
                sortOrderPromoted = 0,
                preview           = false,
                metadata          = new {
                    properties = new[] {
                        new {
                            name           = "",
                            caption        = "",
                            description    = "",
                            editor         = "",
                            editorSettings = "",
                        }
                    }
                }
            });

            // Read the package file
            ITemplateFile packageFile = items.FindFile(settings.packageFile);

            if (packageFile == null)
            {
                WriteLog($"Cannot find file {settings.packageFile}");
                return(null);
            }

            var package = new DomainModel.Package
            {
                Id         = Guid.NewGuid(),
                PackageUrl = packageFile.DownloadUri.ToString(),
                // New properties for Wave2
                Promoted           = settings.promoted,
                Preview            = settings.preview,
                TimesApplied       = 0,
                PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata),
                // New properties for Wave 5
                Abstract              = settings.@abstract,
                SortOrder             = settings.sortOrder,
                SortOrderPromoted     = settings.sortOrderPromoted,
                RepositoryRelativeUrl = folder.Path,
            };

            // Read the instructions.md and the provisioning.md files
            String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME);

            String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME);

            package.Instructions   = instructionsContent;
            package.ProvisionRecap = provisioningContent;

            // Find the categories to apply
            var dbCategories = settings.categories.Select(c =>
            {
                Category dbCategory = context.Categories.Find(c);
                if (dbCategory == null)
                {
                    WriteLog($"Cannot find category with id {c}");
                }

                return(dbCategory);
            }).ToArray();

            package.Categories.AddRange(dbCategories);

            // Find then author and fill his informations
            await FillAuthorAsync(package, packageFile.Path);

            // Open the package and set info
            await FillPackageAsync(package, packageFile);

            return(package);
        }
Esempio n. 21
0
        private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context)
        {
            var items = await _cloneProvider.GetAsync(folder.Path, WriteLog);

            // Read settings file
            ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME);

            if (settingsFile == null)
            {
                WriteLog($"Cannot find file {SETTINGS_NAME}");
                return(null);
            }

            #region Prepare the settings file and its outline

            var settings = await settingsFile.DownloadAsJsonAsync(new TemplateSettings());

            #endregion

            // Read the package file
            ITemplateFile packageFile = items.FindFile(settings.packageFile);
            if (packageFile == null)
            {
                WriteLog($"Cannot find file {settings.packageFile}");
                return(null);
            }

            #region Fix the Preview Image URLs

            // Fix the URLs in the metadata settings
            if (settings?.metadata?.displayInfo?.previewImages != null)
            {
                int previewImagesCount = settings.metadata.displayInfo.previewImages.Length;
                for (var n = 0; n < previewImagesCount; n++)
                {
                    var previewImageUrl = settings.metadata.displayInfo.previewImages[n].url;
                    settings.metadata.displayInfo.previewImages[n].url =
                        ChangeUri(packageFile.DownloadUri, previewImageUrl);
                }
            }

            if (settings?.metadata?.displayInfo?.detailItemCategories != null)
            {
                int detailItemCategoriesCount = settings.metadata.displayInfo.detailItemCategories.Length;
                for (var n = 0; n < detailItemCategoriesCount; n++)
                {
                    if (settings.metadata.displayInfo.detailItemCategories[n].items != null)
                    {
                        var detailItemCategoryItemsCount = settings.metadata.displayInfo.detailItemCategories[n].items.Length;
                        for (var m = 0; m < detailItemCategoryItemsCount; m++)
                        {
                            var detailItemCategoryItemPreviewImageUrl = settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage;
                            if (!String.IsNullOrEmpty(detailItemCategoryItemPreviewImageUrl))
                            {
                                settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage =
                                    ChangeUri(packageFile.DownloadUri, detailItemCategoryItemPreviewImageUrl);
                            }
                        }
                    }
                }
            }

            #endregion

            var package = new DomainModel.Package
            {
                Id         = Guid.NewGuid(),
                PackageUrl = packageFile.DownloadUri.ToString(),
                // New properties for Wave2
                Promoted           = settings.promoted,
                Preview            = settings.preview,
                TimesApplied       = 0,
                PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata),
                // New properties for Wave 5
                Abstract                   = settings.@abstract,
                SortOrder                  = settings.sortOrder,
                SortOrderPromoted          = settings.sortOrderPromoted,
                RepositoryRelativeUrl      = folder.Path,
                MatchingSiteBaseTemplateId = settings.matchingSiteBaseTemplateId,
                ForceNewSite               = settings.forceNewSite,
                Visible        = settings.visible,
                PageTemplateId = settings.metadata?.displayInfo?.pageTemplateId,
            };

            // Read the instructions.md and the provisioning.md files
            String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME);

            String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME);

            package.Instructions   = instructionsContent;
            package.ProvisionRecap = provisioningContent;

            // Find the categories to apply
            if (settings.categories != null && settings.categories.Length > 0)
            {
                var dbCategories = settings.categories.Select(c =>
                {
                    Category dbCategory = context.Categories.Find(c);
                    if (dbCategory == null)
                    {
                        WriteLog($"Cannot find category with id {c}");
                    }

                    return(dbCategory);
                }).ToArray();
                package.Categories.AddRange(dbCategories);
            }

            // Find the platforms to apply
            if (settings.platforms != null && settings.platforms.Length > 0)
            {
                var dbPlatforms = settings.platforms.Select(p =>
                {
                    Platform dbPlatform = context.Platforms.Find(p);
                    if (dbPlatform == null)
                    {
                        WriteLog($"Cannot find platform with id {p}");
                    }

                    return(dbPlatform);
                }).ToArray();
                package.TargetPlatforms.AddRange(dbPlatforms);
            }

            // Find then author and fill his informations
            await FillAuthorAsync(package, packageFile.Path);

            // Open the package and set info
            await FillPackageAsync(package, packageFile);

            return(package);
        }
        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();
            }
        }