示例#1
0
        public void Configure(OpenIddictValidationDataProtectionOptions options)
        {
            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))
            {
                CreateTenantScope(settings.Tenant).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();
            }
        }
        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();
        }
示例#3
0
        public async Task <IActionResult> Create(EditTenantViewModel model)
        {
            if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants))
            {
                return(Unauthorized());
            }

            if (!IsDefaultShell())
            {
                return(Unauthorized());
            }

            if (ModelState.IsValid)
            {
                ValidateViewModel(model, true);
            }

            if (ModelState.IsValid)
            {
                // Creates a default shell settings based on the configuration.
                var shellSettings = _shellSettingsManager.CreateDefaultSettings();

                shellSettings.Name             = model.Name;
                shellSettings.RequestUrlHost   = model.RequestUrlHost;
                shellSettings.RequestUrlPrefix = model.RequestUrlPrefix;
                shellSettings.State            = TenantState.Uninitialized;

                shellSettings["ConnectionString"] = model.ConnectionString;
                shellSettings["TablePrefix"]      = model.TablePrefix;
                shellSettings["DatabaseProvider"] = model.DatabaseProvider;
                shellSettings["Secret"]           = Guid.NewGuid().ToString();
                shellSettings["RecipeName"]       = model.RecipeName;

                _shellSettingsManager.SaveSettings(shellSettings);
                var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings);

                return(RedirectToAction(nameof(Index)));
            }

            var recipeCollections = await Task.WhenAll(_recipeHarvesters.Select(x => x.HarvestRecipesAsync()));

            var recipes = recipeCollections.SelectMany(x => x).Where(x => x.IsSetupRecipe).ToArray();

            model.Recipes = recipes;

            // If we got this far, something failed, redisplay form
            return(View(model));
        }
        public async Task <IActionResult> Create(EditTenantViewModel model)
        {
            if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants))
            {
                return(Unauthorized());
            }

            if (!IsDefaultShell())
            {
                return(Unauthorized());
            }

            if (ModelState.IsValid)
            {
                await ValidateViewModel(model, true);
            }

            if (ModelState.IsValid)
            {
                var shellSettings = new ShellSettings
                {
                    Name             = model.Name,
                    RequestUrlPrefix = model.RequestUrlPrefix?.Trim(),
                    RequestUrlHost   = model.RequestUrlHost,
                    ConnectionString = model.ConnectionString,
                    TablePrefix      = model.TablePrefix,
                    DatabaseProvider = model.DatabaseProvider,
                    State            = TenantState.Uninitialized,
                    Secret           = Guid.NewGuid().ToString(),
                    RecipeName       = model.RecipeName
                };

                _shellSettingsManager.SaveSettings(shellSettings);
                var shellContext = await _orchardHost.GetOrCreateShellContextAsync(shellSettings);

                return(RedirectToAction(nameof(Index)));
            }

            var recipeCollections = await Task.WhenAll(_recipeHarvesters.Select(x => x.HarvestRecipesAsync()));

            var recipes = recipeCollections.SelectMany(x => x).Where(x => x.IsSetupRecipe).ToArray();

            model.Recipes = recipes;

            // If we got this far, something failed, redisplay form
            return(View(model));
        }
