diff --git a/docs/configuration/configuration-authorization.md b/docs/configuration/configuration-authorization.md
index a260a0d4..8bf5067f 100644
--- a/docs/configuration/configuration-authorization.md
+++ b/docs/configuration/configuration-authorization.md
@@ -61,22 +61,28 @@ Here an example of how to configure client role:
There are three options to determine a source for the roles:
```csharp
+[Flags]
public enum RolesClaimTransformationSource
{
///
- /// No Transformation. Default
+ /// Specifies that no transformation should be applied from the source.
///
- None,
+ None = 0,
///
- /// Use realm roles as source
+ /// Specifies that transformation should be applied to the realm.
///
- Realm,
+ Realm = 1 << 0,
///
- /// Use client roles as source
+ /// Specifies that transformation should be applied to the resource access.
///
- ResourceAccess
+ ResourceAccess = 1 << 1,
+
+ ///
+ /// Specifies that transformation should be applied to all sources.
+ ///
+ All = Realm | ResourceAccess
}
```
@@ -135,4 +141,13 @@ If we specify `KeycloakAuthorizationOptions.EnableRolesMapping = RolesClaimTrans
Result = ["manage-account","manage-account-links","view-profile"]
+See below the table for possible combinations:
+
+| EnableRolesMapping | RolesResource | Result |
+|--------------------|---------------|----------------------------------------------------------------------------------------------------------------------|
+| Realm | N/A | `["default-roles-test","offline_access","uma_authorization"]` |
+| ResourceAccess | `test-client` | `["manage-account","manage-account-links","view-profile"]` |
+| All | `test-client` | `["default-roles-test","offline_access","uma_authorization","manage-account","manage-account-links","view-profile"]` |
+
+
The target claim can be configured `KeycloakAuthorizationOptions.RoleClaimType`, the default value is "role".
diff --git a/src/Keycloak.AuthServices.Authorization/Claims/KeycloakRolesClaimsTransformation.cs b/src/Keycloak.AuthServices.Authorization/Claims/KeycloakRolesClaimsTransformation.cs
index 96a7c9f1..d168eb63 100644
--- a/src/Keycloak.AuthServices.Authorization/Claims/KeycloakRolesClaimsTransformation.cs
+++ b/src/Keycloak.AuthServices.Authorization/Claims/KeycloakRolesClaimsTransformation.cs
@@ -70,7 +70,7 @@ public Task TransformAsync(ClaimsPrincipal principal)
var result = principal.Clone();
- if (this.roleSource == RolesClaimTransformationSource.ResourceAccess)
+ if (this.roleSource.HasFlag(RolesClaimTransformationSource.ResourceAccess))
{
var resourceAccessValue = principal.FindFirst("resource_access")?.Value;
if (string.IsNullOrWhiteSpace(resourceAccessValue))
@@ -107,11 +107,9 @@ out var rolesElement
identity.AddClaim(new Claim(this.roleClaimType, value));
}
}
-
- return Task.FromResult(result);
}
- if (this.roleSource == RolesClaimTransformationSource.Realm)
+ if (this.roleSource.HasFlag(RolesClaimTransformationSource.Realm))
{
var realmAccessValue = principal.FindFirst("realm_access")?.Value;
if (string.IsNullOrWhiteSpace(realmAccessValue))
@@ -144,8 +142,6 @@ out var rolesElement
identity.AddClaim(new Claim(this.roleClaimType, value));
}
}
-
- return Task.FromResult(result);
}
}
diff --git a/src/Keycloak.AuthServices.Authorization/KeycloakAuthorizationOptions.cs b/src/Keycloak.AuthServices.Authorization/KeycloakAuthorizationOptions.cs
index 2a464f20..32a3bb0e 100644
--- a/src/Keycloak.AuthServices.Authorization/KeycloakAuthorizationOptions.cs
+++ b/src/Keycloak.AuthServices.Authorization/KeycloakAuthorizationOptions.cs
@@ -32,20 +32,26 @@ public class KeycloakAuthorizationOptions : KeycloakInstallationOptions
///
/// RolesClaimTransformationSource
///
+[Flags]
public enum RolesClaimTransformationSource
{
///
/// Specifies that no transformation should be applied from the source.
///
- None,
+ None = 0,
///
/// Specifies that transformation should be applied to the realm.
///
- Realm,
+ Realm = 1 << 0,
///
/// Specifies that transformation should be applied to the resource access.
///
- ResourceAccess
+ ResourceAccess = 1 << 1,
+
+ ///
+ /// Specifies that transformation should be applied to all sources.
+ ///
+ All = Realm | ResourceAccess
}
diff --git a/tests/Keycloak.AuthServices.Authorization.Tests/Claims/KeycloakRolesClaimsTransformationTests.cs b/tests/Keycloak.AuthServices.Authorization.Tests/Claims/KeycloakRolesClaimsTransformationTests.cs
index 376eebfe..416bbffa 100644
--- a/tests/Keycloak.AuthServices.Authorization.Tests/Claims/KeycloakRolesClaimsTransformationTests.cs
+++ b/tests/Keycloak.AuthServices.Authorization.Tests/Claims/KeycloakRolesClaimsTransformationTests.cs
@@ -19,12 +19,55 @@ public async Task ClaimsTransformationShouldMap(RolesClaimTransformationSource r
for (var testCount = 0; testCount < 3; testCount++)
{
claimsPrincipal = await target.TransformAsync(claimsPrincipal);
- claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleUserClaim).Should().BeTrue();
- claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleSuperUserClaim).Should().BeTrue();
+ switch (roleSource)
+ {
+ case RolesClaimTransformationSource.Realm:
+ claimsPrincipal.HasClaim(ClaimTypes.Role, RealmRoleUserClaim).Should().BeTrue();
+ claimsPrincipal.HasClaim(ClaimTypes.Role, RealmRoleSuperUserClaim).Should().BeTrue();
+ break;
+ case RolesClaimTransformationSource.ResourceAccess:
+ claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleUserClaim).Should().BeTrue();
+ claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleSuperUserClaim).Should().BeTrue();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(roleSource), roleSource, "Unexpected role source");
+ }
claimsPrincipal.Claims.Count(item => ClaimTypes.Role == item.Type).Should().Be(2);
}
}
+ [Fact]
+ public async Task ClaimsTransformationShouldHandleNoneSource()
+ {
+ var target = new KeycloakRolesClaimsTransformation(
+ ClaimTypes.Role,
+ RolesClaimTransformationSource.None,
+ ClientId
+ );
+ var claimsPrincipal = GetClaimsPrincipal(MyRealmClaimValue, MyResourceClaimValue);
+
+ claimsPrincipal = await target.TransformAsync(claimsPrincipal);
+ claimsPrincipal.Claims.Count(item => ClaimTypes.Role == item.Type).Should().Be(0);
+ }
+
+ [Fact]
+ public async Task ClaimsTransformationShouldHandleAllSource()
+ {
+ var target = new KeycloakRolesClaimsTransformation(
+ ClaimTypes.Role,
+ RolesClaimTransformationSource.All,
+ ClientId
+ );
+ var claimsPrincipal = GetClaimsPrincipal(MyRealmClaimValue, MyResourceClaimValue);
+
+ claimsPrincipal = await target.TransformAsync(claimsPrincipal);
+ claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleUserClaim).Should().BeTrue();
+ claimsPrincipal.HasClaim(ClaimTypes.Role, AppRoleSuperUserClaim).Should().BeTrue();
+ claimsPrincipal.HasClaim(ClaimTypes.Role, RealmRoleUserClaim).Should().BeTrue();
+ claimsPrincipal.HasClaim(ClaimTypes.Role, RealmRoleSuperUserClaim).Should().BeTrue();
+ claimsPrincipal.Claims.Count(item => ClaimTypes.Role == item.Type).Should().Be(4);
+ }
+
[Fact]
public async Task ClaimsTransformationShouldHandleMissingResourceClaim()
{
@@ -72,8 +115,8 @@ public async Task ClaimsTransformationShouldHandleMissingResourceClaim()
"""
{
"roles": [
- "my_client_app_role_user",
- "my_client_app_role_super_user"
+ "realm_role_user",
+ "realm_role_super_user"
]
}
""";
@@ -81,6 +124,8 @@ public async Task ClaimsTransformationShouldHandleMissingResourceClaim()
// Fake claim values
private const string AppRoleUserClaim = "my_client_app_role_user";
private const string AppRoleSuperUserClaim = "my_client_app_role_super_user";
+ private const string RealmRoleUserClaim = "realm_role_user";
+ private const string RealmRoleSuperUserClaim = "realm_role_super_user";
// The issuer/original issuer
private const string MyUrl = "https://keycloak.mydomain.com/realms/my_realm";