Skip to content

Conversation

@AssahBismarkabah
Copy link
Collaborator

What this PR does / why we need it:

This PR fixes Fine-Grained Admin Permissions (FGAP) V2 compatibility issues with Keycloak 26.2.0+ by implementing graceful error handling for HTTP 501 responses and maintaining backward compatibility with older Keycloak versions.

Key Changes:

  • Added @DisabledIfSystemProperty annotations to disable FGAP V1 tests for Keycloak 26.2.0+
  • Implemented graceful error handling in ClientRepository for HTTP 501 responses
  • Updated version-specific baseline files for Keycloak 26.3.3 compatibility
  • Fixed recursive property references in pom.xml
  • Added comprehensive test suite for FGAP V2 compatibility validation

###Which issue this PR fixes:
Fixes #1305 - HTTP 501 Not Implemented with fine-grained permissions on Keycloak 26.3.0

Special notes for your reviewer:

The core issue was not that the CLI uses V1 APIs (it correctly uses V2 APIs), but that FGAP V2 must be enabled per realm even when enabled at the server level. The HTTP 501 error occurs when FGAP V2 is disabled for a specific realm, which is expected behavior.

Testing:

  • All tests pass with Keycloak 26.3.3 (mvn clean install -Dkeycloak.version=26.3.3)
  • FGAP V1 tests are properly disabled for 26.2.0+ versions
  • Backward compatibility maintained for Keycloak ≤ 26.1.x
  • HTTP 501 errors are handled gracefully with informative warnings

PR Readiness Checklist:

Complete these before marking the PR as ready to review:

  • the CHANGELOG.md release notes have been updated to reflect any significant (and particularly user-facing) changes introduced by this PR

@AssahBismarkabah AssahBismarkabah added bug enhancement dependencies Pull requests that update a dependency file java Pull requests that update Java code labels Sep 18, 2025
@AssahBismarkabah AssahBismarkabah self-assigned this Sep 18, 2025
@AssahBismarkabah AssahBismarkabah force-pushed the fix-issue-1305-FGAP-returns-501-for-keycloak-26.2.0+ branch from 3a15cb8 to 804338a Compare October 16, 2025 12:00
@AssahBismarkabah AssahBismarkabah changed the title feature/Enhanced Fine-Grained Admin Permissions (FGAP) V2 compatibility with Keycloak 26.2.0+ feature/Fine-Grained Admin Permissions (FGAP) V2 compatibility with Keycloak 26.2.0+ Oct 18, 2025
@AssahBismarkabah AssahBismarkabah marked this pull request as draft October 18, 2025 14:17
…date tests for realm management authorization
@jwklijnsma
Copy link

@AssahBismarkabah maybe why you test are failing check contrib/custom-representations/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
then
public void setLoginTheme(String loginTheme) {
this.loginTheme = loginTheme;
}

change to

public void setLoginTheme(String loginTheme) {
// prevent DB constraint violation (VARCHAR 255)
if (loginTheme != null && loginTheme.length() > 255) {
loginTheme = loginTheme.substring(0, 255);
}
this.loginTheme = loginTheme;
}

then see if test are green then

@AssahBismarkabah
Copy link
Collaborator Author

@AssahBismarkabah maybe why you test are failing check contrib/custom-representations/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java then public void setLoginTheme(String loginTheme) { this.loginTheme = loginTheme; }

change to

