Beispiel #1
0
        /// <summary>
        /// Tries to creates a standalone service scope that can be used to resolve local services and
        /// replaces <see cref="HttpContext.RequestServices"/> with it.
        /// </summary>
        /// <param name="tenant">The tenant name related to the service scope to get.</param>
        /// <returns>An associated scope if the tenant name is valid, otherwise null.</returns>
        /// <remarks>
        /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state.
        /// </remarks>
        public static async Task <IServiceScope> TryGetScopeAsync(this IShellHost shellHost, string tenant)
        {
            if (!shellHost.TryGetSettings(tenant, out var settings))
            {
                return(null);
            }

            return((await shellHost.GetScopeAndContextAsync(settings)).Scope);
        }
Beispiel #2
0
        /// <summary>
        /// Tries to creates a standalone service scope that can be used to resolve local services and
        /// replaces <see cref="HttpContext.RequestServices"/> with it.
        /// </summary>
        /// <param name="tenant">The tenant name related to the scope and the shell to get.</param>
        /// <returns>Associated scope and shell if the tenant name is valid, otherwise null values.</returns>
        /// <remarks>
        /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state.
        /// </remarks>
        public static Task <(IServiceScope Scope, ShellContext ShellContext)> TryGetScopeAndContextAsync(this IShellHost shellHost, string tenant)
        {
            if (!shellHost.TryGetSettings(tenant, out var settings))
            {
                IServiceScope scope = null;
                ShellContext  shell = null;
                return(Task.FromResult((scope, shell)));
            }

            return(shellHost.GetScopeAndContextAsync(settings));
        }
Beispiel #3
0
        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, StringComparison.Ordinal))
            {
                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, StringComparison.Ordinal))
            {
                var shellSettings = _shellSettingsManager.GetSettings(settings.Tenant);
                var(scope, shellContext) = _shellHost.GetScopeAndContextAsync(shellSettings).GetAwaiter().GetResult();
                using (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.
                    shellContext.AddDependentShell(_shellHost.GetOrCreateShellContextAsync(_shellSettings).GetAwaiter().GetResult());

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

            // 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);
        }
Beispiel #4
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);
                        }
                    }
                }
            }
        }
        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 = ShellContextExtensions.CreateHttpContext(shell);

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

                    IServiceScope scope  = null;
                    ShellContext context = null;

                    try
                    {
                        (scope, context) = await _shellHost.GetScopeAndContextAsync(shell.Settings);
                    }

                    catch (Exception e)
                    {
                        Logger.LogError(e, "Can't resolve a scope on tenant '{TenantName}'.", tenant);
                        return;
                    }

                    using (scope)
                    {
                        if (scope == null || context.Pipeline == null)
                        {
                            break;
                        }

                        var taskName = scheduler.Name;

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

                        if (task == null)
                        {
                            continue;
                        }

                        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);
                        }
                    }
                }
            });
        }
Beispiel #6
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);
                }
            }
        }
Beispiel #7
0
 /// <summary>
 /// Creates a standalone service scope that can be used to resolve local services and
 /// replaces <see cref="HttpContext.RequestServices"/> with it.
 /// </summary>
 /// <param name="tenant">The tenant name related to the scope and the shell to get.</param>
 /// <remarks>
 /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state.
 /// </remarks>
 public static Task <(IServiceScope Scope, ShellContext ShellContext)> GetScopeAndContextAsync(this IShellHost shellHost, string tenant)
 {
     return(shellHost.GetScopeAndContextAsync(shellHost.GetSettings(tenant)));
 }
Beispiel #8
0
 /// <summary>
 /// Creates a standalone service scope that can be used to resolve local services and
 /// replaces <see cref="HttpContext.RequestServices"/> with it.
 /// </summary>
 /// <param name="tenant">The tenant name related to the service scope to get.</param>
 /// <remarks>
 /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state.
 /// </remarks>
 public static async Task <IServiceScope> GetScopeAsync(this IShellHost shellHost, string tenant)
 {
     return((await shellHost.GetScopeAndContextAsync(shellHost.GetSettings(tenant))).Scope);
 }