public void Configure(string name, OpenIddictValidationOptions options)
        {
            // Ignore validation handler instances that don't correspond to the instance managed by the OpenID module.
            if (!string.Equals(name, OpenIddictValidationDefaults.AuthenticationScheme))
            {
                return;
            }

            var settings = GetValidationSettingsAsync().GetAwaiter().GetResult();

            if (settings == null)
            {
                return;
            }

            // If the tokens are issued by an authorization server located in a separate tenant,
            // resolve the isolated data protection provider associated with the specified tenant.
            if (!string.IsNullOrEmpty(settings.Tenant) &&
                !string.Equals(settings.Tenant, _shellSettings.Name))
            {
                _shellHost.GetScopeAsync(settings.Tenant).GetAwaiter().GetResult().UsingAsync(async scope =>
                {
                    // If the other tenant is released, ensure the current tenant is also restarted as it
                    // relies on a data protection provider whose lifetime is managed by the other tenant.
                    // To make sure the other tenant is not disposed before all the pending requests are
                    // processed by the current tenant, a tenant dependency is manually added.
                    scope.ShellContext.AddDependentShell(await _shellHost.GetOrCreateShellContextAsync(_shellSettings));

                    // Note: the data protection provider is always registered as a singleton and thus will
                    // survive the current scope, which is mainly used to prevent the other tenant from being
                    // released before we have a chance to declare the current tenant as a dependent tenant.
                    options.DataProtectionProvider = scope.ServiceProvider.GetDataProtectionProvider();
                }).GetAwaiter().GetResult();
            }

            // Don't allow the current tenant to choose the valid audiences, as this would
            // otherwise allow it to introspect tokens meant to be used with another tenant.
            options.Audiences.Add(OpenIdConstants.Prefixes.Tenant + _shellSettings.Name);

            CreateTenantScope(settings.Tenant).UsingAsync(async scope =>
            {
                var service = scope.ServiceProvider.GetService <IOpenIdServerService>();
                if (service == null)
                {
                    return;
                }

                var configuration = await GetServerSettingsAsync(service);
                if (configuration == null)
                {
                    return;
                }

                options.UseReferenceTokens = configuration.UseReferenceTokens;
            }).GetAwaiter().GetResult();
        }
예제 #2
0
        private async Task RunAsync(IEnumerable <ShellContext> runningShells, CancellationToken stoppingToken)
        {
            await GetShellsToRun(runningShells).ForEachAsync(async shell =>
            {
                var tenant = shell.Settings.Name;

                var schedulers = GetSchedulersToRun(tenant);

                _httpContextAccessor.HttpContext = shell.CreateHttpContext();

                foreach (var scheduler in schedulers)
                {
                    if (stoppingToken.IsCancellationRequested)
                    {
                        break;
                    }

                    var shellScope = await _shellHost.GetScopeAsync(shell.Settings);

                    if (shellScope.ShellContext.Pipeline == null)
                    {
                        break;
                    }

                    await shellScope.UsingAsync(async scope =>
                    {
                        var taskName = scheduler.Name;

                        var task = scope.ServiceProvider.GetServices <IBackgroundTask>().GetTaskByName(taskName);

                        if (task == null)
                        {
                            return;
                        }

                        try
                        {
                            Logger.LogInformation("Start processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);

                            scheduler.Run();
                            await task.DoWorkAsync(scope.ServiceProvider, stoppingToken);

                            Logger.LogInformation("Finished processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);
                        }

                        catch (Exception e)
                        {
                            Logger.LogError(e, "Error while processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);
                        }
                    });
                }
            });
        }
예제 #3
0
        private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep)
        {
            var shellScope = recipeStep.RecipeDescriptor.RequireNewScope
                ? await _shellHost.GetScopeAsync(_shellSettings)
                : ShellScope.Current;

            await shellScope.UsingAsync(async scope =>
            {
                var recipeStepHandlers = scope.ServiceProvider.GetServices <IRecipeStepHandler>();
                var scriptingManager   = scope.ServiceProvider.GetRequiredService <IScriptingManager>();

                // Substitutes the script elements by their actual values
                EvaluateJsonTree(scriptingManager, recipeStep, recipeStep.Step);

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name);
                }

                await _recipeEventHandlers.InvokeAsync((handler, recipeStep) => handler.RecipeStepExecutingAsync(recipeStep), recipeStep, _logger);

                foreach (var recipeStepHandler in recipeStepHandlers)
                {
                    await recipeStepHandler.ExecuteAsync(recipeStep);
                }

                await _recipeEventHandlers.InvokeAsync((handler, recipeStep) => handler.RecipeStepExecutedAsync(recipeStep), recipeStep, _logger);

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name);
                }
            });
        }
        public async Task <ContentItem> AssignGroupAsync(ContentItem profile, string groupContentItemId)
        {
            if (profile == null)
            {
                return(null);
            }

            using (var scope = await _shellHost.GetScopeAsync(_shellSettings))
            {
                var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>();

                profile.Alter <ProfileGroupedPart>(x => x.GroupContentItemId = groupContentItemId);

                profile.Apply(nameof(ProfileGroupedPart), profile.As <ProfileGroupedPart>());
                ContentExtensions.Apply(profile, profile);

                await contentManager.UpdateAsync(profile);

                await contentManager.PublishAsync(profile);

                await _session.CommitAsync();

                return(profile);
            }
        }
        private async Task <IList <ContentItem> > CreateUpdateAsync(IList <Posting> postings)
        {
            var contentItems = new List <ContentItem>();

            var shellScope = await _shellHost.GetScopeAsync(_shellSettings);

            await shellScope.UsingAsync(async scope =>
            {
                var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>();

                var postingContentItems = await GetAllAsync();

                // Remove old postings
                await RemoveAsync(contentManager, postingContentItems.Where(x => !postings.Any(y => y.Id == x.As <LeverPostingPart>().LeverId)).ToList());

                // Add/Update posings
                _logger.LogInformation($"Lever: Add/Update ({0}) content items", postings.Count);
                foreach (var posting in postings)
                {
                    var contentItem = postingContentItems.SingleOrDefault(x => x.As <LeverPostingPart>().LeverId == posting.Id);

                    // If not already exists create a new one
                    if (contentItem == null)
                    {
                        contentItems.Add(await CreateAsync(contentManager, posting));
                        continue;
                    }

                    contentItems.Add(await UpdateAsync(contentManager, contentItem, posting));
                }
            });

            return(contentItems);
        }
        public async Task PaymentSuccess(string tenantId, string tenantName, BillingPeriod billingPeriod, decimal amount, PaymentMethod paymentMethod, string planName)
        {
            //TODO: Should billing info be saved in default tenant only, in the tenant's db, or both ?

            // Retrieve settings for speficified tenant.
            var settingsList = await _shellSettingsManager.LoadSettingsAsync();

            if (settingsList.Any())
            {
                var settings   = settingsList.SingleOrDefault(s => string.Equals(s.Name, tenantName, StringComparison.OrdinalIgnoreCase));
                var shellScope = await _shellHost.GetScopeAsync(settings);

                await shellScope.UsingAsync(async scope =>
                {
                    //Check if billing history exists
                    var tenantBillingRepo    = scope.ServiceProvider.GetServices <ITenantBillingHistoryRepository>().FirstOrDefault();
                    var tenantBillingHistory = await tenantBillingRepo.GetTenantBillingDetailsByNameAsync(tenantName);
                    if (tenantBillingHistory == null)
                    {
                        tenantBillingHistory = new TenantBillingDetails(tenantId, tenantName, planName);
                    }

                    if (tenantBillingHistory.IsNewPaymentMethod(paymentMethod))
                    {
                        tenantBillingHistory.AddNewPaymentMethod(paymentMethod);
                    }

                    tenantBillingHistory.AddMonthlyBill(billingPeriod, PaymentStatus.Success, amount, paymentMethod.CreditCardInfo);



                    await tenantBillingRepo.CreateAsync(tenantBillingHistory);
                });
            }
        }
