-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
In Auto setup, when IsUninitialized, response http status code 503 #16834
base: main
Are you sure you want to change the base?
Changes from 24 commits
5c0cfbe
cbb43d2
1573e53
ff715ae
5ffc4aa
c02123c
281daa4
511dc12
7a51f44
1fbc8c6
6f99df3
9c25005
2d52d80
37aaf73
1cf54b3
23333d7
4f665ce
4255865
ed75f46
889565e
b039be0
13ba574
debdf12
9d0902f
29a8a10
4b5ae4b
3c806ab
30cf024
d02b9e1
703a622
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,12 @@ | ||
using System.Text; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using OrchardCore.Abstractions.Setup; | ||
using OrchardCore.AutoSetup.Extensions; | ||
using OrchardCore.AutoSetup.Options; | ||
using OrchardCore.AutoSetup.Services; | ||
using OrchardCore.Environment.Shell; | ||
using OrchardCore.Locking.Distributed; | ||
using OrchardCore.Setup.Services; | ||
|
||
namespace OrchardCore.AutoSetup; | ||
|
||
|
@@ -47,11 +45,6 @@ public class AutoSetupMiddleware | |
/// </summary> | ||
private readonly AutoSetupOptions _options; | ||
|
||
/// <summary> | ||
/// The logger. | ||
/// </summary> | ||
private readonly ILogger _logger; | ||
|
||
/// <summary> | ||
/// The auto setup lock options. | ||
/// </summary> | ||
|
@@ -70,25 +63,21 @@ public class AutoSetupMiddleware | |
/// <param name="shellSettings">The shell settings.</param> | ||
/// <param name="shellSettingsManager">The shell settings manager.</param> | ||
/// <param name="distributedLock">The distributed lock.</param> | ||
/// <param name="options">The auto setup options.</param> | ||
/// <param name="logger">The logger.</param> | ||
/// <param name="options">The auto setup options.</param> | ||
public AutoSetupMiddleware( | ||
RequestDelegate next, | ||
IShellHost shellHost, | ||
ShellSettings shellSettings, | ||
IShellSettingsManager shellSettingsManager, | ||
IDistributedLock distributedLock, | ||
IOptions<AutoSetupOptions> options, | ||
ILogger<AutoSetupMiddleware> logger) | ||
IOptions<AutoSetupOptions> options) | ||
{ | ||
_next = next; | ||
_shellHost = shellHost; | ||
_shellSettings = shellSettings; | ||
_shellSettingsManager = shellSettingsManager; | ||
_distributedLock = distributedLock; | ||
_options = options.Value; | ||
_logger = logger; | ||
|
||
_lockOptions = _options.LockOptions; | ||
_setupOptions = _options.Tenants.FirstOrDefault(options => _shellSettings.Name == options.ShellName); | ||
} | ||
|
@@ -124,20 +113,23 @@ public async Task InvokeAsync(HttpContext httpContext) | |
} | ||
|
||
// Check if the tenant was installed by another instance. | ||
using var settings = (await _shellSettingsManager | ||
.LoadSettingsAsync(_shellSettings.Name)) | ||
.AsDisposable(); | ||
using var settings = await _shellSettingsManager.LoadSettingsAsync(_shellSettings.Name); | ||
|
||
if (!settings.IsUninitialized()) | ||
if (settings != null) | ||
{ | ||
await _shellHost.ReloadShellContextAsync(_shellSettings, eventSource: false); | ||
httpContext.Response.Redirect(pathBase); | ||
|
||
return; | ||
settings.AsDisposable(); | ||
if (!settings.IsUninitialized()) | ||
{ | ||
await _shellHost.ReloadShellContextAsync(_shellSettings, eventSource: false); | ||
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; | ||
await httpContext.Response.WriteAsync("The requested tenant is not initialized."); | ||
return; | ||
} | ||
} | ||
|
||
var setupService = httpContext.RequestServices.GetRequiredService<ISetupService>(); | ||
if (await SetupTenantAsync(setupService, _setupOptions, _shellSettings)) | ||
var autoSetupService = httpContext.RequestServices.GetRequiredService<IAutoSetupService>(); | ||
(var setupContext, var isSuccess) = await autoSetupService.SetupTenantAsync(_setupOptions, _shellSettings); | ||
if (isSuccess) | ||
{ | ||
if (_setupOptions.IsDefault) | ||
{ | ||
|
@@ -146,123 +138,28 @@ public async Task InvokeAsync(HttpContext httpContext) | |
{ | ||
if (_setupOptions != setupOptions) | ||
{ | ||
await CreateTenantSettingsAsync(setupOptions); | ||
await autoSetupService.CreateTenantSettingsAsync(setupOptions); | ||
} | ||
} | ||
} | ||
|
||
httpContext.Response.Redirect(pathBase); | ||
} | ||
else | ||
{ | ||
httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; | ||
var stringBuilder = new StringBuilder(); | ||
foreach (var error in setupContext.Errors) | ||
{ | ||
stringBuilder.AppendLine($"{error.Key} : '{error.Value}'"); | ||
} | ||
|
||
await httpContext.Response.WriteAsync($"The AutoSetup failed installing the site '{_setupOptions.SiteName}' with errors: {stringBuilder}."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't write error messages to the response directly. For instance here the SiteName is user input and could contain XSS. I believe it was only logged before. If we want to keep doing it then we don't need the tuple as a return type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sebastienros : Committed new one of shouldn't write error messages to the response directly |
||
return; | ||
} | ||
} | ||
} | ||
|
||
await _next.Invoke(httpContext); | ||
} | ||
|
||
/// <summary> | ||
/// Sets up a tenant. | ||
/// </summary> | ||
/// <param name="setupService">The setup service.</param> | ||
/// <param name="setupOptions">The tenant setup options.</param> | ||
/// <param name="shellSettings">The tenant shell settings.</param> | ||
/// <returns> | ||
/// Returns <c>true</c> if successfully setup. | ||
/// </returns> | ||
public async Task<bool> SetupTenantAsync(ISetupService setupService, TenantSetupOptions setupOptions, ShellSettings shellSettings) | ||
{ | ||
var setupContext = await GetSetupContextAsync(setupOptions, setupService, shellSettings); | ||
|
||
_logger.LogInformation("AutoSetup is initializing the site"); | ||
|
||
await setupService.SetupAsync(setupContext); | ||
|
||
if (setupContext.Errors.Count == 0) | ||
{ | ||
_logger.LogInformation("AutoSetup successfully provisioned the site '{SiteName}'.", setupOptions.SiteName); | ||
|
||
return true; | ||
} | ||
|
||
var stringBuilder = new StringBuilder(); | ||
foreach (var error in setupContext.Errors) | ||
{ | ||
stringBuilder.AppendLine($"{error.Key} : '{error.Value}'"); | ||
} | ||
|
||
_logger.LogError("AutoSetup failed installing the site '{SiteName}' with errors: {Errors}", setupOptions.SiteName, stringBuilder); | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a tenant shell settings. | ||
/// </summary> | ||
/// <param name="setupOptions">The setup options.</param> | ||
/// <returns>The <see cref="ShellSettings"/>.</returns> | ||
public async Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions) | ||
{ | ||
using var shellSettings = _shellSettingsManager | ||
.CreateDefaultSettings() | ||
.AsUninitialized() | ||
.AsDisposable(); | ||
|
||
shellSettings.Name = setupOptions.ShellName; | ||
shellSettings.RequestUrlHost = setupOptions.RequestUrlHost; | ||
shellSettings.RequestUrlPrefix = setupOptions.RequestUrlPrefix; | ||
|
||
shellSettings["ConnectionString"] = setupOptions.DatabaseConnectionString; | ||
shellSettings["TablePrefix"] = setupOptions.DatabaseTablePrefix; | ||
shellSettings["Schema"] = setupOptions.DatabaseSchema; | ||
shellSettings["DatabaseProvider"] = setupOptions.DatabaseProvider; | ||
shellSettings["Secret"] = Guid.NewGuid().ToString(); | ||
shellSettings["RecipeName"] = setupOptions.RecipeName; | ||
shellSettings["FeatureProfile"] = setupOptions.FeatureProfile; | ||
|
||
await _shellHost.UpdateShellSettingsAsync(shellSettings); | ||
|
||
return shellSettings; | ||
} | ||
|
||
/// <summary> | ||
/// Gets a setup context from the configuration. | ||
/// </summary> | ||
/// <param name="options">The tenant setup options.</param> | ||
/// <param name="setupService">The setup service.</param> | ||
/// <param name="shellSettings">The tenant shell settings.</param> | ||
/// <returns> The <see cref="SetupContext"/> used to setup the site.</returns> | ||
private static async Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ISetupService setupService, ShellSettings shellSettings) | ||
{ | ||
var recipes = await setupService.GetSetupRecipesAsync(); | ||
|
||
var recipe = recipes.SingleOrDefault(r => r.Name == options.RecipeName); | ||
|
||
var setupContext = new SetupContext | ||
{ | ||
Recipe = recipe, | ||
ShellSettings = shellSettings, | ||
Errors = new Dictionary<string, string>() | ||
}; | ||
|
||
if (shellSettings.IsDefaultShell()) | ||
{ | ||
// The 'Default' shell is first created by the infrastructure, | ||
// so the following 'Autosetup' options need to be passed. | ||
shellSettings.RequestUrlHost = options.RequestUrlHost; | ||
shellSettings.RequestUrlPrefix = options.RequestUrlPrefix; | ||
} | ||
|
||
setupContext.Properties[SetupConstants.AdminEmail] = options.AdminEmail; | ||
setupContext.Properties[SetupConstants.AdminPassword] = options.AdminPassword; | ||
setupContext.Properties[SetupConstants.AdminUsername] = options.AdminUsername; | ||
setupContext.Properties[SetupConstants.DatabaseConnectionString] = options.DatabaseConnectionString; | ||
setupContext.Properties[SetupConstants.DatabaseProvider] = options.DatabaseProvider; | ||
setupContext.Properties[SetupConstants.DatabaseTablePrefix] = options.DatabaseTablePrefix; | ||
setupContext.Properties[SetupConstants.DatabaseSchema] = options.DatabaseSchema; | ||
setupContext.Properties[SetupConstants.SiteName] = options.SiteName; | ||
setupContext.Properties[SetupConstants.SiteTimeZone] = options.SiteTimeZone; | ||
|
||
return setupContext; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using System.Text; | ||
using Microsoft.Extensions.Logging; | ||
using OrchardCore.Abstractions.Setup; | ||
using OrchardCore.AutoSetup.Options; | ||
using OrchardCore.Environment.Shell; | ||
using OrchardCore.Setup.Services; | ||
|
||
namespace OrchardCore.AutoSetup.Services; | ||
|
||
public class AutoSetupService : IAutoSetupService | ||
{ | ||
private readonly IShellHost _shellHost; | ||
private readonly IShellSettingsManager _shellSettingsManager; | ||
private readonly ISetupService _setupService; | ||
private readonly ILogger _logger; | ||
|
||
public AutoSetupService( | ||
IShellHost shellHost, | ||
IShellSettingsManager shellSettingsManager, | ||
ISetupService setupService, | ||
ILogger<AutoSetupService> logger | ||
) | ||
{ | ||
_shellHost = shellHost; | ||
_shellSettingsManager = shellSettingsManager; | ||
_setupService = setupService; | ||
_logger = logger; | ||
} | ||
|
||
public async Task<(SetupContext, bool)> SetupTenantAsync(TenantSetupOptions setupOptions, ShellSettings shellSettings) | ||
{ | ||
var setupContext = await GetSetupContextAsync(setupOptions, shellSettings); | ||
|
||
_logger.LogInformation("The AutoSetup is initializing the site."); | ||
|
||
await _setupService.SetupAsync(setupContext); | ||
|
||
if (setupContext.Errors.Count == 0) | ||
{ | ||
_logger.LogInformation("The AutoSetup successfully provisioned the site '{SiteName}'.", setupOptions.SiteName); | ||
|
||
return (setupContext, true); | ||
} | ||
|
||
var stringBuilder = new StringBuilder(); | ||
foreach (var error in setupContext.Errors) | ||
{ | ||
stringBuilder.AppendLine($"{error.Key} : '{error.Value}'"); | ||
} | ||
|
||
_logger.LogError("The AutoSetup failed installing the site '{SiteName}' with errors: {Errors}.", setupOptions.SiteName, stringBuilder); | ||
|
||
return (setupContext, false); | ||
} | ||
|
||
public async Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions) | ||
{ | ||
using var shellSettings = _shellSettingsManager | ||
.CreateDefaultSettings() | ||
.AsUninitialized() | ||
.AsDisposable(); | ||
|
||
shellSettings.Name = setupOptions.ShellName; | ||
shellSettings.RequestUrlHost = setupOptions.RequestUrlHost; | ||
shellSettings.RequestUrlPrefix = setupOptions.RequestUrlPrefix; | ||
shellSettings["ConnectionString"] = setupOptions.DatabaseConnectionString; | ||
shellSettings["TablePrefix"] = setupOptions.DatabaseTablePrefix; | ||
shellSettings["Schema"] = setupOptions.DatabaseSchema; | ||
shellSettings["DatabaseProvider"] = setupOptions.DatabaseProvider; | ||
shellSettings["Secret"] = Guid.NewGuid().ToString(); | ||
shellSettings["RecipeName"] = setupOptions.RecipeName; | ||
shellSettings["FeatureProfile"] = setupOptions.FeatureProfile; | ||
|
||
await _shellHost.UpdateShellSettingsAsync(shellSettings); | ||
|
||
return shellSettings; | ||
} | ||
|
||
public async Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ShellSettings shellSettings) | ||
{ | ||
var recipes = await _setupService.GetSetupRecipesAsync(); | ||
|
||
var recipe = recipes.SingleOrDefault(r => r.Name == options.RecipeName); | ||
infofromca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var setupContext = new SetupContext | ||
{ | ||
Recipe = recipe, | ||
ShellSettings = shellSettings, | ||
Errors = new Dictionary<string, string>() | ||
}; | ||
|
||
if (shellSettings.IsDefaultShell()) | ||
{ | ||
// The 'Default' shell is first created by the infrastructure, | ||
// so the following 'Autosetup' options need to be passed. | ||
shellSettings.RequestUrlHost = options.RequestUrlHost; | ||
shellSettings.RequestUrlPrefix = options.RequestUrlPrefix; | ||
} | ||
|
||
setupContext.Properties[SetupConstants.AdminEmail] = options.AdminEmail; | ||
setupContext.Properties[SetupConstants.AdminPassword] = options.AdminPassword; | ||
setupContext.Properties[SetupConstants.AdminUsername] = options.AdminUsername; | ||
setupContext.Properties[SetupConstants.DatabaseConnectionString] = options.DatabaseConnectionString; | ||
setupContext.Properties[SetupConstants.DatabaseProvider] = options.DatabaseProvider; | ||
setupContext.Properties[SetupConstants.DatabaseTablePrefix] = options.DatabaseTablePrefix; | ||
setupContext.Properties[SetupConstants.DatabaseSchema] = options.DatabaseSchema; | ||
setupContext.Properties[SetupConstants.SiteName] = options.SiteName; | ||
setupContext.Properties[SetupConstants.SiteTimeZone] = options.SiteTimeZone; | ||
|
||
return setupContext; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using OrchardCore.AutoSetup.Options; | ||
using OrchardCore.Environment.Shell; | ||
using OrchardCore.Setup.Services; | ||
|
||
namespace OrchardCore.AutoSetup.Services; | ||
|
||
public interface IAutoSetupService | ||
{ | ||
/// <summary> | ||
/// Creates a tenant shell settings. | ||
/// </summary> | ||
/// <param name="setupOptions">The setup options.</param> | ||
/// <returns>The <see cref="ShellSettings"/>.</returns> | ||
Task<ShellSettings> CreateTenantSettingsAsync(TenantSetupOptions setupOptions); | ||
|
||
/// <summary> | ||
/// Gets a setup context from the configuration. | ||
/// </summary> | ||
/// <param name="options">The tenant setup options.</param> | ||
/// <param name="shellSettings">The tenant shell settings.</param> | ||
/// <returns> The <see cref="SetupContext"/> used to setup the site.</returns> | ||
Task<SetupContext> GetSetupContextAsync(TenantSetupOptions options, ShellSettings shellSettings); | ||
|
||
/// <summary> | ||
/// Sets up a tenant. | ||
/// </summary> | ||
/// <param name="setupOptions">The tenant setup options.</param> | ||
/// <param name="shellSettings">The tenant shell settings.</param> | ||
/// <returns> | ||
/// Returns <see langword="true" /> if successfully setup. | ||
/// </returns> | ||
Task<(SetupContext, bool)> SetupTenantAsync(TenantSetupOptions setupOptions, ShellSettings shellSettings); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would prevent the error message from being displayed to the user. Like server-side validation or database creation error.
Even if you still decide to keep 503 as the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sebastienros : Just committed
Won't stop other Middlewares after success