public void setLoginTheme(String loginTheme) { // prevent DB constraint violation (VARCHAR 255) if (loginTheme != null && loginTheme.length() > 255) { loginTheme = loginTheme.substring(0, 255); } this.loginTheme = loginTheme; }

then see if test are green then

Hello @jwklijnsma, thanks for the proposal,
Changing contrib/custom-representations/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java#setLoginTheme will only take effect if that custom representation class is actually on the runtime classpath (or the server loads the custom JAR). In the current test/runtime setup it will not be used automatically , so editing that file alone will not fix the IT test failure

i am currently implementing a client-side validation in RealmRepository so bad input causes an explicit exception in the CLI before the request reaches Keycloak.

@AssahBismarkabah AssahBismarkabah marked this pull request as ready for review October 26, 2025 14:42
Copy link
Collaborator

@Thendo20 Thendo20 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, FGAP V1 is skipped with warnings and FGAP V2 config with authorizationSchema is imported successfully tested with Keycloak version 26.3.

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Oct 27, 2025

I just tested this with Keycloak 26.4.2 and I wasn't able to import the example from #1305 (comment)

Due to this: https://github.com/keycloak/keycloak/blob/release/26.4/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java#L80

realm: my-realm
enabled: true

adminPermissionsEnabled: true

clients:
  - clientId: admin-permissions
    authorizationSettings:
      policies:
        - name: my-permission
          type: scope
          config:
            defaultResourceType: Clients
            scopes: '["manage"]'
      authorizationSchema:  # ← Required for V2
        resourceTypes:
          Clients:
            type: Clients
            scopes: [view, manage]

Yields:

keycloak-config-1  | de.adorsys.keycloak.config.exception.ImportProcessingException: Cannot update client 'admin-permissions' in realm 'my-realm': HTTP 400 Bad Request{"error":"unknown_error"}
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.ClientImportService.updateClient(ClientImportService.java:269)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.ClientImportService.updateAuthenticationFlowBindingOverrides(ClientImportService.java:325)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.ClientImportService.updateClientAuthenticationFlowBindingOverrides(ClientImportService.java:286)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.ClientImportService.doImportDependencies(ClientImportService.java:106)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.RealmImportService.configureRealm(RealmImportService.java:234)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.RealmImportService.createRealm(RealmImportService.java:187)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.RealmImportService.doImport(RealmImportService.java:152)
keycloak-config-1  | 	at de.adorsys.keycloak.config.KeycloakConfigRunner.run(KeycloakConfigRunner.java:89)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.lambda$callRunner$5(SpringApplication.java:790)
keycloak-config-1  | 	at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83)
keycloak-config-1  | 	at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60)
keycloak-config-1  | 	at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:789)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774)
keycloak-config-1  | 	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
keycloak-config-1  | 	at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:342)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
keycloak-config-1  | 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
keycloak-config-1  | 	at de.adorsys.keycloak.config.KeycloakConfigApplication.main(KeycloakConfigApplication.java:34)
keycloak-config-1  | 	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
keycloak-config-1  | 	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
keycloak-config-1  | 	at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:102)
keycloak-config-1  | 	at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:64)
keycloak-config-1  | 	at org.springframework.boot.loader.launch.PropertiesLauncher.main(PropertiesLauncher.java:580)
keycloak-config-1  | Caused by: jakarta.ws.rs.BadRequestException: HTTP 400 Bad Request
keycloak-config-1  | 	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:236)
keycloak-config-1  | 	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
keycloak-config-1  | 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:136)
keycloak-config-1  | 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:103)
keycloak-config-1  | 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:102)
keycloak-config-1  | 	at jdk.proxy2/jdk.proxy2.$Proxy93.update(Unknown Source)
keycloak-config-1  | 	at de.adorsys.keycloak.config.repository.ClientRepository.update(ClientRepository.java:136)
keycloak-config-1  | 	at de.adorsys.keycloak.config.service.ClientImportService.updateClient(ClientImportService.java:265)
keycloak-config-1  | 	... 32 common frames omitted

@rpion05
Copy link

rpion05 commented Nov 6, 2025

Thanks for developing this feature ! :)

I tested your branch fix-issue-1305-FGAP-returns-501-for-keycloak-26.2.0+

I noticed that I was not able to update an existing Policy in the client "admin-permissions" (because in FGAP v2 we no longer need to use realm-management). I received the error "policy already exists".


jakarta.ws.rs.WebApplicationException: Create method returned status Conflict (Code: 409); expected status: Created (201). Error: Policy with name [Domain managers] already exists
	at org.keycloak.admin.client.CreatedResponseUtil.getCreatedId(CreatedResponseUtil.java:67)
	at de.adorsys.keycloak.config.repository.ClientRepository.createAuthorizationPolicy(ClientRepository.java:256)
	at de.adorsys.keycloak.config.service.ClientAuthorizationImportService.createOrUpdateAuthorizationPolicy(ClientAuthorizationImportService.java:680)
	at de.adorsys.keycloak.config.service.ClientAuthorizationImportService.createOrUpdateAuthorizationPolicies(ClientAuthorizationImportService.java:663)
	at de.adorsys.keycloak.config.service.ClientAuthorizationImportService.updateAuthorization(ClientAuthorizationImportService.java:244)
	at de.adorsys.keycloak.config.service.ClientAuthorizationImportService.updateClientAuthorizationSettings(ClientAuthorizationImportService.java:166)
	at de.adorsys.keycloak.config.service.ClientAuthorizationImportService.doImport(ClientAuthorizationImportService.java:151)
	at de.adorsys.keycloak.config.service.RealmImportService.configureRealm(RealmImportService.java:237)
[.....]

Because, when keycloak-config-cli calls this API:
GET /admin/realms/{realm}/clients/{client-uuid}/authz/resource-server/settings
with {client-uuid} being the UUID of the admin-permissions client, Keycloak returns an unknown_error instead of a ResourceServerRepresentation

