Skip to content

Add Client resource type and scopes to authorization schema#5

Draft
ShashankFC wants to merge 1 commit into
feature-clients-authz-baselinefrom
feature-clients-authz-implementation
Draft

Add Client resource type and scopes to authorization schema#5
ShashankFC wants to merge 1 commit into
feature-clients-authz-baselinefrom
feature-clients-authz-implementation

Conversation

@ShashankFC

Copy link
Copy Markdown
Collaborator

Test 5nn---nReplicated from ai-code-review-evaluation/keycloak-greptile#5

…valuation implementation for ClientsPermissionsV2

Closes #35564

Signed-off-by: Martin Kanis <mkanis@redhat.com>
@GitHoobar

Copy link
Copy Markdown

@claude

1 similar comment
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@claude

@claude

claude Bot commented Jan 12, 2026

Copy link
Copy Markdown

Claude finished @ShashankFC's task in 1m 52s —— View job


Code Review Complete

I've reviewed the PR that adds Client resource type and scopes to the authorization schema. Here's my detailed feedback:


Summary

This PR implements fine-grained authorization for Clients in Keycloak's V2 authorization framework. The implementation adds the CLIENTS resource type with associated scopes (configure, manage, map-roles, map-roles-client-scope, map-roles-composite, view) to the authorization schema and provides the evaluation logic in ClientPermissionsV2.


Positive Aspects

  1. Consistent Architecture: The implementation follows the same pattern established for Users resource type, maintaining consistency across the codebase.

  2. Comprehensive Test Coverage: The PermissionClientTest class provides thorough coverage of various permission scenarios including single-client permissions, all-clients permissions, and role mapping.

  3. Clean Separation: The V2 implementation properly extends existing classes without breaking V1 functionality.


Issues and Recommendations

1. Inconsistent Client Resolution Logic (AdminPermissionsSchema.java:93-94, 175-183)

The resolveClient method has a potential issue where it returns the client ID in both lookup paths, but doesn't handle the case consistently:

private String resolveClient(KeycloakSession session, String id) {
    RealmModel realm = session.getContext().getRealm();
    ClientModel client = session.clients().getClientById(realm, id);

    if (client == null) {
        client = session.clients().getClientByClientId(realm, id);
    }

    return client == null ? null : client.getId();
}

Issue: This method accepts either a client UUID or clientId string, but always returns the UUID. This is inconsistent with the resolveUser method which does the same. While this works, the method name and logic suggest it should be clearer about what it's resolving.

Recommendation: Add documentation to clarify that this method normalizes any client identifier to the UUID.

2. Potential NullPointerException (ClientPermissionsV2.java:214-222)

Resource resource =  resourceStore.findByName(server, client.getId(), server.getId());
if (resource == null) {
    // check if there is permission for "all-clients". If so, load its resource and proceed with evaluation
    resource = AdminPermissionsSchema.SCHEMA.getResourceTypeResource(session, server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE);

    if (authz.getStoreFactory().getPolicyStore().findByResource(server, resource).isEmpty()) {
        return false;
    }
}

Issue: After getResourceTypeResource returns, resource could still be null, but it's used directly in findByResource(server, resource) without a null check.

Severity: High - This could cause a NullPointerException.

Recommendation: Add null check:

if (resource == null) {
    resource = AdminPermissionsSchema.SCHEMA.getResourceTypeResource(session, server, AdminPermissionsSchema.CLIENTS_RESOURCE_TYPE);
    if (resource == null || authz.getStoreFactory().getPolicyStore().findByResource(server, resource).isEmpty()) {
        return false;
    }
}

Fix this →

3. Code Duplication (ClientPermissionsV2.java:207-235, 237-260)

The two hasPermission methods (one taking ClientModel, one taking just String scope) have significant code duplication in the permission evaluation logic.

Recommendation: Consider extracting the common evaluation logic into a private helper method to reduce duplication and improve maintainability.

4. Unused Method Parameter (ClientPermissionsV2.java:262-272)