示例#5
0
        public async Task <IActionResult> IndexPost(RegisterUserViewModel viewModel)
        {
            if (ModelState.IsValid)
            {
                var shellSettings = new ShellSettings
                {
                    Name             = viewModel.Handle,
                    RequestUrlPrefix = viewModel.Handle,
                    RequestUrlHost   = "",
                    // this should be a setting in the SaaS module
                    ["ConnectionString"] = "", //ComponentChangedEventArgs by giannis
                    //  ConnectionString = "",
                    //TablePrefix = "",
                    ["TablePrefix"] = "",
                    //  DatabaseProvider = "Sqlite",
                    ["DatabaseProvider"] = "Sqlite",
                    State = TenantState.Uninitialized,
                    // Secret = Guid.NewGuid().ToString(),
                    //RecipeName = "Blog"
                };


                await _shellSettingsManager.SaveSettingsAsync(shellSettings);

                var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings);

                var confirmationLink = Url.Action("Confirm", "Home",
                                                  new { email = viewModel.Email, handle = viewModel.Handle, siteName = viewModel.SiteName },
                                                  Request.Scheme);

                var message = new OrchardCore.Email.MailMessage();
                message.From       = "*****@*****.**"; // new MailAddress("*****@*****.**", "Orchard SaaS");
                message.To         = viewModel.Email;     ////.Add(viewModel.Email);
                message.IsBodyHtml = true;
                message.Body       = $"Click <a href=\"{HttpUtility.HtmlEncode(confirmationLink)}\">this link</a>";
                ;


                await _smtpService.SendAsync(message);

                return(RedirectToAction(nameof(Success)));
            }

            return(View("Index", viewModel));
        }
        public async Task <IActionResult> Create(CreateApiViewModel model)
        {
            if (!IsDefaultShell())
            {
                return(Unauthorized());
            }

            if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageTenants))
            {
                return(Unauthorized());
            }

            if (!string.IsNullOrEmpty(model.Name) && !Regex.IsMatch(model.Name, @"^\w+$"))
            {
                ModelState.AddModelError(nameof(CreateApiViewModel.Name), S["Invalid tenant name. Must contain characters only and no spaces."]);
            }

            // Creates a default shell settings based on the configuration.
            var shellSettings = _shellSettingsManager.CreateDefaultSettings();

            shellSettings.Name             = model.Name;
            shellSettings.RequestUrlHost   = model.RequestUrlHost;
            shellSettings.RequestUrlPrefix = model.RequestUrlPrefix;
            shellSettings.State            = TenantState.Uninitialized;

            shellSettings["ConnectionString"] = model.ConnectionString;
            shellSettings["TablePrefix"]      = model.TablePrefix;
            shellSettings["DatabaseProvider"] = model.DatabaseProvider;
            shellSettings["Secret"]           = Guid.NewGuid().ToString();
            shellSettings["RecipeName"]       = model.RecipeName;

            if (!IsDefaultShell() && string.IsNullOrWhiteSpace(shellSettings.RequestUrlHost) && string.IsNullOrWhiteSpace(shellSettings.RequestUrlPrefix))
            {
                ModelState.AddModelError(nameof(CreateApiViewModel.RequestUrlPrefix), S["Host and url prefix can not be empty at the same time."]);
            }

            if (!string.IsNullOrWhiteSpace(shellSettings.RequestUrlPrefix))
            {
                if (shellSettings.RequestUrlPrefix.Contains('/'))
                {
                    ModelState.AddModelError(nameof(CreateApiViewModel.RequestUrlPrefix), S["The url prefix can not contain more than one segment."]);
                }
            }

            if (ModelState.IsValid)
            {
                if (_shellHost.TryGetSettings(model.Name, out var settings))
                {
                    // Site already exists, return 200 for indempotency purpose

                    var token = CreateSetupToken(settings);

                    return(StatusCode(201, GetTenantUrl(settings, token)));
                }
                else
                {
                    _shellSettingsManager.SaveSettings(shellSettings);
                    var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings);

                    var token = CreateSetupToken(shellSettings);

                    return(Ok(GetTenantUrl(shellSettings, token)));
                }
            }

            return(BadRequest(ModelState));
        }
        public async Task <IActionResult> IndexPost(RegisterUserViewModel model)
        {
            if (!model.AcceptTerms)
            {
                ModelState.AddModelError(nameof(RegisterUserViewModel.AcceptTerms), S["Please, accept the terms and conditions."]);
            }

            if (!string.IsNullOrEmpty(model.Handle) && !Regex.IsMatch(model.Handle, @"^\w+$"))
            {
                ModelState.AddModelError(nameof(RegisterUserViewModel.Handle), S["Invalid tenant name. Must contain characters only and no spaces."]);
            }

            if (ModelState.IsValid)
            {
                if (_shellHost.TryGetSettings(model.Handle, out var shellSettings))
                {
                    ModelState.AddModelError(nameof(RegisterUserViewModel.Handle), S["This site name already exists."]);
                }
                else
                {
                    shellSettings = new ShellSettings
                    {
                        Name             = model.Handle,
                        RequestUrlPrefix = model.Handle.ToLower(),
                        RequestUrlHost   = null,
                        State            = TenantState.Uninitialized
                    };
                    shellSettings["RecipeName"]       = model.RecipeName;
                    shellSettings["DatabaseProvider"] = "Sqlite";

                    await _shellSettingsManager.SaveSettingsAsync(shellSettings);

                    var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings);

                    var recipes = await _setupService.GetSetupRecipesAsync();

                    var recipe = recipes.FirstOrDefault(x => x.Name == model.RecipeName);

                    if (recipe == null)
                    {
                        ModelState.AddModelError(nameof(RegisterUserViewModel.RecipeName), S["Invalid recipe name."]);
                    }

                    var adminName     = defaultAdminName;
                    var adminPassword = GenerateRandomPassword();
                    var siteName      = model.SiteName;
                    var siteUrl       = GetTenantUrl(shellSettings);

                    var dataProtector     = _dataProtectionProvider.CreateProtector(dataProtectionPurpose).ToTimeLimitedDataProtector();
                    var encryptedPassword = dataProtector.Protect(adminPassword, _clock.UtcNow.Add(new TimeSpan(24, 0, 0)));
                    var confirmationLink  = Url.Action(nameof(Confirm), "Home", new { email = model.Email, handle = model.Handle, siteName = model.SiteName, ep = encryptedPassword }, Request.Scheme);

                    var message = new MailMessage();
                    if (emailToBcc)
                    {
                        message.Bcc = _smtpSettingsOptions.Value.DefaultSender;
                    }
                    message.To         = model.Email;
                    message.IsBodyHtml = true;
                    message.Subject    = emailSubject;
                    message.Body       = S[$"Hello,<br><br>Your demo site '{siteName}' has been created.<br><br>1) Setup your site by opening <a href=\"{confirmationLink}\">this link</a>.<br><br>2) Log into the <a href=\"{siteUrl}/admin\">admin</a> with these credentials:<br>Username: {adminName}<br>Password: {adminPassword}"];

                    await _smtpService.SendAsync(message);

                    return(RedirectToAction(nameof(Success)));
                }
            }

            return(View(nameof(Index), model));
        }