I created an issue on Keycloak about this "unkwown_error", you'll find more details there :
Keycloak issue

The keycloak team said that this is the expected behavior because the admin-permissions client is internally managed by Keycloak and should not be displayed.

Regarding keycloak-config-cli, is this issue known?

You can reproduce the problem with the following steps:

  1. Import data-1.yaml :

realm: test-realm
enabled: true
adminPermissionsEnabled: true

clients:
  - clientId: admin-permissions
    authorizationServicesEnabled: true
    authorizationSettings:
      allowRemoteResourceManagement: false
      policyEnforcementMode: ENFORCING
      
      policies:
        - name: Domain managers
          description: All domain managers
          type: role
          logic: POSITIVE
          decisionStrategy: UNANIMOUS
          config:
            fetchRoles: "false"

  1. Then import data-2.yaml (updated with fetchRoles => true)

realm: test-realm
enabled: true
adminPermissionsEnabled: true

clients:
  - clientId: admin-permissions
    authorizationServicesEnabled: true
    authorizationSettings:
      allowRemoteResourceManagement: false
      policyEnforcementMode: ENFORCING
      
      policies:
        - name: Domain managers
          description: All domain managers
          type: role
          logic: POSITIVE
          decisionStrategy: UNANIMOUS
          config:
            fetchRoles: "true"

You will get a 409 Conflict.

@AssahBismarkabah
Copy link
Collaborator Author

AssahBismarkabah commented Nov 10, 2025

Hello @rpion05 @antikalk

Thank you for the detailed testing and for opening the issue on Keycloak.

Based on my investigation, the admin-permissions client in FGAP V2 is system-managed and its authorization settings cannot be imported via configuration files. This is by design. When adminPermissionsEnabled: true is set at the realm level, Keycloak automatically creates the client with a predefined schema (AdminPermissionsSchema.java lines 279-313. The authorization settings (resources, scopes, policies) are then managed exclusively through the Admin Console UI or direct REST API calls after realm creation.

The 409 Conflict you're experiencing occurs because our code attempts to import authorization settings for a client that Keycloak intentionally blocks from external modification (returning 400 with "unknown_error" per AdminPermissionsSchema.java line 202-206). Keycloak's own import logic explicitly skips this client (DefaultExportImportManager.java lines 578-584.

The fix is to align with Keycloak's behavior: skip authorization import entirely for the admin-permissions client when FGAP V2 is active. Users should remove this client from their import configurations and rely on the realm-level adminPermissionsEnabled flag. Authorization policies must then be managed post-import through the Admin Console or REST API, not through declarative configuration files.

I'll implement this fix along with updated documentation clarifying that FGAP V2 authorization settings.

@antikalk
Copy link
Contributor

Authorization policies must then be managed post-import through the Admin Console or REST API, not through declarative configuration files.

imo it would still be great to be able to configure the policies with config-cli - to automate and ensure their configuration, instead of manually creating them which could lead to mistakes.

Unfortunately it looks like the migration after all is not as easy as initially thought...

@AssahBismarkabah
Copy link
Collaborator Author

Unfortunately it looks like the migration after all is not as easy as initially thought...

Agree, @antikalk. Unfortunately, with FGAP v2 the admin-permissions client is system‑managed and the relevant Admin REST endpoints are intentionally guarded, so full declarative management via config‑cli isn’t feasible without an upstream API change in Keycloak

@sonarqubecloud
Copy link

@AssahBismarkabah
Copy link
Collaborator Author

Hello @rpion05 @antikalk

I've implemented the changes to resolve the FGAP V2 409 Conflict issue.
As we suspected, the root cause was Keycloak blocking API access to the admin-permissions client, so I've updated the logic to explicitly skip authorization settings for that specific client when FGAP V2 is active, which aligns with Keycloak's own import behavior.
I also updated the documentation and example config to clarify that while system-level admin permissions must be managed manually, you can still fully configure authorization for your own application clients. Please give it another try and let me know if you run into any issues.

@jwklijnsma
Copy link

any update when this will be release ?

@MahmoudH96
Copy link

We faced the same issue when trying to upgrading keycloak from version 26.0.5 to 26.4.7, so we ended up upgrading it version 26.1.0 which is the latest version supported at this point in time. but would be very interested in knowing the timeline for releasing this feature so we plan the next upgrade.

@pschiffe
Copy link

pschiffe commented Jan 5, 2026

Hello and a happy new year to everyone 🎉

Is there any update about this PR? Next Keycloak version is around the corner.

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

Labels

bug dependencies Pull requests that update a dependency file enhancement java Pull requests that update Java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Config CLI - Keycloak Version Compability Keycloak 26.2.x and fine grained permissions

9 participants