private EvaluationContext getEvaluationContext(ClientModel authorizedClient, AccessToken token) {
    ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient, token);
    return new DefaultEvaluationContext(identity, session) {
        @Override
        public Map<String, Collection<String>> getBaseAttributes() {
            Map<String, Collection<String>> attributes = super.getBaseAttributes();
            attributes.put("kc.client.id", List.of(authorizedClient.getClientId()));
            return attributes;
        }
    };
}

Issue: This method is never called in the class. It appears to be leftover code or prepared for future functionality.

Recommendation: Either remove this method if unused, or add a TODO comment explaining its future purpose.

5. Missing Documentation (ClientPermissionEvaluator.java:30-196)

The interface has been updated with V2-specific comments, but the overall javadoc could be clearer about when V2 behavior applies vs V1.

Recommendation: Add class-level documentation explaining the difference between V1 and V2 implementations and when each applies (controlled by the ADMIN_FINE_GRAINED_AUTHZ_V2 feature flag).

6. Test Cleanup Logic (PermissionClientTest.java:79-83)

@AfterEach
public void onAfter() {
    ScopePermissionsResource permissions = getScopePermissionsResource(client);
    permissions.findAll(null, null, null, -1, -1).forEach(p -> permissions.findById(p.getId()).remove());
}

Issue: This cleanup could fail silently if permission removal fails, and it doesn't follow the cleanup pattern used in AbstractPermissionTest with realm.cleanup().add().

Recommendation: Consider using the realm cleanup mechanism for consistency and better error handling.

7. Error Handling in Schema Initialization (AdminPermissionsSchema.java:106-110)

resource.updateScopes(getResourceTypes().get(resourceType).getScopes().stream().map(scopeName -> {
    Scope findByName = scopeStore.findByName(resourceServer, scopeName);
    if (findByName == null) throw new ModelException("No scopes found.");
    return findByName;
}).collect(Collectors.toSet()));

Issue: The error message "No scopes found." doesn't indicate which scope was not found, making debugging difficult.

Recommendation: Improve error message:

if (findByName == null) throw new ModelException("Scope '" + scopeName + "' not found for resource type '" + resourceType + "'.");

8. Unclear Logic in getResourceName (AdminPermissionsSchema.java:311-322)

The method only handles the USERS resource type case but doesn't have an else clause for CLIENTS:

if (USERS.getType().equals(resourceType)) {
    if (resource.getName().equals(USERS_RESOURCE_TYPE)) {
        return "All users";
    }
    UserModel user = session.users().getUserById(session.getContext().getRealm(), resource.getName());
    if (user == null) {
        throw new ModelIllegalStateException("User not found for resource [" + resource.getId() + "]");
    }
    return user.getUsername();
}

return resource.getDisplayName();

Issue: Since CLIENTS resource type was added, this method should handle that case too for consistent naming.

Recommendation: Add handling for CLIENTS resource type:

if (CLIENTS.getType().equals(resourceType)) {
    if (resource.getName().equals(CLIENTS_RESOURCE_TYPE)) {
        return "All clients";
    }
    ClientModel client = session.clients().getClientById(session.getContext().getRealm(), resource.getName());
    if (client == null) {
        throw new ModelIllegalStateException("Client not found for resource [" + resource.getId() + "]");
    }
    return client.getClientId();
}

Minor Observations

  1. AdminPermissions.java: The formatting change (line spacing) is fine but seems incidental to the main changes.

  2. Test Coverage: The tests are comprehensive but could benefit from negative test cases (e.g., testing that permissions are properly denied in various scenarios).

  3. Constants: The scope constants are well-defined and reusable across the codebase.


Conclusion

The implementation is solid and follows established patterns. The critical issue is the potential NullPointerException in ClientPermissionsV2.java:217 which should be fixed before merging. The other issues are mostly about code quality, maintainability, and completeness.

Recommendation: Address the NullPointerException issue (#2) and consider implementing the missing CLIENTS handling in getResourceName (#8) for feature completeness.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants