From 13f07c692592edef8be56f0afa84df2c7712f07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 27 Nov 2024 20:41:10 +0100 Subject: [PATCH 01/19] Better IdleTenantManagement test with OC 2.1's background task configurability --- .../IdleTenantManagementExtensions.cs | 8 +++++- .../TestCaseUITestContextExtensions.cs | 25 ++++++++++++------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/IdleTenantManagementExtensions.cs b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/IdleTenantManagementExtensions.cs index fbff091..6d81fd3 100644 --- a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/IdleTenantManagementExtensions.cs +++ b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/IdleTenantManagementExtensions.cs @@ -19,7 +19,13 @@ public static void ConfigureIdleTenantManagementTestSettings(this OrchardCoreUIT "1") .AddWithValue( "Logging:LogLevel:Lombiq.Hosting.Tenants.IdleTenantManagement.Services.IdleShutdownService", - "Information"); + "Information") + .AddWithValue( + "OrchardCore:OrchardCore_BackgroundService:PollingTime", + "00:00:05") + .AddWithValue( + "OrchardCore:OrchardCore_BackgroundService:MinimumIdleTime", + "00:00:01"); return Task.CompletedTask; }; diff --git a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs index 4b8cd9d..aacd8ed 100644 --- a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -1,6 +1,9 @@ using Lombiq.Tests.UI.Extensions; +using Lombiq.Tests.UI.Helpers; using Lombiq.Tests.UI.Pages; using Lombiq.Tests.UI.Services; +using System; +using System.Linq; using System.Threading.Tasks; using static Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.Constants.IdleTenantData; @@ -26,19 +29,23 @@ await context.GoToSetupPageAndSetupOrchardCoreAsync( RunSetupOnCurrentPage = true, }); - // We are letting the site to sit idle for more than two minutes so that the tenant can be shut down by the - // background task. - // Once https://github.com/OrchardCMS/OrchardCore/issues/17031 is fixed, we can configure a short background - // task polling and idle time instead. - await Task.Delay(129420); + // Due to the background ask scheduling configuration in ConfigureIdleTenantManagementTestSettings(), the + // background task should run within not much more than a minute (background tasks are run with a frequency of + // at least a minute, due to the limitation of cron expressions). Polling for it here. + await Task.Delay(TimeSpan.FromMinutes(1)); + await ReliabilityHelper.DoWithRetriesOrFailAsync( + async () => + { + var logEntries = await context.Application.GetLogEntriesFromAllLogsAsync(); + return logEntries.Any(logEntry => + logEntry.Message == $"Shutting down tenant \"{IdleTenantName}\" because of idle timeout."); + }, + TimeSpan.FromSeconds(30), + TimeSpan.FromSeconds(1)); // If we can access the admin menu after the tenant shut down that means the new shell was created and it is // working as intended. await context.SignInDirectlyAsync(); await context.GoToDashboardAsync(); - - // Make sure the shutdown message is in the logs. - await context.Application.LogsShouldContainAsync(logEntry => - logEntry.Message == $"Shutting down tenant \"{IdleTenantName}\" because of idle timeout."); } } From 2fd5938aad1f6a5b349ed7962403d7fdd06239f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 27 Nov 2024 21:11:23 +0100 Subject: [PATCH 02/19] Increasing idle tenant timeout --- .../Extensions/TestCaseUITestContextExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs index aacd8ed..fadc7cc 100644 --- a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -40,7 +40,7 @@ await ReliabilityHelper.DoWithRetriesOrFailAsync( return logEntries.Any(logEntry => logEntry.Message == $"Shutting down tenant \"{IdleTenantName}\" because of idle timeout."); }, - TimeSpan.FromSeconds(30), + TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(1)); // If we can access the admin menu after the tenant shut down that means the new shell was created and it is From 24d881056780b4fa79b9033bf786d3ce555d83aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 00:13:50 +0100 Subject: [PATCH 03/19] Reimplementing AddSiteOwnerPermissionToRole as AddAdministratorRoleToUsersWithRole --- ...eUITestExecutorConfigurationExtensions.cs} | 8 +-- .../TestCaseUITestContextExtensions.cs | 35 ++++++++++-- .../Constants/FeatureNames.cs | 2 +- ...orRoleToUsersWithRoleMaintenanceOptions.cs | 7 +++ ...rRoleToUsersWithRoleMaintenanceProvider.cs | 56 +++++++++++++++++++ .../Startup.cs | 10 ++-- ...OwnerPermissionToRoleMaintenanceOptions.cs | 7 --- ...wnerPermissionToRoleMaintenanceProvider.cs | 41 -------------- .../Manifest.cs | 7 ++- Lombiq.Hosting.Tenants.Maintenance/Readme.md | 6 +- .../Services/IMaintenanceManager.cs | 6 ++ .../Services/MaintenanceManager.cs | 13 +++++ 12 files changed, 130 insertions(+), 68 deletions(-) rename Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/{MaintenanceExtensions.cs => MaintenanceOrchardCoreUITestExecutorConfigurationExtensions.cs} (88%) create mode 100644 Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceOptions.cs create mode 100644 Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs rename Lombiq.Hosting.Tenants.Maintenance/Maintenance/{AddSiteOwnerPermissionToRole => AddAdministratorRoleToUsersWithRole}/Startup.cs (58%) delete mode 100644 Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs delete mode 100644 Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceProvider.cs diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceOrchardCoreUITestExecutorConfigurationExtensions.cs similarity index 88% rename from Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs rename to Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceOrchardCoreUITestExecutorConfigurationExtensions.cs index cd61f23..3b90d2f 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceExtensions.cs +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/MaintenanceOrchardCoreUITestExecutorConfigurationExtensions.cs @@ -3,7 +3,7 @@ namespace Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Extensions; -public static class MaintenanceExtensions +public static class MaintenanceOrchardCoreUITestExecutorConfigurationExtensions { public static void SetUpdateSiteUrlMaintenanceConfiguration( this OrchardCoreUITestExecutorConfiguration configuration) => configuration.OrchardCoreConfiguration.BeforeAppStart += @@ -20,16 +20,16 @@ public static void SetUpdateSiteUrlMaintenanceConfiguration( return Task.CompletedTask; }; - public static void SetAddSiteOwnerPermissionToRoleMaintenanceConfiguration( + public static void SetAddAdministratorRoleToUsersWithRoleConfiguration( this OrchardCoreUITestExecutorConfiguration configuration) => configuration.OrchardCoreConfiguration.BeforeAppStart += (_, argumentsBuilder) => { argumentsBuilder .AddWithValue( - "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole:IsEnabled", + "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddAdministratorRoleToUsersWithRole:IsEnabled", value: true) .AddWithValue( - "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole:Role", + "OrchardCore:Lombiq_Hosting_Tenants_Maintenance:AddAdministratorRoleToUsersWithRole: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 ffb1386..3f3f272 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -1,9 +1,17 @@ using Atata; +using Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddAdministratorRoleToUsersWithRole; +using Lombiq.Hosting.Tenants.Maintenance.Services; using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Extensions; using Lombiq.Tests.UI.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium; +using OrchardCore; +using OrchardCore.Users; +using OrchardCore.Users.Models; using Shouldly; +using System; using System.Threading.Tasks; namespace Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Extensions; @@ -17,11 +25,30 @@ public static async Task TestSiteUrlMaintenanceExecutionAsync(this UITestContext context.Get(By.Name("ISite.BaseUrl")).GetValue().ShouldBe("https://test.com"); } - public static async Task TestSiteOwnerPermissionToRoleMaintenanceExecutionAsync(this UITestContext context) + public static async Task TestAdministratorRoleToUsersWithRoleMaintenanceExecutionAsync(this UITestContext context) { - await context.SignInDirectlyAsync(); - await context.GoToAdminRelativeUrlAsync("/Roles/Edit/Editor"); - context.Get(By.Id("Checkbox.SiteOwner")).GetDomProperty("checked").ShouldBe(bool.TrueString); + // Preparing a user account with the Editor role. The user should get the Administrator role once the + // maintenance runs. + const string userName = "TestUser"; + await context.CreateUserAsync(userName, DefaultUser.Password, "testuser@example.com"); + await context.AddUserToRoleAsync(userName, "Editor"); + + // Resetting the maintenance and restarting the app to let the it run. + await context.Application.UsingScopeAsync(async serviceProvider => + { + var maintenanceManager = serviceProvider.GetRequiredService(); + await maintenanceManager + .DeleteMaintenanceExecutionsByIdAsync(nameof(AddAdministratorRoleToUsersWithRoleMaintenanceProvider)); + }); + + await context.RestartAndWarmUpApplicationAsync(); + + await context.Application.UsingScopeAsync(async serviceProvider => + { + var userManager = serviceProvider.GetRequiredService>(); + var user = (User)await userManager.FindByNameAsync(userName); + user.RoleNames.ShouldContain(OrchardCoreConstants.Roles.Administrator); + }); } public static async Task ChangeUserSensitiveContentMaintenanceExecutionAsync(this UITestContext context) diff --git a/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs b/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs index a62185d..891a1a5 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Constants/FeatureNames.cs @@ -6,7 +6,7 @@ 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); + public const string AddAdministratorRoleToUsersWithRole = Maintenance + "." + nameof(AddAdministratorRoleToUsersWithRole); public const string RemoveUsers = Maintenance + "." + nameof(RemoveUsers); public const string ChangeUserSensitiveContent = Maintenance + "." + nameof(ChangeUserSensitiveContent); public const string DeleteOrRebuildElasticsearchIndices = Maintenance + "." + nameof(DeleteOrRebuildElasticsearchIndices); diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceOptions.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceOptions.cs new file mode 100644 index 0000000..e67f976 --- /dev/null +++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceOptions.cs @@ -0,0 +1,7 @@ +namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddAdministratorRoleToUsersWithRole; + +public class AddAdministratorRoleToUsersWithRoleMaintenanceOptions +{ + public bool IsEnabled { get; set; } + public string Role { get; set; } +} diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs new file mode 100644 index 0000000..4723063 --- /dev/null +++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs @@ -0,0 +1,56 @@ +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.Logging; +using Microsoft.Extensions.Options; +using OrchardCore.Security; +using OrchardCore.Users; +using System.Threading.Tasks; +using static OrchardCore.OrchardCoreConstants.Roles; + +namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddAdministratorRoleToUsersWithRole; + +public class AddAdministratorRoleToUsersWithRoleMaintenanceProvider : MaintenanceProviderBase +{ + private readonly IOptions _options; + private readonly RoleManager _roleManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public AddAdministratorRoleToUsersWithRoleMaintenanceProvider( + IOptions options, + RoleManager roleManager, + UserManager userManager, + ILogger logger) + { + _options = options; + _roleManager = roleManager; + _userManager = userManager; + _logger = logger; + } + + 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) + { + _logger.LogWarning("Role {Role} not found. The maintenance task will not be executed.", _options.Value.Role); + return; + } + + var usersInRole = await _userManager.GetUsersInRoleAsync(role.RoleName); + + foreach (var user in usersInRole) + { + if (await _userManager.IsInRoleAsync(user, Administrator)) continue; + + await _userManager.AddToRoleAsync(user, Administrator); + _logger.LogInformation("The Administrator role has been added to the user {UserName}.", user.UserName); + } + } +} diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/Startup.cs similarity index 58% rename from Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs rename to Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/Startup.cs index 25a612b..3fee540 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/Startup.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/Startup.cs @@ -5,9 +5,9 @@ using OrchardCore.Environment.Shell.Configuration; using OrchardCore.Modules; -namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole; +namespace Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddAdministratorRoleToUsersWithRole; -[Feature(FeatureNames.AddSiteOwnerPermissionToRole)] +[Feature(FeatureNames.AddAdministratorRoleToUsersWithRole)] public sealed class Startup : StartupBase { private readonly IShellConfiguration _shellConfiguration; @@ -17,10 +17,10 @@ public Startup(IShellConfiguration shellConfiguration) => public override void ConfigureServices(IServiceCollection services) { - services.BindAndConfigureSection( + services.BindAndConfigureSection( _shellConfiguration, - "Lombiq_Hosting_Tenants_Maintenance:AddSiteOwnerPermissionToRole"); + "Lombiq_Hosting_Tenants_Maintenance:AddAdministratorRoleToUsersWithRole"); - services.AddScoped(); + services.AddScoped(); } } diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs deleted file mode 100644 index 016e9c4..0000000 --- a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index d346d9e..0000000 --- a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddSiteOwnerPermissionToRole/AddSiteOwnerPermissionToRoleMaintenanceProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -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.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.Exists(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/Manifest.cs b/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs index 89c1abf..ba36f26 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Manifest.cs @@ -35,9 +35,10 @@ )] [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).", + Id = AddAdministratorRoleToUsersWithRole, + Name = "Lombiq Hosting - Tenants Maintenance Add Administrator Role to Users With Role", + Description = "Adds the Administrator role to users with the configured role (e.g., when the production database " + + "is copied to staging).", Category = "Maintenance", DefaultTenantOnly = true, Dependencies = [Maintenance] diff --git a/Lombiq.Hosting.Tenants.Maintenance/Readme.md b/Lombiq.Hosting.Tenants.Maintenance/Readme.md index 781b880..85997df 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Readme.md +++ b/Lombiq.Hosting.Tenants.Maintenance/Readme.md @@ -22,9 +22,9 @@ 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` +### `Lombiq.Hosting.Tenants.Maintenance.AddAdministratorRoleToUsersWithRole` -It's a maintenance task that adds the `SiteOwner` permission to a role set in the app configuration. It is available on any tenant. +A maintenance task that adds the `Administrator` role to users who have a role set in the app configuration. It is available on any tenant. The following configuration options are available to set the role: @@ -32,7 +32,7 @@ The following configuration options are available to set the role: { "OrchardCore": { "Lombiq_Hosting_Tenants_Maintenance": { - "AddSiteOwnerPermissionToRole": { + "AddAdministratorRoleToUsersWithRole": { "IsEnabled": true, "RoleName": "NameOfTheRole" } diff --git a/Lombiq.Hosting.Tenants.Maintenance/Services/IMaintenanceManager.cs b/Lombiq.Hosting.Tenants.Maintenance/Services/IMaintenanceManager.cs index cdee1a7..0cbb094 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Services/IMaintenanceManager.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Services/IMaintenanceManager.cs @@ -19,4 +19,10 @@ public interface IMaintenanceManager /// Executes all maintenance tasks if needed. /// public Task ExecuteMaintenanceTasksAsync(); + + /// + /// Deletes all the executions of a maintenance task by its ID. + /// + /// The ID of the maintenance task. + public Task DeleteMaintenanceExecutionsByIdAsync(string maintenanceId); } diff --git a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs index dcc9ce1..0c48d30 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Services/MaintenanceManager.cs @@ -63,6 +63,19 @@ public async Task ExecuteMaintenanceTasksAsync() } } + public async Task DeleteMaintenanceExecutionsByIdAsync(string maintenanceId) + { + var executions = await _session + .Query(collection: DocumentCollections.Maintenance) + .Where(execution => execution.MaintenanceId == maintenanceId) + .ListAsync(); + + foreach (var execution in executions) + { + _session.Delete(execution, collection: DocumentCollections.Maintenance); + } + } + private async Task ExecuteMaintenanceTaskIfNeededAsync( IMaintenanceProvider provider, MaintenanceTaskExecutionContext context, From c915f33ea6d9d0f44b8cfd74684786055c8719e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 00:46:19 +0100 Subject: [PATCH 04/19] Better ChangeUserSensitiveContentMaintenance test --- .../TestCaseUITestContextExtensions.cs | 48 +++++++++---------- ...osting.Tenants.Maintenance.Tests.UI.csproj | 10 ++-- ...nts.Maintenance.Tests.UI.Users.recipe.json | 19 -------- ...rRoleToUsersWithRoleMaintenanceProvider.cs | 1 + ...eUserSensitiveContentMaintenanceOptions.cs | 2 +- Lombiq.Hosting.Tenants.Maintenance/Readme.md | 2 +- 6 files changed, 30 insertions(+), 52 deletions(-) delete mode 100644 Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Recipes/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users.recipe.json diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs index 3f3f272..d995bd1 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -1,5 +1,6 @@ using Atata; using Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddAdministratorRoleToUsersWithRole; +using Lombiq.Hosting.Tenants.Maintenance.Maintenance.ChangeUserSensitiveContent; using Lombiq.Hosting.Tenants.Maintenance.Services; using Lombiq.Tests.UI.Constants; using Lombiq.Tests.UI.Extensions; @@ -7,12 +8,12 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium; -using OrchardCore; using OrchardCore.Users; using OrchardCore.Users.Models; using Shouldly; using System; using System.Threading.Tasks; +using static OrchardCore.OrchardCoreConstants.Roles; namespace Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Extensions; @@ -29,44 +30,43 @@ public static async Task TestAdministratorRoleToUsersWithRoleMaintenanceExecutio { // Preparing a user account with the Editor role. The user should get the Administrator role once the // maintenance runs. - const string userName = "TestUser"; - await context.CreateUserAsync(userName, DefaultUser.Password, "testuser@example.com"); - await context.AddUserToRoleAsync(userName, "Editor"); + await context.CreateUserAsync(); + await context.AddUserToRoleAsync(TestUser.UserName, Editor); - // Resetting the maintenance and restarting the app to let the it run. - await context.Application.UsingScopeAsync(async serviceProvider => - { - var maintenanceManager = serviceProvider.GetRequiredService(); - await maintenanceManager - .DeleteMaintenanceExecutionsByIdAsync(nameof(AddAdministratorRoleToUsersWithRoleMaintenanceProvider)); - }); - - await context.RestartAndWarmUpApplicationAsync(); + await ResetMaintenanceAsync(context, nameof(AddAdministratorRoleToUsersWithRoleMaintenanceProvider)); await context.Application.UsingScopeAsync(async serviceProvider => { var userManager = serviceProvider.GetRequiredService>(); - var user = (User)await userManager.FindByNameAsync(userName); - user.RoleNames.ShouldContain(OrchardCoreConstants.Roles.Administrator); + var user = (User)await userManager.FindByNameAsync(TestUser.UserName); + user.RoleNames.ShouldContain(Administrator); }); } public static async Task ChangeUserSensitiveContentMaintenanceExecutionAsync(this UITestContext context) { - const string username = "TestUser"; - const string lombiqUserCreatorRecipe = "Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users"; - await context.ExecuteRecipeDirectlyAsync(lombiqUserCreatorRecipe); + await context.CreateUserAsync(); + await context.AddUserToRoleAsync(TestUser.UserName, Administrator); - var loginPage = await context.GoToLoginPageAsync(); - (await loginPage.LogInWithAsync(context, username, DefaultUser.Password)) - .ShouldLeaveLoginPage(); + await ResetMaintenanceAsync(context, nameof(ChangeUserSensitiveContentMaintenanceProvider)); - await context.GoToDashboardAsync(); + await context.SignInDirectlyAsync(TestUser.UserName); await context.GoToUsersAsync(); - context.Exists(By.XPath($"//h5[contains(text(), '{username}')]")); - context.Exists(By.XPath($"//span[contains(text(), 'TestUser@lombiq.com')]")); + context.Exists(By.XPath($"//h5[contains(text(), '{TestUser.UserName}')]")); + context.Exists(By.XPath($"//span[contains(text(), '{TestUser.Email}')]")); context.Missing(By.XPath($"//h5[contains(text(), '{DefaultUser.UserName}')]")); context.Missing(By.XPath($"//span[contains(text(), '{DefaultUser.Email}')]")); } + + private static async Task ResetMaintenanceAsync(UITestContext context, string maintenanceId) + { + await context.Application.UsingScopeAsync(async serviceProvider => + { + var maintenanceManager = serviceProvider.GetRequiredService(); + await maintenanceManager.DeleteMaintenanceExecutionsByIdAsync(maintenanceId); + }); + + await context.RestartAndWarmUpApplicationAsync(); + } } diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj index 444e378..6fee41b 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj @@ -25,16 +25,12 @@ - + + - - - - PreserveNewest - true - + diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Recipes/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users.recipe.json b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Recipes/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users.recipe.json deleted file mode 100644 index 630228a..0000000 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Recipes/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users.recipe.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Users", - "displayName": "Lombiq Hosting Tenants Maintenance - UI Test Extensions - Users", - "description": "Creates a user with lombiq email.", - "author": "Lombiq Technologies", - "website": "https://github.com/Lombiq/Hosting-Tenants", - "version": "1.0", - "issetuprecipe": false, - "categories": [ "test" ], - "tags": [ "test" ], - "steps": [ - { - "name": "command", - "commands": [ - "createUser /UserName:TestUser /Password:Password1! /Email:TestUser@lombiq.com /Roles:Administrator" - ] - } - ] -} diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs index 4723063..f4a8118 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/AddAdministratorRoleToUsersWithRole/AddAdministratorRoleToUsersWithRoleMaintenanceProvider.cs @@ -33,6 +33,7 @@ public AddAdministratorRoleToUsersWithRoleMaintenanceProvider( public override Task ShouldExecuteAsync(MaintenanceTaskExecutionContext context) => Task.FromResult( _options.Value.IsEnabled && + !string.IsNullOrEmpty(_options.Value.Role) && !context.WasLatestExecutionSuccessful()); public override async Task ExecuteAsync(MaintenanceTaskExecutionContext context) diff --git a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/ChangeUserSensitiveContent/ChangeUserSensitiveContentMaintenanceOptions.cs b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/ChangeUserSensitiveContent/ChangeUserSensitiveContentMaintenanceOptions.cs index 32477d3..a48d963 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Maintenance/ChangeUserSensitiveContent/ChangeUserSensitiveContentMaintenanceOptions.cs +++ b/Lombiq.Hosting.Tenants.Maintenance/Maintenance/ChangeUserSensitiveContent/ChangeUserSensitiveContentMaintenanceOptions.cs @@ -4,5 +4,5 @@ public class ChangeUserSensitiveContentMaintenanceOptions { public bool IsEnabled { get; set; } public string TenantNames { get; set; } - public string EmailExcludePattern { get; set; } = ".+@lombiq.com$"; + public string EmailExcludePattern { get; set; } = ".+@(lombiq.com|example.com)$"; } diff --git a/Lombiq.Hosting.Tenants.Maintenance/Readme.md b/Lombiq.Hosting.Tenants.Maintenance/Readme.md index 85997df..33e3f46 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Readme.md +++ b/Lombiq.Hosting.Tenants.Maintenance/Readme.md @@ -146,7 +146,7 @@ The following configuration should be used to allow the maintenance to run: "ChangeUserSensitiveContent": { "IsEnabled": true, "TenantNames": "Default, Tenant1, Tenant2", - "EmailExcludePattern": ".+@(lombiq.com|example.com)$" + "EmailExcludePattern": ".+@(lombiq.com|example.com|mydomain.com)$" } } } From 3ab34a958771d444f6e756a0efe8676d758eb939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 00:51:01 +0100 Subject: [PATCH 05/19] Testing ChangeUserSensitiveContentMaintenance with direct code --- .../TestCaseUITestContextExtensions.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs index d995bd1..a192037 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Extensions/TestCaseUITestContextExtensions.cs @@ -11,7 +11,6 @@ using OrchardCore.Users; using OrchardCore.Users.Models; using Shouldly; -using System; using System.Threading.Tasks; using static OrchardCore.OrchardCoreConstants.Roles; @@ -50,13 +49,16 @@ public static async Task ChangeUserSensitiveContentMaintenanceExecutionAsync(thi await ResetMaintenanceAsync(context, nameof(ChangeUserSensitiveContentMaintenanceProvider)); - await context.SignInDirectlyAsync(TestUser.UserName); - await context.GoToUsersAsync(); + await context.Application.UsingScopeAsync(async serviceProvider => + { + var userManager = serviceProvider.GetRequiredService>(); - context.Exists(By.XPath($"//h5[contains(text(), '{TestUser.UserName}')]")); - context.Exists(By.XPath($"//span[contains(text(), '{TestUser.Email}')]")); - context.Missing(By.XPath($"//h5[contains(text(), '{DefaultUser.UserName}')]")); - context.Missing(By.XPath($"//span[contains(text(), '{DefaultUser.Email}')]")); + var testUser = (User)await userManager.FindByNameAsync(TestUser.UserName); + testUser.UserName.ShouldBe(TestUser.UserName); + testUser.Email.ShouldBe(TestUser.Email); + + (await userManager.FindByNameAsync(DefaultUser.UserName)).ShouldBeNull(); + }); } private static async Task ResetMaintenanceAsync(UITestContext context, string maintenanceId) From b5b1cd2999c1682b9d0fe45267520ed26f832031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 01:12:15 +0100 Subject: [PATCH 06/19] Spelling --- Lombiq.Hosting.Tenants.Maintenance/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.Maintenance/Readme.md b/Lombiq.Hosting.Tenants.Maintenance/Readme.md index 33e3f46..2a729eb 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Readme.md +++ b/Lombiq.Hosting.Tenants.Maintenance/Readme.md @@ -146,7 +146,7 @@ The following configuration should be used to allow the maintenance to run: "ChangeUserSensitiveContent": { "IsEnabled": true, "TenantNames": "Default, Tenant1, Tenant2", - "EmailExcludePattern": ".+@(lombiq.com|example.com|mydomain.com)$" + "EmailExcludePattern": ".+@(lombiq.com|example.com|foo.com)$" } } } From caf086a54907adae7b4be1483d49557a3e6857c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 01:35:39 +0100 Subject: [PATCH 07/19] Marking permission as security critical --- .../Permissions/TenantAdminPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.Admin.Login/Permissions/TenantAdminPermissions.cs b/Lombiq.Hosting.Tenants.Admin.Login/Permissions/TenantAdminPermissions.cs index 6d2bc4c..f49d6f4 100644 --- a/Lombiq.Hosting.Tenants.Admin.Login/Permissions/TenantAdminPermissions.cs +++ b/Lombiq.Hosting.Tenants.Admin.Login/Permissions/TenantAdminPermissions.cs @@ -7,7 +7,7 @@ namespace Lombiq.Hosting.Tenants.Admin.Login.Permissions; public sealed class TenantAdminPermissions : AdminPermissionBase { public static readonly Permission LoginAsAdmin = - new(nameof(LoginAsAdmin), "Able to login as an admin to any tenant from the Default tenant."); + new(nameof(LoginAsAdmin), "Able to login as an admin to any tenant from the Default tenant.", isSecurityCritical: true); protected override IEnumerable AdminPermissions => [LoginAsAdmin]; } From d09f1e937a64275ea4d82ca27886402c378556f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 02:28:20 +0100 Subject: [PATCH 08/19] Adapting EmailQuotaManagement to Site Owner being deprecated --- .../Readme.md | 2 +- .../Services/EmailQuotaService.cs | 23 +++---------------- .../Services/IEmailQuotaService.cs | 2 +- .../QuotaManagingSmtpServiceDecorator.cs | 12 +++++----- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Readme.md b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Readme.md index 6da1177..71fafcf 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Readme.md +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Readme.md @@ -18,7 +18,7 @@ This module currently contains one feature: With this module, you can specify how much space would you like to limit each tenant's maximum email quota. The default is 1000 per month. You can change this value in the _appsettings.json_ file or with an environment variable. When the quota is reached the email won't be sent and also the following will happen: -- An email will be sent to the tenant's users who has Site Owner permission. +- An email will be sent to the tenant's users with the Administrator role. - A warning message will be shown that the limit has been reached on the admin dashboard. Also a warning message is always shown with the current email quota status on the email settings page when the same host is used as the predefined one from the environment variables or from the _appsettings.json_ file. diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs index 37f436f..deea5f2 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs @@ -1,10 +1,9 @@ using Lombiq.Hosting.Tenants.EmailQuotaManagement.Models; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; +using OrchardCore; using OrchardCore.Email; using OrchardCore.Modules; -using OrchardCore.Security; -using OrchardCore.Security.Services; using OrchardCore.Users; using OrchardCore.Users.Models; using System; @@ -12,8 +11,6 @@ using System.Linq; using System.Threading.Tasks; using YesSql; -using static OrchardCore.Security.Permissions.Permission; -using static OrchardCore.Security.StandardPermissions; namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; @@ -26,7 +23,6 @@ public class EmailQuotaService : IEmailQuotaService private readonly DefaultSmtpOptions _defaultSmtpOptions; private readonly SmtpOptions _smtpOptions; private readonly IClock _clock; - private readonly IRoleService _roleService; private readonly UserManager _userManager; public EmailQuotaService( @@ -35,7 +31,6 @@ public EmailQuotaService( IOptions defaultSmtpOptions, IOptions smtpOptions, IClock clock, - IRoleService roleService, UserManager userManager) { _session = session; @@ -43,25 +38,13 @@ public EmailQuotaService( _defaultSmtpOptions = defaultSmtpOptions.Value; _smtpOptions = smtpOptions.Value; _clock = clock; - _roleService = roleService; _userManager = userManager; } public async Task> GetUserEmailsForEmailReminderAsync() { - // Get users with site owner permission. - var roles = await _roleService.GetRolesAsync(); - var siteOwnerRoles = roles.Where(role => - (role as Role)?.RoleClaims.Exists(claim => - claim.ClaimType == ClaimType && claim.ClaimValue == SiteOwner.Name) == true); - - var siteOwners = new List(); - foreach (var role in siteOwnerRoles) - { - siteOwners.AddRange(await _userManager.GetUsersInRoleAsync(role.RoleName)); - } - - return siteOwners.Select(user => (user as User)?.Email); + var administratorUsers = await _userManager.GetUsersInRoleAsync(OrchardCoreConstants.Roles.Administrator); + return administratorUsers.Select(user => (user as User)?.Email); } public bool ShouldLimitEmails() => diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/IEmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/IEmailQuotaService.cs index 006c2e4..510fb5d 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/IEmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/IEmailQuotaService.cs @@ -10,7 +10,7 @@ namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; public interface IEmailQuotaService { /// - /// Collects the emails of the users who should receive the email reminder, based on the site owner permission. + /// Collects the emails of the users who should receive the email reminder, based on the Administrator role. /// Task> GetUserEmailsForEmailReminderAsync(); diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/QuotaManagingSmtpServiceDecorator.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/QuotaManagingSmtpServiceDecorator.cs index de6a080..0782487 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/QuotaManagingSmtpServiceDecorator.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/QuotaManagingSmtpServiceDecorator.cs @@ -62,12 +62,12 @@ private async Task SendAlertEmailIfNecessaryAsync(EmailQuota emailQuota) var currentUsagePercentage = emailQuota.CurrentUsagePercentage(_emailQuotaService.GetEmailQuotaPerMonth()); if (!_emailQuotaService.ShouldSendReminderEmail(emailQuota, currentUsagePercentage)) return; - var siteOwnerEmails = (await _emailQuotaService.GetUserEmailsForEmailReminderAsync()).ToList(); + var administratorEmails = (await _emailQuotaService.GetUserEmailsForEmailReminderAsync()).ToList(); if (currentUsagePercentage >= 100) { await SendQuotaEmailAsync( emailQuota, - siteOwnerEmails, + administratorEmails, "EmailQuotaExceededError", _emailQuotaSubjectService.GetExceededEmailSubject(), currentUsagePercentage); @@ -76,7 +76,7 @@ await SendQuotaEmailAsync( await SendQuotaEmailAsync( emailQuota, - siteOwnerEmails, + administratorEmails, $"EmailQuotaWarning", _emailQuotaSubjectService.GetWarningEmailSubject(currentUsagePercentage), currentUsagePercentage); @@ -84,7 +84,7 @@ await SendQuotaEmailAsync( private Task SendQuotaEmailAsync( EmailQuota emailQuota, - IEnumerable siteOwnerEmails, + IEnumerable administratorEmails, string emailTemplateName, string subject, int percentage) @@ -94,11 +94,11 @@ private Task SendQuotaEmailAsync( IsHtmlBody = true, Subject = subject, }; - foreach (var siteOwnerEmail in siteOwnerEmails) + foreach (var administratorEmail in administratorEmails) { ShellScope.AddDeferredTask(async _ => { - emailMessage.To = siteOwnerEmail; + emailMessage.To = administratorEmail; emailMessage.Body = await _emailTemplateService.RenderEmailTemplateAsync(emailTemplateName, new { HostName = _shellSettings.Name, From c2225573bb4e38a6603c90d28afaa51ea08ccc6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 03:05:41 +0100 Subject: [PATCH 09/19] Updating to Orchard Core 2.1.0 --- .../Lombiq.Hosting.Tenants.Admin.Login.csproj | 8 ++++---- ...q.Hosting.Tenants.EmailQuotaManagement.csproj | 16 ++++++++-------- ...mbiq.Hosting.Tenants.EnvironmentRobots.csproj | 6 +++--- .../Lombiq.Hosting.Tenants.FeaturesGuard.csproj | 10 +++++----- ...q.Hosting.Tenants.IdleTenantManagement.csproj | 6 +++--- .../Lombiq.Hosting.Tenants.Maintenance.csproj | 16 ++++++++-------- .../Lombiq.Hosting.Tenants.Management.csproj | 6 +++--- ...Hosting.Tenants.MediaStorageManagement.csproj | 8 ++++---- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Lombiq.Hosting.Tenants.Admin.Login/Lombiq.Hosting.Tenants.Admin.Login.csproj b/Lombiq.Hosting.Tenants.Admin.Login/Lombiq.Hosting.Tenants.Admin.Login.csproj index d626321..14e3fa1 100644 --- a/Lombiq.Hosting.Tenants.Admin.Login/Lombiq.Hosting.Tenants.Admin.Login.csproj +++ b/Lombiq.Hosting.Tenants.Admin.Login/Lombiq.Hosting.Tenants.Admin.Login.csproj @@ -33,10 +33,10 @@ - - - - + + + + diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Lombiq.Hosting.Tenants.EmailQuotaManagement.csproj b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Lombiq.Hosting.Tenants.EmailQuotaManagement.csproj index f66ab19..15dddc1 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Lombiq.Hosting.Tenants.EmailQuotaManagement.csproj +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Lombiq.Hosting.Tenants.EmailQuotaManagement.csproj @@ -33,14 +33,14 @@ - - - - - - - - + + + + + + + + diff --git a/Lombiq.Hosting.Tenants.EnvironmentRobots/Lombiq.Hosting.Tenants.EnvironmentRobots.csproj b/Lombiq.Hosting.Tenants.EnvironmentRobots/Lombiq.Hosting.Tenants.EnvironmentRobots.csproj index 4afea05..170ab98 100644 --- a/Lombiq.Hosting.Tenants.EnvironmentRobots/Lombiq.Hosting.Tenants.EnvironmentRobots.csproj +++ b/Lombiq.Hosting.Tenants.EnvironmentRobots/Lombiq.Hosting.Tenants.EnvironmentRobots.csproj @@ -33,8 +33,8 @@ - - - + + + diff --git a/Lombiq.Hosting.Tenants.FeaturesGuard/Lombiq.Hosting.Tenants.FeaturesGuard.csproj b/Lombiq.Hosting.Tenants.FeaturesGuard/Lombiq.Hosting.Tenants.FeaturesGuard.csproj index 4f1af63..7488672 100644 --- a/Lombiq.Hosting.Tenants.FeaturesGuard/Lombiq.Hosting.Tenants.FeaturesGuard.csproj +++ b/Lombiq.Hosting.Tenants.FeaturesGuard/Lombiq.Hosting.Tenants.FeaturesGuard.csproj @@ -33,11 +33,11 @@ - - - - - + + + + + diff --git a/Lombiq.Hosting.Tenants.IdleTenantManagement/Lombiq.Hosting.Tenants.IdleTenantManagement.csproj b/Lombiq.Hosting.Tenants.IdleTenantManagement/Lombiq.Hosting.Tenants.IdleTenantManagement.csproj index a0c57de..cdd4232 100644 --- a/Lombiq.Hosting.Tenants.IdleTenantManagement/Lombiq.Hosting.Tenants.IdleTenantManagement.csproj +++ b/Lombiq.Hosting.Tenants.IdleTenantManagement/Lombiq.Hosting.Tenants.IdleTenantManagement.csproj @@ -24,9 +24,9 @@ - - - + + + diff --git a/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj b/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj index 9c2b1d8..6eb70b0 100644 --- a/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj +++ b/Lombiq.Hosting.Tenants.Maintenance/Lombiq.Hosting.Tenants.Maintenance.csproj @@ -23,14 +23,14 @@ - - - - - - - - + + + + + + + + diff --git a/Lombiq.Hosting.Tenants.Management/Lombiq.Hosting.Tenants.Management.csproj b/Lombiq.Hosting.Tenants.Management/Lombiq.Hosting.Tenants.Management.csproj index 1b820f6..86f4043 100644 --- a/Lombiq.Hosting.Tenants.Management/Lombiq.Hosting.Tenants.Management.csproj +++ b/Lombiq.Hosting.Tenants.Management/Lombiq.Hosting.Tenants.Management.csproj @@ -33,9 +33,9 @@ - - - + + + diff --git a/Lombiq.Hosting.Tenants.MediaStorageManagement/Lombiq.Hosting.Tenants.MediaStorageManagement.csproj b/Lombiq.Hosting.Tenants.MediaStorageManagement/Lombiq.Hosting.Tenants.MediaStorageManagement.csproj index 0708b7c..b446cd7 100644 --- a/Lombiq.Hosting.Tenants.MediaStorageManagement/Lombiq.Hosting.Tenants.MediaStorageManagement.csproj +++ b/Lombiq.Hosting.Tenants.MediaStorageManagement/Lombiq.Hosting.Tenants.MediaStorageManagement.csproj @@ -33,10 +33,10 @@ - - - - + + + + From c6ae6c6684fce01809a8403ea312110d2a13c33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 18:55:55 +0100 Subject: [PATCH 10/19] Adding breaking change suppression --- .../CompatibilitySuppressions.xml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Lombiq.Hosting.Tenants.Maintenance/CompatibilitySuppressions.xml diff --git a/Lombiq.Hosting.Tenants.Maintenance/CompatibilitySuppressions.xml b/Lombiq.Hosting.Tenants.Maintenance/CompatibilitySuppressions.xml new file mode 100644 index 0000000..9dba318 --- /dev/null +++ b/Lombiq.Hosting.Tenants.Maintenance/CompatibilitySuppressions.xml @@ -0,0 +1,39 @@ + + + + + CP0001 + T:Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole.AddSiteOwnerPermissionToRoleMaintenanceOptions + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + true + + + CP0001 + T:Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole.AddSiteOwnerPermissionToRoleMaintenanceProvider + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + true + + + CP0001 + T:Lombiq.Hosting.Tenants.Maintenance.Maintenance.AddSiteOwnerPermissionToRole.Startup + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + true + + + CP0002 + F:Lombiq.Hosting.Tenants.Maintenance.Constants.FeatureNames.AddSiteOwnerPermissionToRole + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + true + + + CP0006 + M:Lombiq.Hosting.Tenants.Maintenance.Services.IMaintenanceManager.DeleteMaintenanceExecutionsByIdAsync(System.String) + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.dll + true + + \ No newline at end of file From b005738548df218d65659fe229cf0e1034067d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:17:56 +0100 Subject: [PATCH 11/19] Updating UITT reference to latest alpha --- .../Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj index 6fee41b..fc763e2 100644 --- a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.csproj @@ -21,7 +21,7 @@ - + From 058b24bc9620be44b5a809ecdcf7328af8efcf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:21:22 +0100 Subject: [PATCH 12/19] More UITT references --- .../Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI.csproj | 2 +- .../Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI.csproj b/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI.csproj index a7cfd93..bc7fb1a 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI.csproj +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI/Lombiq.Hosting.Tenants.EmailQuotaManagement.Tests.UI.csproj @@ -32,7 +32,7 @@ - + diff --git a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.csproj b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.csproj index a02439f..ae1547c 100644 --- a/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.csproj +++ b/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI/Lombiq.Hosting.Tenants.IdleTenantManagement.Tests.UI.csproj @@ -21,7 +21,7 @@ - + From 44eeb65874df825d298abd2cf60a94f97a0666e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:27:32 +0100 Subject: [PATCH 13/19] Attempting to get rid of NuGet package validation false negative --- .../Services/EmailQuotaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs index deea5f2..5179c03 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs @@ -16,7 +16,7 @@ namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; // As noted under the GitHub issue https://github.com/OrchardCMS/OrchardCore/issues/15290#issuecomment-2093363619 // SaveAsync calls could be removed due to redundancy, however, removing them actually breaks functionality. -public class EmailQuotaService : IEmailQuotaService +internal class EmailQuotaService : IEmailQuotaService { private readonly ISession _session; private readonly EmailQuotaOptions _emailQuotaOptions; From dd32af89f2acdca090cb4258829b7a8c00970771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:33:15 +0100 Subject: [PATCH 14/19] Sealing --- .../Services/EmailQuotaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs index 5179c03..6fa4a4d 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs @@ -16,7 +16,7 @@ namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; // As noted under the GitHub issue https://github.com/OrchardCMS/OrchardCore/issues/15290#issuecomment-2093363619 // SaveAsync calls could be removed due to redundancy, however, removing them actually breaks functionality. -internal class EmailQuotaService : IEmailQuotaService +internal sealed class EmailQuotaService : IEmailQuotaService { private readonly ISession _session; private readonly EmailQuotaOptions _emailQuotaOptions; From f76f2fccd65d0228c275f890b01a1a21456b72ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:34:07 +0100 Subject: [PATCH 15/19] Suppressing breaking changes --- .../CompatibilitySuppressions.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml b/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml new file mode 100644 index 0000000..ad5247c --- /dev/null +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml @@ -0,0 +1,11 @@ + + + + + CP0001 + T:Lombiq.Hosting.Tenants.EmailQuotaManagement.Services.EmailQuotaService + lib/net8.0/Lombiq.Hosting.Tenants.EmailQuotaManagement.dll + lib/net8.0/Lombiq.Hosting.Tenants.EmailQuotaManagement.dll + true + + \ No newline at end of file From 852cd85928c5102610d972cdc887eae91467d02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:34:55 +0100 Subject: [PATCH 16/19] Revert "Sealing" This reverts commit dd32af89f2acdca090cb4258829b7a8c00970771. --- .../Services/EmailQuotaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs index 6fa4a4d..5179c03 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs @@ -16,7 +16,7 @@ namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; // As noted under the GitHub issue https://github.com/OrchardCMS/OrchardCore/issues/15290#issuecomment-2093363619 // SaveAsync calls could be removed due to redundancy, however, removing them actually breaks functionality. -internal sealed class EmailQuotaService : IEmailQuotaService +internal class EmailQuotaService : IEmailQuotaService { private readonly ISession _session; private readonly EmailQuotaOptions _emailQuotaOptions; From e1acde70abffff19b7cdc4f8fd3f62826e2c7215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:35:11 +0100 Subject: [PATCH 17/19] Revert "Attempting to get rid of NuGet package validation false negative" This reverts commit 44eeb65874df825d298abd2cf60a94f97a0666e5. --- .../Services/EmailQuotaService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs index 5179c03..deea5f2 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/Services/EmailQuotaService.cs @@ -16,7 +16,7 @@ namespace Lombiq.Hosting.Tenants.EmailQuotaManagement.Services; // As noted under the GitHub issue https://github.com/OrchardCMS/OrchardCore/issues/15290#issuecomment-2093363619 // SaveAsync calls could be removed due to redundancy, however, removing them actually breaks functionality. -internal class EmailQuotaService : IEmailQuotaService +public class EmailQuotaService : IEmailQuotaService { private readonly ISession _session; private readonly EmailQuotaOptions _emailQuotaOptions; From 41850fa164b200f165e9b5ea52057b4413fc9e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:41:43 +0100 Subject: [PATCH 18/19] And again --- .../CompatibilitySuppressions.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml b/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml index ad5247c..8898b3e 100644 --- a/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml +++ b/Lombiq.Hosting.Tenants.EmailQuotaManagement/CompatibilitySuppressions.xml @@ -2,8 +2,8 @@ - CP0001 - T:Lombiq.Hosting.Tenants.EmailQuotaManagement.Services.EmailQuotaService + CP0002 + M:Lombiq.Hosting.Tenants.EmailQuotaManagement.Services.EmailQuotaService.#ctor(YesSql.ISession,Microsoft.Extensions.Options.IOptions{Lombiq.Hosting.Tenants.EmailQuotaManagement.Models.EmailQuotaOptions},Microsoft.Extensions.Options.IOptions{OrchardCore.Email.DefaultSmtpOptions},Microsoft.Extensions.Options.IOptions{OrchardCore.Email.SmtpOptions},OrchardCore.Modules.IClock,OrchardCore.Security.Services.IRoleService,Microsoft.AspNetCore.Identity.UserManager{OrchardCore.Users.IUser}) lib/net8.0/Lombiq.Hosting.Tenants.EmailQuotaManagement.dll lib/net8.0/Lombiq.Hosting.Tenants.EmailQuotaManagement.dll true From d5d94e448fe471dadfb218a5d0334e4b152d51be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 28 Nov 2024 19:47:59 +0100 Subject: [PATCH 19/19] And more --- .../CompatibilitySuppressions.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Lombiq.Hosting.Tenants.Maintenance.Tests.UI/CompatibilitySuppressions.xml diff --git a/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/CompatibilitySuppressions.xml b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/CompatibilitySuppressions.xml new file mode 100644 index 0000000..87f28c3 --- /dev/null +++ b/Lombiq.Hosting.Tenants.Maintenance.Tests.UI/CompatibilitySuppressions.xml @@ -0,0 +1,18 @@ + + + + + CP0001 + T:Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Extensions.MaintenanceExtensions + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.dll + true + + + CP0002 + M:Lombiq.Hosting.Tenants.Maintenance.Tests.UI.Extensions.TestCaseUITestContextExtensions.TestSiteOwnerPermissionToRoleMaintenanceExecutionAsync(Lombiq.Tests.UI.Services.UITestContext) + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.dll + lib/net8.0/Lombiq.Hosting.Tenants.Maintenance.Tests.UI.dll + true + + \ No newline at end of file