예제 #7
0
        private async Task <bool> IsTokenValid(string token)
        {
            try
            {
                using (var scope = await _shellHost.GetScopeAsync(ShellHelper.DefaultShellName))
                {
                    var dataProtectionProvider = scope.ServiceProvider.GetRequiredService <IDataProtectionProvider>();
                    var dataProtector          = dataProtectionProvider.CreateProtector("Tokens").ToTimeLimitedDataProtector();

                    var tokenValue = dataProtector.Unprotect(token, out var expiration);

                    if (_clock.UtcNow < expiration.ToUniversalTime())
                    {
                        if (_shellSettings["Secret"] == tokenValue)
                        {
                            return(true);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in decrypting the token");
            }

            return(false);
        }
예제 #8
0
        public async Task RunRecipeAsync(IShellHost shellHost, string recipeName, string recipePath)
        {
            var shellScope = await shellHost.GetScopeAsync(TenantName);

            await shellScope.UsingAsync(async scope =>
            {
                var shellFeaturesManager = scope.ServiceProvider.GetRequiredService <IShellFeaturesManager>();
                var recipeHarvesters     = scope.ServiceProvider.GetRequiredService <IEnumerable <IRecipeHarvester> >();
                var recipeExecutor       = scope.ServiceProvider.GetRequiredService <IRecipeExecutor>();

                var recipeCollections = await Task.WhenAll(
                    recipeHarvesters.Select(recipe => recipe.HarvestRecipesAsync()));

                var recipes = recipeCollections.SelectMany(recipeCollection => recipeCollection);
                var recipe  = recipes
                              .FirstOrDefault(recipe => recipe.RecipeFileInfo.Name == recipeName && recipe.BasePath == recipePath);

                var executionId = Guid.NewGuid().ToString("n");

                await recipeExecutor.ExecuteAsync(
                    executionId,
                    recipe,
                    new Dictionary <string, object>(),
                    CancellationToken.None);
            });
        }
예제 #9
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all ShellContext are loaded and available.
            await _shellHost.InitializeAsync();

            var shellSettings = _runningShellTable.Match(httpContext);

            // We only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                if (shellSettings.State == TenantState.Initializing)
                {
                    httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10");
                    httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
                    await httpContext.Response.WriteAsync("The requested tenant is currently initializing.");

                    return;
                }

                // Makes 'RequestServices' aware of the current 'ShellScope'.
                httpContext.UseShellScopeServices();

                var shellScope = await _shellHost.GetScopeAsync(shellSettings);

                // Holds the 'ShellContext' for the full request.
                httpContext.Features.Set(new ShellContextFeature
                {
                    ShellContext     = shellScope.ShellContext,
                    OriginalPath     = httpContext.Request.Path,
                    OriginalPathBase = httpContext.Request.PathBase
                });

                await shellScope.UsingAsync(scope => _next.Invoke(httpContext));
            }
        }
예제 #10
0
        private ShellScope CreateTenantScope(string tenant)
        {
            // Optimization: if the specified name corresponds to the current tenant, use the current 'ShellScope'.
            if (string.IsNullOrEmpty(tenant) || string.Equals(tenant, _shellSettings.Name))
            {
                return(ShellScope.Current);
            }

            return(_shellHost.GetScopeAsync(tenant).GetAwaiter().GetResult());
        }
예제 #11
0
        public async Task <ActionResult> IndexPOST(CustomSetupViewModel model)
        {
            if (!await IsValidRequest(model.Secret))
            {
                return(BadRequest(S["Error with tenant setup link. Please contact support to issue a new link"]));
            }
            if (!IsModelValid(model))
            {
                return(View(model));
            }

            var setupContext = await CreateSetupContext(model.SiteName, model.SiteTimeZone);

            await _setupService.SetupAsync(setupContext);

            // Check if a component in the Setup failed
            if (setupContext.Errors.Any())
            {
                foreach (var error in setupContext.Errors)
                {
                    ModelState.AddModelError(error.Key, error.Value);
                }
                return(View(model));
            }

            var shellSetting = await _shellSettingsManager.LoadSettingsAsync(_shellSettings.Name);

            await(await _shellHost.GetScopeAsync(shellSetting)).UsingAsync(async scope =>
            {
                void reportError(string key, string message)
                {
                    setupContext.Errors[key] = message;
                }

                // Invoke modules to react to the custom setup event
                var customsetupEventHandlers = scope.ServiceProvider.GetServices <ICustomTenantSetupEventHandler>();
                var logger = scope.ServiceProvider.GetRequiredService <ILogger <CustomSetupController> >();
                await customsetupEventHandlers.InvokeAsync(x => x.Setup(model.Email, model.Password, "CourseAdmin", reportError), logger);
            });

            return(Redirect($"~/{_adminOptions.AdminUrlPrefix}"));
        }
예제 #12
0
        private IServiceScope CreateTenantScope(string tenant)
        {
            // Optimization: if the specified name corresponds to the current tenant, use the
            // service provider injected via the constructor instead of using the host APIs.
            if (string.IsNullOrEmpty(tenant) || string.Equals(tenant, _shellSettings.Name, StringComparison.Ordinal))
            {
                return(_serviceProvider.CreateScope());
            }

            return(_shellHost.GetScopeAsync(tenant).GetAwaiter().GetResult());
        }
예제 #13
0
        public async Task ResetLuceneIndiciesAsync(IShellHost shellHost, string indexName)
        {
            var shellScope = await shellHost.GetScopeAsync(TenantName);

            await shellScope.UsingAsync(async scope =>
            {
                var luceneIndexSettingsService = scope.ServiceProvider.GetRequiredService <LuceneIndexSettingsService>();
                var luceneIndexingService      = scope.ServiceProvider.GetRequiredService <LuceneIndexingService>();

                var luceneIndexSettings = await luceneIndexSettingsService.GetSettingsAsync(indexName);

                luceneIndexingService.ResetIndex(indexName);
                await luceneIndexingService.ProcessContentItemsAsync(indexName);
            });
        }
예제 #14
0
        public async Task <string> Index()
        {
            var scope = await _shellHost.GetScopeAsync("Tenant1");

            var result = 0;
            await scope.UsingAsync(async scope =>
            {
                var siteService = scope.ServiceProvider.GetRequiredService <ISiteService>();
                var site        = await siteService.GetSiteSettingsAsync();
                var mySettings  = site.As <MySettings>();

                result = mySettings.IntProperty;
            });

            return($"My int property is: {result}");
        }
예제 #15
0
        public async Task <ContentItem> CreateAsync(IUser user)
        {
            using (var scope = await _shellHost.GetScopeAsync(_shellSettings))
            {
                var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>();
                var contentItem    = await contentManager.NewAsync(Constants.ContentTypeName);

                var profile = contentItem.As <ProfilePart>();
                profile.UserIdentifier = await _userManager.GetUserIdAsync(user);

                profile.Apply();

                contentItem.DisplayText = user.UserName;

                contentItem.Apply(nameof(ProfilePart), profile);
                ContentExtensions.Apply(contentItem, contentItem);

                await contentManager.CreateAsync(contentItem);

                await contentManager.PublishAsync(contentItem);

                return(contentItem);
            }
        }
        public async Task <ImmutableArray <ValidationResult> > ValidateSettingsAsync(OpenIdValidationSettings settings)
        {
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            var results = ImmutableArray.CreateBuilder <ValidationResult>();

            if (!(settings.Authority == null ^ string.IsNullOrEmpty(settings.Tenant)))
            {
                results.Add(new ValidationResult(S["Either a tenant or an authority must be registered."], new[]
                {
                    nameof(settings.Authority),
                    nameof(settings.Tenant)
                }));
            }

            if (settings.Authority != null)
            {
                if (!settings.Authority.IsAbsoluteUri || !settings.Authority.IsWellFormedOriginalString())
                {
                    results.Add(new ValidationResult(S["The specified authority is not valid."], new[]
                    {
                        nameof(settings.Authority)
                    }));
                }

                if (!string.IsNullOrEmpty(settings.Authority.Query) || !string.IsNullOrEmpty(settings.Authority.Fragment))
                {
                    results.Add(new ValidationResult(S["The authority cannot contain a query string or a fragment."], new[]
                    {
                        nameof(settings.Authority)
                    }));
                }
            }

            if (!string.IsNullOrEmpty(settings.Tenant) && !string.IsNullOrEmpty(settings.Audience))
            {
                results.Add(new ValidationResult(S["No audience can be set when using another tenant."], new[]
                {
                    nameof(settings.Audience)
                }));
            }

            if (settings.Authority != null && string.IsNullOrEmpty(settings.Audience))
            {
                results.Add(new ValidationResult(S["An audience must be set when configuring the authority."], new[]
                {
                    nameof(settings.Audience)
                }));
            }

            if (!string.IsNullOrEmpty(settings.Audience) &&
                settings.Audience.StartsWith(OpenIdConstants.Prefixes.Tenant, StringComparison.OrdinalIgnoreCase))
            {
                results.Add(new ValidationResult(S["The audience cannot start with the special 'oct:' prefix."], new[]
                {
                    nameof(settings.Audience)
                }));
            }

            // If a tenant was specified, ensure it is valid, that the OpenID server feature
            // was enabled and that at least a scope linked with the current tenant exists.
            if (!string.IsNullOrEmpty(settings.Tenant) &&
                !string.Equals(settings.Tenant, _shellSettings.Name))
            {
                if (!_shellHost.TryGetSettings(settings.Tenant, out var shellSettings))
                {
                    results.Add(new ValidationResult(S["The specified tenant is not valid."]));
                }
                else
                {
                    var shellScope = await _shellHost.GetScopeAsync(shellSettings);

                    await shellScope.UsingAsync(async scope =>
                    {
                        var manager = scope.ServiceProvider.GetService <IOpenIdScopeManager>();
                        if (manager == null)
                        {
                            results.Add(new ValidationResult(S["The specified tenant is not valid."], new[]
                            {
                                nameof(settings.Tenant)
                            }));
                        }
                        else
                        {
                            var resource = OpenIdConstants.Prefixes.Tenant + _shellSettings.Name;
                            var scopes   = await manager.FindByResourceAsync(resource);
                            if (scopes.IsDefaultOrEmpty)
                            {
                                results.Add(new ValidationResult(S["No appropriate scope was found."], new[]
                                {
                                    nameof(settings.Tenant)
                                }));
                            }
                        }
                    });
                }
            }

            return(results.ToImmutable());
        }
        public async Task ProcessContentItemsAsync()
        {
            // TODO: Lock over the filesystem in case two instances get a command to rebuild the index concurrently.

            var allIndices = new Dictionary <string, int>();

            // Find the lowest task id to process
            int lastTaskId = int.MaxValue;

            foreach (var indexName in _indexManager.List())
            {
                var taskId = _indexingState.GetLastTaskId(indexName);
                lastTaskId = Math.Min(lastTaskId, taskId);
                allIndices.Add(indexName, taskId);
            }

            if (!allIndices.Any())
            {
                return;
            }

            IndexingTask[] batch;

            do
            {
                // Create a scope for the content manager
                using (var scope = await _shellHost.GetScopeAsync(_shellSettings))
                {
                    // Load the next batch of tasks
                    batch = (await _indexingTaskManager.GetIndexingTasksAsync(lastTaskId, BatchSize)).ToArray();

                    if (!batch.Any())
                    {
                        break;
                    }

                    foreach (var task in batch)
                    {
                        var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>();
                        var indexHandlers  = scope.ServiceProvider.GetServices <IContentItemIndexHandler>();

                        foreach (var index in allIndices)
                        {
                            // TODO: ignore if this index is not configured for the content type

                            if (index.Value < task.Id)
                            {
                                _indexManager.DeleteDocuments(index.Key, new string[] { task.ContentItemId });
                            }
                        }

                        if (task.Type == IndexingTaskTypes.Update)
                        {
                            var contentItem = await contentManager.GetAsync(task.ContentItemId);

                            if (contentItem == null)
                            {
                                continue;
                            }

                            var context = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType });

                            // Update the document from the index if its lastIndexId is smaller than the current task id.
                            await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(context), Logger);

                            foreach (var index in allIndices)
                            {
                                if (index.Value < task.Id)
                                {
                                    _indexManager.StoreDocuments(index.Key, new DocumentIndex[] { context.DocumentIndex });
                                }
                            }
                        }
                    }


                    // Update task ids
                    lastTaskId = batch.Last().Id;

                    foreach (var index in allIndices)
                    {
                        if (index.Value < lastTaskId)
                        {
                            _indexingState.SetLastTaskId(index.Key, lastTaskId);
                        }
                    }

                    _indexingState.Update();
                }
            } while (batch.Length == BatchSize);
        }
