diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs
index 0fb468c7..088eed5e 100644
--- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs
@@ -19,4 +19,19 @@ public static void SetUpdateSiteUrlMaintenanceConfiguration(
return Task.CompletedTask;
};
+
+ public static void SetAddSiteOwnerPermissionToRoleMaintenanceConfiguration(
+ this OrchardCoreUITestExecutorConfiguration configuration) => configuration.OrchardCoreConfiguration.BeforeAppStart +=
+ (_, argumentsBuilder) =>
+ {
+ argumentsBuilder
+ .AddWithValue(
+ "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole:IsEnabled",
+ value: true)
+ .AddWithValue(
+ "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole:Role",
+ value: "Editor");
+
+ return Task.CompletedTask;
+ };
}
diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs
index a2bce6ba..7850edf9 100644
--- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs
@@ -15,4 +15,11 @@ public static async Task TestSiteUrlMaintenanceExecutionAsync(this UITestContext
await context.GoToAdminRelativeUrlAsync("/Settings/general");
context.Get(By.Name("ISite.BaseUrl")).GetValue().ShouldBe("https://test.com");
}
+
+ public static async Task TestSiteOwnerPermissionToRoleMaintenanceExecutionAsync(this UITestContext context)
+ {
+ await context.SignInDirectlyAsync();
+ await context.GoToAdminRelativeUrlAsync("/Roles/Edit/Editor");
+ context.Get(By.Id("Checkbox.SiteOwner")).GetDomProperty("checked").ShouldBe(bool.TrueString);
+ }
}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs b/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs
index bbe6ae41..b53d3da2 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs
@@ -6,4 +6,5 @@ public static class FeatureNames
public const string Maintenance = Module;
public const string UpdateSiteUrl = Maintenance + "." + nameof(UpdateSiteUrl);
public const string UpdateShellRequestUrls = Maintenance + "." + nameof(UpdateShellRequestUrls);
+ public const string AddSiteOwnerPermissionToRole = Maintenance + "." + nameof(AddSiteOwnerPermissionToRole);
}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj b/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj
index c21d3476..ef8f9c69 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj
+++ b/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj
@@ -41,4 +41,12 @@
+
+
+
+
+
+
+
+
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs
new file mode 100644
index 00000000..016e9c4a
--- /dev/null
+++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs
@@ -0,0 +1,7 @@
+namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole;
+
+public class AddSiteOwnerPermissionToRoleMaintenanceOptions
+{
+ public bool IsEnabled { get; set; }
+ public string Role { get; set; }
+}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceProvider.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceProvider.cs
new file mode 100644
index 00000000..bd34df66
--- /dev/null
+++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceProvider.cs
@@ -0,0 +1,42 @@
+using Lombiq.Hosting.Tenants.Maintenance.Extensions;
+using Lombiq.Hosting.Tenants.Maintenance.Models;
+using Lombiq.Hosting.Tenants.Maintenance.Services;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Options;
+using OrchardCore.Security;
+using System.Linq;
+using System.Threading.Tasks;
+using static OrchardCore.Security.Permissions.Permission;
+using static OrchardCore.Security.StandardPermissions;
+
+namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole;
+
+public class AddSiteOwnerPermissionToRoleMaintenanceProvider : MaintenanceProviderBase
+{
+ private readonly IOptions _options;
+ private readonly RoleManager _roleManager;
+
+ public AddSiteOwnerPermissionToRoleMaintenanceProvider(
+ IOptions options,
+ RoleManager roleManager)
+ {
+ _options = options;
+ _roleManager = roleManager;
+ }
+
+ public override Task ShouldExecuteAsync(MaintenanceTaskExecutionContext context) =>
+ Task.FromResult(
+ _options.Value.IsEnabled &&
+ !context.WasLatestExecutionSuccessful());
+
+ public override async Task ExecuteAsync(MaintenanceTaskExecutionContext context)
+ {
+ if (await _roleManager.FindByNameAsync(_options.Value.Role) is not Role role) return;
+
+ if (role.RoleClaims.Any(claim => claim.ClaimType == ClaimType && claim.ClaimValue == SiteOwner.Name)) return;
+
+ role.RoleClaims.Add(new RoleClaim { ClaimType = ClaimType, ClaimValue = SiteOwner.Name });
+
+ await _roleManager.UpdateAsync(role);
+ }
+}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs
new file mode 100644
index 00000000..4722717d
--- /dev/null
+++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs
@@ -0,0 +1,27 @@
+using Lombiq.Hosting.Tenants.Maintenance.Constants;
+using Lombiq.Hosting.Tenants.Maintenance.Services;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using OrchardCore.Environment.Shell.Configuration;
+using OrchardCore.Modules;
+
+namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole;
+
+[Feature(FeatureNames.AddSiteOwnerPermissionToRole)]
+public class Startup : StartupBase
+{
+ private readonly IShellConfiguration _shellConfiguration;
+
+ public Startup(IShellConfiguration shellConfiguration) =>
+ _shellConfiguration = shellConfiguration;
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ var options = new AddSiteOwnerPermissionToRoleMaintenanceOptions();
+ var configSection = _shellConfiguration.GetSection("Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole");
+ configSection.Bind(options);
+ services.Configure(configSection);
+
+ services.AddScoped();
+ }
+}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs b/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs
index ac85a786..07119061 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs
@@ -33,3 +33,12 @@
DefaultTenantOnly = true,
Dependencies = new[] { Maintenance }
)]
+
+[assembly: Feature(
+ Id = AddSiteOwnerPermissionToRole,
+ Name = "Lombiq Hosting - Tenants Maintenance - Add Site Owner Permission To Role",
+ Description = "Adds the Site Owner permission to a role (e.g., when the production database is copied to staging).",
+ Category = "Maintenance",
+ DefaultTenantOnly = true,
+ Dependencies = new[] { Maintenance }
+)]
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Readme.md b/Lombiq.Hosting.Tenants.Maintenance/Readme.md
index 4ace2cee..6d552793 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Readme.md
+++ b/Lombiq.Hosting.Tenants.Maintenance/Readme.md
@@ -22,6 +22,25 @@ public void ConfigureServices(IServiceCollection services) =>
To add new maintenance tasks, you need to implement the `IMaintenanceProvider` interface and register it as a service.
+### `Lombiq.Hosting.Tenants.Maintenance.AddSiteOwnerPermissionToRole`
+
+It's a maintenance task that adds the `SiteOwner` permission to a role set in the app configuration. It is available on any tenant.
+
+The following configuration options are available to set the role:
+
+```json
+{
+ "OrchardCore": {
+ "Lombiq_Hosting_Tenants_Maintenance": {
+ "AddSiteOwnerPermissionToRole": {
+ "IsEnabled": true,
+ "RoleName": "NameOfTheRole"
+ }
+ }
+ }
+}
+```
+
### `Lombiq.Hosting.Tenants.Maintenance.UpdateSiteUrl`
It's a maintenance task that updates the site's base URL in the site settings based on the app configuration. It is available on any tenant.
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs
index afdc86a6..4a05ad12 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs
@@ -3,18 +3,18 @@
using Lombiq.Hosting.Tenants.Maintenance.Models;
using Microsoft.Extensions.Logging;
using OrchardCore.Environment.Shell;
-using OrchardCore.Modules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YesSql;
+using IOrchardClock = OrchardCore.Modules.IClock;
namespace Lombiq.Hosting.Tenants.Maintenance.Services;
public class MaintenanceManager : IMaintenanceManager
{
- private readonly IClock _clock;
+ private readonly IOrchardClock _clock;
private readonly ILogger _logger;
private readonly IEnumerable _maintenanceProviders;
private readonly ISession _session;
@@ -22,7 +22,7 @@ public class MaintenanceManager : IMaintenanceManager
private readonly ShellSettings _shellSettings;
public MaintenanceManager(
- IClock clock,
+ IOrchardClock clock,
ILogger logger,
IEnumerable maintenanceProviders,
ISession session,
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceRunnerService.cs b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceRunnerService.cs
index a349b0cf..2ef3fc85 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceRunnerService.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceRunnerService.cs
@@ -2,6 +2,7 @@
using OrchardCore.Environment.Shell;
using OrchardCore.Environment.Shell.Models;
using OrchardCore.Modules;
+using System;
using System.Threading.Tasks;
namespace Lombiq.Hosting.Tenants.Maintenance.Services;
@@ -10,16 +11,16 @@ public class MaintenanceRunnerService : ModularTenantEvents
{
private readonly ShellSettings _shellSettings;
private readonly ILogger _logger;
- private readonly IMaintenanceManager _maintenanceManager;
+ private readonly Lazy _maintenanceManagerLazy;
public MaintenanceRunnerService(
ShellSettings shellSettings,
ILogger logger,
- IMaintenanceManager maintenanceManager)
+ Lazy maintenanceManagerLazy)
{
_shellSettings = shellSettings;
_logger = logger;
- _maintenanceManager = maintenanceManager;
+ _maintenanceManagerLazy = maintenanceManagerLazy;
}
public override async Task ActivatedAsync()
@@ -29,6 +30,6 @@ public override async Task ActivatedAsync()
_logger.LogDebug(
"Executing maintenance tasks on shell '{ShellName}'.",
_shellSettings.Name);
- await _maintenanceManager.ExecuteMaintenanceTasksAsync();
+ await _maintenanceManagerLazy.Value.ExecuteMaintenanceTasksAsync();
}
}
diff --git a/Lombiq.Hosting.Tenants.Maintenance/Startup.cs b/Lombiq.Hosting.Tenants.Maintenance/Startup.cs
index aad3dd3c..400b2929 100644
--- a/Lombiq.Hosting.Tenants.Maintenance/Startup.cs
+++ b/Lombiq.Hosting.Tenants.Maintenance/Startup.cs
@@ -1,3 +1,4 @@
+using Lombiq.HelpfulLibraries.Common.DependencyInjection;
using Lombiq.Hosting.Tenants.Maintenance.Constants;
using Lombiq.Hosting.Tenants.Maintenance.Indexes;
using Lombiq.Hosting.Tenants.Maintenance.Services;
@@ -13,6 +14,7 @@ public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
+ services.AddLazyInjectionSupport();
services.Configure(options => options.Collections.Add(DocumentCollections.Maintenance));
services.AddScoped();
services.AddSingleton();