Skip to content
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

feat: Convert RolesClaimTransformationSource to flags #97

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions docs/configuration/configuration-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// No Transformation. Default
/// Specifies that no transformation should be applied from the source.
/// </summary>
None,
None = 0,

/// <summary>
/// Use realm roles as source
/// Specifies that transformation should be applied to the realm.
/// </summary>
Realm,
Realm = 1 << 0,

/// <summary>
/// Use client roles as source
/// Specifies that transformation should be applied to the resource access.
/// </summary>
ResourceAccess
ResourceAccess = 1 << 1,

/// <summary>
/// Specifies that transformation should be applied to all sources.
/// </summary>
All = Realm | ResourceAccess
}
```

Expand Down Expand Up @@ -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".
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public Task<ClaimsPrincipal> 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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -144,8 +142,6 @@ out var rolesElement
identity.AddClaim(new Claim(this.roleClaimType, value));
}
}

return Task.FromResult(result);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,26 @@ public class KeycloakAuthorizationOptions : KeycloakInstallationOptions
/// <summary>
/// RolesClaimTransformationSource
/// </summary>
[Flags]
public enum RolesClaimTransformationSource
{
/// <summary>
/// Specifies that no transformation should be applied from the source.
/// </summary>
None,
None = 0,

/// <summary>
/// Specifies that transformation should be applied to the realm.
/// </summary>
Realm,
Realm = 1 << 0,

/// <summary>
/// Specifies that transformation should be applied to the resource access.
/// </summary>
ResourceAccess
ResourceAccess = 1 << 1,

/// <summary>
/// Specifies that transformation should be applied to all sources.
/// </summary>
All = Realm | ResourceAccess
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -72,15 +115,17 @@ public async Task ClaimsTransformationShouldHandleMissingResourceClaim()
"""
{
"roles": [
"my_client_app_role_user",
"my_client_app_role_super_user"
"realm_role_user",
"realm_role_super_user"
]
}
""";

// 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";
Expand Down
Loading