예제 #18
0
        public async Task <string> SetupInternalAsync(SetupContext context)
        {
            string executionId;

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name);
            }

            string[] hardcoded =
            {
                _applicationName,
                "SeedModules.Features",
                "OrchardCore.Scripting",
                "SeedModules.Recipes",
            };

            context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList();

            context.ShellSettings.State = TenantState.Initializing;

            var shellSettings = new ShellSettings(context.ShellSettings);

            if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"]))
            {
                shellSettings["DatabaseProvider"] = context.DatabaseProvider;
                shellSettings["ConnectionString"] = context.DatabaseConnectionString;
                shellSettings["TablePrefix"]      = context.DatabaseTablePrefix;
            }

            if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"]))
            {
                throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required");
            }

            var shellDescriptor = new ShellDescriptor
            {
                Features = context.EnabledFeatures.Select(id => new ShellFeature {
                    Id = id
                }).ToList()
            };

            using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor))
            {
                await shellContext.CreateScope().UsingAsync(async scope =>
                {
                    IStore store;

                    try
                    {
                        store = scope.ServiceProvider.GetRequiredService <IStore>();
                        // await store.InitializeAsync(scope.ServiceProvider);
                    }
                    catch (Exception e)
                    {
                        // 表已存在或数据库问题

                        _logger.LogError(e, "An error occurred while initializing the datastore.");
                        context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]);
                        return;
                    }

                    await scope
                    .ServiceProvider
                    .GetService <IShellDescriptorManager>()
                    .UpdateShellDescriptorAsync(0,
                                                shellContext.Blueprint.Descriptor.Features,
                                                shellContext.Blueprint.Descriptor.Parameters);
                });

                if (context.Errors.Any())
                {
                    return(null);
                }

                executionId = Guid.NewGuid().ToString("n");

                var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>();

                await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new
                {
                    SiteName                 = context.SiteName,
                    AdminUsername            = context.AdminUsername,
                    AdminEmail               = context.AdminEmail,
                    AdminPassword            = context.AdminPassword,
                    DatabaseProvider         = context.DatabaseProvider,
                    DatabaseConnectionString = context.DatabaseConnectionString,
                    DatabaseTablePrefix      = context.DatabaseTablePrefix
                },
                                                  _applicationLifetime.ApplicationStopping);
            }


            await(await _shellHost.GetScopeAsync(shellSettings)).UsingAsync(async scope =>
            {
                void reportError(string key, string message)
                {
                    context.Errors[key] = message;
                }

                // Invoke modules to react to the setup event
                var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>();
                var logger             = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >();

                await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup(
                                                         context.SiteName,
                                                         context.AdminUsername,
                                                         context.AdminEmail,
                                                         context.AdminPassword,
                                                         context.DatabaseProvider,
                                                         context.DatabaseConnectionString,
                                                         context.DatabaseTablePrefix,
                                                         context.SiteTimeZone,
                                                         reportError
                                                         ), context, logger);
            });

            if (context.Errors.Any())
            {
                return(executionId);
            }


            shellSettings.State = TenantState.Running;
            await _shellHost.UpdateShellSettingsAsync(shellSettings);

            return(executionId);
        }
