diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Recipes/RolesStep.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Recipes/RolesStep.cs index 26775056502..f6031d9b0ed 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Recipes/RolesStep.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Recipes/RolesStep.cs @@ -51,11 +51,33 @@ protected override async Task HandleAsync(RecipeExecutionContext context) if (role is Role r) { r.RoleDescription = roleEntry.Description; - r.RoleClaims.RemoveAll(c => c.ClaimType == Permission.ClaimType); + + if (roleEntry.PermissionBehavior == PermissionBehavior.Replace) + { + // At this point, we know we are replacing permissions. + // Remove all existing permission so we can add the replacements later. + r.RoleClaims.RemoveAll(c => c.ClaimType == Permission.ClaimType); + } if (!await _systemRoleNameProvider.IsAdminRoleAsync(roleName)) { - r.RoleClaims.AddRange(roleEntry.Permissions.Select(RoleClaim.Create)); + if (roleEntry.PermissionBehavior == PermissionBehavior.Remove) + { + // Materialize this list to prevent an exception. + var permissions = r.RoleClaims.Where(c => c.ClaimType == Permission.ClaimType && roleEntry.Permissions.Contains(c.ClaimValue)).ToArray(); + + foreach (var permission in permissions) + { + r.RoleClaims.Remove(permission); + } + } + else + { + var permissions = roleEntry.Permissions.Select(RoleClaim.Create) + .Where(newClaim => !r.RoleClaims.Exists(existingClaim => existingClaim.ClaimType == newClaim.ClaimType && existingClaim.ClaimValue == newClaim.ClaimValue)); + + r.RoleClaims.AddRange(permissions); + } } } @@ -83,4 +105,13 @@ public sealed class RolesStepRoleModel public string Description { get; set; } public string[] Permissions { get; set; } + + public PermissionBehavior PermissionBehavior { get; set; } +} + +public enum PermissionBehavior +{ + Replace, + Add, + Remove, } diff --git a/src/docs/reference/modules/Roles/README.md b/src/docs/reference/modules/Roles/README.md index df512d77dc5..0c7e4337d75 100644 --- a/src/docs/reference/modules/Roles/README.md +++ b/src/docs/reference/modules/Roles/README.md @@ -27,17 +27,50 @@ A sample of a roles configuration step: ```json { - "name": "roles", - "Roles": [ + "steps": [ { - "Name": "Journalist", - "Description" "Journalist Role", - "Permissions": ["PublishContent", "EditContent"] - }, + "name": "roles", + "Roles": [ + { + "Name": "Journalist", + "Description" "Journalist Role", + "PermissionBehavior": "Replace", + "Permissions": ["PublishContent", "EditContent"] + }, + { + "Name": "Subscriber", + "Description" "Subscriber Role", + "PermissionBehavior": "Replace", + "Permissions": [] + } + ] + } + ] +} +``` + +As of version 3.0, the `Roles` recipe includes the ability to define specific permission behaviors, giving you greater control over how permissions are managed within a role. The following behaviors are available: + +- **Replace**: This behavior removes all existing permissions associated with the role and replaces them with the new permissions from the `Permissions` collection. This is the default behavior. +- **Add**: This behavior adds the new permission(s) from the `Permissions` collection to the role, but only if they do not already exist. Existing permissions are left unchanged. +- **Remove**: This behavior removes the specified permission(s) from the role’s existing permissions based on the `Permissions` collection. + +### Example: Adding a New Permission to a Role + +For instance, to add the "CanChat" permission to the `Subscriber` role, use the following configuration: + +```json +{ + "steps": [ { - "Name": "Subscriber", - "Description" "Subscriber Role", - "Permissions": [] + "name": "roles", + "Roles": [ + { + "Name": "Subscriber", + "PermissionBehavior": "Add", + "Permissions": ["CanChat"] + } + ] } ] } diff --git a/src/docs/releases/3.0.0.md b/src/docs/releases/3.0.0.md index 3f96b53c08e..a52e416103e 100644 --- a/src/docs/releases/3.0.0.md +++ b/src/docs/releases/3.0.0.md @@ -145,9 +145,21 @@ This change is designed to simplify your integration process and make it easier ## Change Log +### Roles Module + +#### Permission Behavior Added to Roles Recipe Step + +The `Roles` recipe now includes the ability to define specific permission behaviors, allowing you to control how permissions are managed within a role. The following behaviors are available: + +- **Replace**: This behavior removes all existing permissions associated with the role and replaces them with the new permissions from the `Permissions` collection. This is the default behavior. +- **Add**: This behavior adds the new permission(s) from the `Permissions` collection to the role, but only if they do not already exist. It does not affect the existing permissions. +- **Remove**: This behavior removes the specified permission(s) in the `Permissions` collection from the role’s existing permissions. + +For more info about the new `PermissionBehavior`, check out the [documentation](../reference/modules/Roles/README.md). + ### ReCaptcha -### New ReCaptcha Shape +#### New ReCaptcha Shape A new `ReCaptcha` shape has been introduced, enabling you to render the ReCaptcha challenge using a customizable shape. For more details, please refer to the [documentation](../reference/modules/ReCaptcha/README.md).