示例#8
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);
            }

            // Features to enable for Setup
            string[] hardcoded =
            {
                _applicationName,
                //TODO
            };

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

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

            var shellSettings = new ShellSettings(context.ShellSettings);

            shellSettings["ConnectionString"] = context.DatabaseConnectionString;

            // 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)) {
                using (var scope = shellContext.CreateScope()) {
                    var hasErrors = false;

                    void reportError(string key, string message)
                    {
                        hasErrors = true;
                        context.SetError(key, message);
                    }

                    try {
                        await this.InitializeDatabaseForTenantAsync(context, scope, reportError);

                        if (hasErrors)
                        {
                            _logger.LogError("An error occurred while initializing the tenant {0}", context.DbName);
                            return(null);
                        }
                    }
                    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.SetError("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]);
                        return(null);
                    }

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

                    // TODO FIXME

                    /*
                     * var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>();
                     *
                     * if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) {
                     *  var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                     *  await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                     * }
                     */
                }

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

            // Reloading the shell context as the recipe  has probably updated its features
            using (var shellContext = await _shellHost.GetOrCreateShellContextAsync(shellSettings)) {
                using (var scope = shellContext.CreateScope()) {
                    var hasErrors = false;

                    void reportError(string key, string message)
                    {
                        hasErrors = true;
                        context.SetError(key, message);
                    }

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

                    //Notify all registered event handlers
                    await setupEventHandlers.InvokeAsync(x => x.Setup(context, reportError), logger);

                    if (hasErrors)
                    {
                        return(executionId);
                    }

                    /* TODO FIXME
                     * var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>();
                     *
                     * if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) {
                     *  var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                     *  await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                     * }
                     */
                }
            }

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

            return(executionId);
        }