예제 #19
0
        // NB: Async void should be avoided; it should only be used for event handlers.Timer.Elapsed is an event handler.So, it's not necessarily wrong here.
        // c.f. http://stackoverflow.com/questions/25007670/using-async-await-inside-the-timer-elapsed-event-handler-within-a-windows-servic
        private async void DoWorkAsync(object group)
        {
            // DoWork needs to be re-entrant as Timer may call the callback before the previous callback has returned.
            // So, because a task may take longer than the period itself, DoWork needs to check if it's still running.

            var groupName = group as string ?? "";

            // Because the execution flow has been suppressed before creating timers, here the 'HttpContext'
            // is always null and can be replaced by a new fake 'HttpContext' without overriding anything.
            _httpContextAccessor.HttpContext = new DefaultHttpContext();

            foreach (var task in _tasks[groupName])
            {
                var taskName = task.GetType().FullName;

                using (var scope = await _orchardHost.GetScopeAsync(_shellSettings))
                {
                    try
                    {
                        if (_states[task] != BackgroundTaskState.Idle)
                        {
                            return;
                        }

                        lock (_states)
                        {
                            // Ensure Terminate() was not called before
                            if (_states[task] != BackgroundTaskState.Idle)
                            {
                                return;
                            }

                            _states[task] = BackgroundTaskState.Running;
                        }

                        if (Logger.IsEnabled(LogLevel.Information))
                        {
                            Logger.LogInformation("Start processing background task '{BackgroundTaskName}'.", taskName);
                        }

                        await task.DoWorkAsync(scope.ServiceProvider, _applicationLifetime.ApplicationStopping);

                        if (Logger.IsEnabled(LogLevel.Information))
                        {
                            Logger.LogInformation("Finished processing background task '{BackgroundTaskName}'.", taskName);
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex, "Error while processing background task '{BackgroundTaskName}'", taskName);
                    }
                    finally
                    {
                        lock (_states)
                        {
                            // Ensure Terminate() was not called during the task
                            if (_states[task] != BackgroundTaskState.Stopped)
                            {
                                _states[task] = BackgroundTaskState.Idle;
                            }
                        }
                    }
                }
            }
        }
 /// <summary>
 /// Creates a standalone service scope that can be used to resolve local services.
 /// </summary>
 /// <param name="tenant">The tenant name related to the service scope to get.</param>
 public static Task <ShellScope> GetScopeAsync(this IShellHost shellHost, string tenant)
 {
     return(shellHost.GetScopeAsync(shellHost.GetSettings(tenant)));
 }
예제 #21
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all ShellContext are loaded and available.
            await _shellHost.InitializeAsync();

            var shellSettings = _runningShellTable.Match(httpContext);

            // We only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                if (shellSettings.State == TenantState.Initializing)
                {
                    httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10");
                    httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
                    await httpContext.Response.WriteAsync("The requested tenant is currently initializing.");

                    return;
                }

                var hasPendingTasks = false;

                // We need to get a scope and the ShellContext that created it
                var(scope, shellContext) = await _shellHost.GetScopeAndContextAsync(shellSettings);

                using (scope)
                {
                    // Register the shell context as a custom feature
                    httpContext.Features.Set(shellContext);

                    if (!shellContext.IsActivated)
                    {
                        var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1));

                        await semaphore.WaitAsync();

                        try
                        {
                            // The tenant gets activated here
                            if (!shellContext.IsActivated)
                            {
                                using (var activatingScope = await _shellHost.GetScopeAsync(shellSettings))
                                {
                                    var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>();

                                    foreach (var tenantEvent in tenantEvents)
                                    {
                                        await tenantEvent.ActivatingAsync();
                                    }

                                    foreach (var tenantEvent in tenantEvents.Reverse())
                                    {
                                        await tenantEvent.ActivatedAsync();
                                    }
                                }

                                shellContext.IsActivated = true;
                            }
                        }
                        finally
                        {
                            semaphore.Release();
                            _semaphores.TryRemove(shellSettings.Name, out semaphore);
                        }
                    }

                    await _next.Invoke(httpContext);

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                    hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false;
                }

                // Create a new scope only if there are pending tasks
                if (hasPendingTasks)
                {
                    using (var pendingScope = await _shellHost.GetScopeAsync(shellSettings))
                    {
                        if (pendingScope != null)
                        {
                            var deferredTaskEngine = pendingScope.ServiceProvider.GetService <IDeferredTaskEngine>();
                            var context            = new DeferredTaskContext(pendingScope.ServiceProvider);
                            await deferredTaskEngine.ExecuteTasksAsync(context);
                        }
                    }
                }
            }
        }
예제 #22
0
        private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep)
        {
            IServiceScope    scope;
            ShellContext     shellContext;
            IServiceProvider serviceProvider;

            if (recipeStep.RecipeDescriptor.RequireNewScope)
            {
                (scope, shellContext) = await _shellHost.GetScopeAndContextAsync(_shellSettings);

                serviceProvider = scope.ServiceProvider;
            }
            else
            {
                (scope, shellContext) = (null, null);
                serviceProvider       = _httpContextAccessor.HttpContext.RequestServices;
            }

            using (scope)
            {
                if (recipeStep.RecipeDescriptor.RequireNewScope && !shellContext.IsActivated)
                {
                    using (var activatingScope = shellContext.CreateScope())
                    {
                        var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>();

                        foreach (var tenantEvent in tenantEvents)
                        {
                            await tenantEvent.ActivatingAsync();
                        }

                        foreach (var tenantEvent in tenantEvents.Reverse())
                        {
                            await tenantEvent.ActivatedAsync();
                        }
                    }

                    shellContext.IsActivated = true;
                }

                var recipeStepHandlers = serviceProvider.GetServices <IRecipeStepHandler>();
                var scriptingManager   = serviceProvider.GetRequiredService <IScriptingManager>();
                scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider);

                // Substitutes the script elements by their actual values
                EvaluateScriptNodes(recipeStep, scriptingManager);

                foreach (var recipeStepHandler in recipeStepHandlers)
                {
                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name);
                    }

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger);

                    await recipeStepHandler.ExecuteAsync(recipeStep);

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger);

                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name);
                    }
                }
            }

            // E.g if we run migrations defined in a recipe.
            if (!recipeStep.RecipeDescriptor.RequireNewScope)
            {
                return;
            }

            // The recipe execution might have invalidated the shell by enabling new features,
            // so the deferred tasks need to run on an updated shell context if necessary.
            using (var localScope = await _shellHost.GetScopeAsync(_shellSettings))
            {
                var deferredTaskEngine = localScope.ServiceProvider.GetService <IDeferredTaskEngine>();

                // The recipe might have added some deferred tasks to process
                if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                {
                    var taskContext = new DeferredTaskContext(localScope.ServiceProvider);
                    await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                }
            }
        }
