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