예제 #23
0
        private async Task <string> SetupInternalAsync(SetupContext context)
        {
            string executionId;

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name);
            }

            // Features to enable for Setup
            string[] hardcoded =
            {
                _applicationName,
                "OrchardCore.Features",
                "OrchardCore.Scripting",
                "OrchardCore.Recipes"
            };

            context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList();

            // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
            context.ShellSettings.State = TenantState.Initializing;

            // Due to database collation we normalize the userId to lower invariant.
            // During setup there are no users so we do not need to check unicity.
            context.AdminUserId = _setupUserIdGenerator.GenerateUniqueId().ToLowerInvariant();

            var shellSettings = new ShellSettings(context.ShellSettings);

            if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"]))
            {
                shellSettings["DatabaseProvider"] = context.DatabaseProvider;
                shellSettings["ConnectionString"] = context.DatabaseConnectionString;
                shellSettings["TablePrefix"]      = context.DatabaseTablePrefix;
            }

            if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"]))
            {
                throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required");
            }

            // Creating a standalone environment based on a "minimum shell descriptor".
            // In theory this environment can be used to resolve any normal components by interface, and those
            // components will exist entirely in isolation - no crossover between the safemode container currently in effect
            // It is used to initialize the database before the recipe is run.

            var shellDescriptor = new ShellDescriptor
            {
                Features = context.EnabledFeatures.Select(id => new ShellFeature {
                    Id = id
                }).ToList()
            };

            using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor))
            {
                await shellContext.CreateScope().UsingServiceScopeAsync(async scope =>
                {
                    IStore store;

                    try
                    {
                        store = scope.ServiceProvider.GetRequiredService <IStore>();
                    }
                    catch (Exception e)
                    {
                        // Tables already exist or database was not found

                        // The issue is that the user creation needs the tables to be present,
                        // if the user information is not valid, the next POST will try to recreate the
                        // tables. The tables should be rolled back if one of the steps is invalid,
                        // unless the recipe is executing?

                        _logger.LogError(e, "An error occurred while initializing the datastore.");
                        context.Errors.Add("DatabaseProvider", S["An error occurred while initializing the datastore: {0}", e.Message]);
                        return;
                    }

                    // Create the "minimum shell descriptor"
                    await scope
                    .ServiceProvider
                    .GetService <IShellDescriptorManager>()
                    .UpdateShellDescriptorAsync(0,
                                                shellContext.Blueprint.Descriptor.Features,
                                                shellContext.Blueprint.Descriptor.Parameters);
                });

                if (context.Errors.Any())
                {
                    return(null);
                }

                executionId = Guid.NewGuid().ToString("n");

                var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>();

                await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new
                {
                    context.SiteName,
                    context.AdminUsername,
                    context.AdminUserId,
                    context.AdminEmail,
                    context.AdminPassword,
                    context.DatabaseProvider,
                    context.DatabaseConnectionString,
                    context.DatabaseTablePrefix,
                    Properties = context.Properties
                },
                                                  _applicationLifetime.ApplicationStopping);
            }

            // Reloading the shell context as the recipe has probably updated its features
            await(await _shellHost.GetScopeAsync(shellSettings)).UsingAsync(async scope =>
            {
                void reportError(string key, string message)
                {
                    context.Errors[key] = message;
                }

                // Invoke modules to react to the setup event
                var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>();
                var logger             = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >();

                await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup(
                                                         context.SiteName,
                                                         context.AdminUsername,
                                                         context.AdminUserId,
                                                         context.AdminEmail,
                                                         context.AdminPassword,
                                                         context.DatabaseProvider,
                                                         context.DatabaseConnectionString,
                                                         context.DatabaseTablePrefix,
                                                         context.SiteTimeZone,
                                                         reportError,
                                                         context.Properties
                                                         ), context, logger);
            });

            if (context.Errors.Any())
            {
                return(executionId);
            }

            // Update the shell state
            shellSettings.State = TenantState.Running;
            await _shellHost.UpdateShellSettingsAsync(shellSettings);

            return(executionId);
        }
예제 #24
0
        public async Task ProcessContentItemsAsync(string indexName = default)
        {
            // TODO: Lock over the filesystem in case two instances get a command to rebuild the index concurrently.
            var allIndices = new Dictionary <string, int>();
            var lastTaskId = Int32.MaxValue;
            IEnumerable <LuceneIndexSettings> indexSettingsList = null;

            if (String.IsNullOrEmpty(indexName))
            {
                indexSettingsList = await _luceneIndexSettingsService.GetSettingsAsync();

                if (!indexSettingsList.Any())
                {
                    return;
                }

                // Find the lowest task id to process
                foreach (var indexSetting in indexSettingsList)
                {
                    var taskId = _indexingState.GetLastTaskId(indexSetting.IndexName);
                    lastTaskId = Math.Min(lastTaskId, taskId);
                    allIndices.Add(indexSetting.IndexName, taskId);
                }
            }
            else
            {
                var settings = await _luceneIndexSettingsService.GetSettingsAsync(indexName);

                if (settings == null)
                {
                    return;
                }

                indexSettingsList = new LuceneIndexSettings[1] {
                    settings
                }.AsEnumerable();

                var taskId = _indexingState.GetLastTaskId(indexName);
                lastTaskId = Math.Min(lastTaskId, taskId);
                allIndices.Add(indexName, taskId);
            }

            if (allIndices.Count == 0)
            {
                return;
            }

            var batch = Array.Empty <IndexingTask>();

            do
            {
                // Create a scope for the content manager
                var shellScope = await _shellHost.GetScopeAsync(_shellSettings);

                await shellScope.UsingAsync(async scope =>
                {
                    // Load the next batch of tasks
                    batch = (await _indexingTaskManager.GetIndexingTasksAsync(lastTaskId, BatchSize)).ToArray();

                    if (!batch.Any())
                    {
                        return;
                    }

                    var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>();
                    var indexHandlers  = scope.ServiceProvider.GetServices <IContentItemIndexHandler>();

                    // Pre-load all content items to prevent SELECT N+1
                    var updatedContentItemIds = batch
                                                .Where(x => x.Type == IndexingTaskTypes.Update)
                                                .Select(x => x.ContentItemId)
                                                .ToArray();

                    var allPublished = await contentManager.GetAsync(updatedContentItemIds);
                    var allLatest    = await contentManager.GetAsync(updatedContentItemIds, latest: true);

                    // Group all DocumentIndex by index to batch update them
                    var updatedDocumentsByIndex = new Dictionary <string, List <DocumentIndex> >();

                    foreach (var index in allIndices)
                    {
                        updatedDocumentsByIndex[index.Key] = new List <DocumentIndex>();
                    }

                    if (indexName != null)
                    {
                        indexSettingsList = indexSettingsList.Where(x => x.IndexName == indexName);
                    }

                    var needLatest    = indexSettingsList.FirstOrDefault(x => x.IndexLatest) != null;
                    var needPublished = indexSettingsList.FirstOrDefault(x => !x.IndexLatest) != null;

                    var settingsByIndex = indexSettingsList.ToDictionary(x => x.IndexName, x => x);

                    foreach (var task in batch)
                    {
                        if (task.Type == IndexingTaskTypes.Update)
                        {
                            BuildIndexContext publishedIndexContext = null, latestIndexContext = null;

                            if (needPublished)
                            {
                                var contentItem = await contentManager.GetAsync(task.ContentItemId);
                                if (contentItem != null)
                                {
                                    publishedIndexContext = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType });
                                    await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(publishedIndexContext), _logger);
                                }
                            }

                            if (needLatest)
                            {
                                var contentItem = await contentManager.GetAsync(task.ContentItemId, VersionOptions.Latest);
                                if (contentItem != null)
                                {
                                    latestIndexContext = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType });
                                    await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(latestIndexContext), _logger);
                                }
                            }

                            // Update the document from the index if its lastIndexId is smaller than the current task id.
                            foreach (var index in allIndices)
                            {
                                if (index.Value >= task.Id || !settingsByIndex.TryGetValue(index.Key, out var settings))
                                {
                                    continue;
                                }

                                var context = !settings.IndexLatest ? publishedIndexContext : latestIndexContext;

                                //We index only if we actually found a content item in the database
                                if (context == null)
                                {
                                    //TODO purge these content items from IndexingTask table
                                    continue;
                                }

                                var cultureAspect        = await contentManager.PopulateAspectAsync <CultureAspect>(context.ContentItem);
                                var culture              = cultureAspect.HasCulture ? cultureAspect.Culture.Name : null;
                                var ignoreIndexedCulture = settings.Culture == "any" ? false : culture != settings.Culture;

                                // Ignore if the content item content type or culture is not indexed in this index
                                if (!settings.IndexedContentTypes.Contains(context.ContentItem.ContentType) || ignoreIndexedCulture)
                                {
                                    continue;
                                }

                                updatedDocumentsByIndex[index.Key].Add(context.DocumentIndex);
                            }
                        }
                    }

                    // Delete all the existing documents
                    foreach (var index in updatedDocumentsByIndex)
                    {
                        var deletedDocuments = updatedDocumentsByIndex[index.Key].Select(x => x.ContentItemId);

                        await _indexManager.DeleteDocumentsAsync(index.Key, deletedDocuments);
                    }

                    // Submits all the new documents to the index
                    foreach (var index in updatedDocumentsByIndex)
                    {
                        await _indexManager.StoreDocumentsAsync(index.Key, updatedDocumentsByIndex[index.Key]);
                    }

                    // Update task ids
                    lastTaskId = batch.Last().Id;

                    foreach (var indexStatus in allIndices)
                    {
                        if (indexStatus.Value < lastTaskId)
                        {
                            _indexingState.SetLastTaskId(indexStatus.Key, lastTaskId);
                        }
                    }

                    _indexingState.Update();
                }, activateShell : false);
            } while (batch.Length == BatchSize);
        }
        private async Task RunAsync(IEnumerable <ShellContext> runningShells, CancellationToken stoppingToken)
        {
            await GetShellsToRun(runningShells).ForEachAsync(async shell =>
            {
                var tenant = shell.Settings.Name;

                var schedulers = GetSchedulersToRun(tenant);

                _httpContextAccessor.HttpContext = shell.CreateHttpContext();

                foreach (var scheduler in schedulers)
                {
                    if (stoppingToken.IsCancellationRequested)
                    {
                        break;
                    }

                    var shellScope = await _shellHost.GetScopeAsync(shell.Settings);

                    if (!_options.ShellWarmup && shellScope.ShellContext.Pipeline == null)
                    {
                        break;
                    }

                    var distributedLock = shellScope.ShellContext.ServiceProvider.GetRequiredService <IDistributedLock>();

                    // Try to acquire a lock before using the scope, so that a next process gets the last committed data.
                    (var locker, var locked) = await distributedLock.TryAcquireBackgroundTaskLockAsync(scheduler.Settings);
                    if (!locked)
                    {
                        _logger.LogInformation("Timeout to acquire a lock on background task '{TaskName}' on tenant '{TenantName}'.", scheduler.Name, tenant);
                        return;
                    }

                    await using var acquiredLock = locker;

                    await shellScope.UsingAsync(async scope =>
                    {
                        var taskName = scheduler.Name;

                        var task = scope.ServiceProvider.GetServices <IBackgroundTask>().GetTaskByName(taskName);

                        if (task == null)
                        {
                            return;
                        }

                        var siteService = scope.ServiceProvider.GetService <ISiteService>();
                        if (siteService != null)
                        {
                            try
                            {
                                _httpContextAccessor.HttpContext.SetBaseUrl((await siteService.GetSiteSettingsAsync()).BaseUrl);
                            }
                            catch (Exception ex) when(!ex.IsFatal())
                            {
                                _logger.LogError(ex, "Error while getting the base url from the site settings of the tenant '{TenantName}'.", tenant);
                            }
                        }

                        var context  = new BackgroundTaskEventContext(taskName, scope);
                        var handlers = scope.ServiceProvider.GetServices <IBackgroundTaskEventHandler>();

                        await handlers.InvokeAsync((handler, context, token) => handler.ExecutingAsync(context, token), context, stoppingToken, _logger);

                        try
                        {
                            _logger.LogInformation("Start processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);

                            scheduler.Run();
                            await task.DoWorkAsync(scope.ServiceProvider, stoppingToken);

                            _logger.LogInformation("Finished processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);
                        }
                        catch (Exception ex) when(!ex.IsFatal())
                        {
                            _logger.LogError(ex, "Error while processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant);
                            context.Exception = ex;

                            await scope.HandleExceptionAsync(ex);
                        }

                        await handlers.InvokeAsync((handler, context, token) => handler.ExecutedAsync(context, token), context, stoppingToken, _logger);
                    });
                }
            });
        }