From aa3b62ae11fefc9d0e00a474a8e93aee2dee00ff Mon Sep 17 00:00:00 2001 From: Marie Daly Date: Thu, 12 Feb 2026 11:23:51 +0000 Subject: [PATCH 1/2] Added RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST - Fixes policy enforcement gap for direct access grant flows. Signed-off-by: Marie Daly --- .../condition/ClientAccessTypeCondition.java | 1 + .../oidc/LightWeightAccessTokenTest.java | 41 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java index c7812c0e260c..854328e37d84 100644 --- a/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java +++ b/services/src/main/java/org/keycloak/services/clientpolicy/condition/ClientAccessTypeCondition.java @@ -70,6 +70,7 @@ public String getProviderId() { public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPolicyException { switch (context.getEvent()) { case AUTHORIZATION_REQUEST: + case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST: case TOKEN_REQUEST: case TOKEN_RESPONSE: case SERVICE_ACCOUNT_TOKEN_REQUEST: diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java index 053e072ed1d3..30c4f5f569b3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java @@ -56,6 +56,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory; +import org.keycloak.services.clientpolicy.condition.ClientAccessTypeConditionFactory; import org.keycloak.services.clientpolicy.executor.UseLightweightAccessTokenExecutorFactory; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; @@ -110,7 +111,6 @@ import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.NEW_ROLE_NAME; import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.ROLE_CONFIG; import static org.keycloak.testsuite.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig; public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { private static final Logger logger = Logger.getLogger(LightWeightAccessTokenTest.class); @@ -608,6 +608,43 @@ public void testAdminApiWithLightweightAccessAndSubClaim() { setScopeProtocolMapper("master", OIDCLoginProtocolFactory.BASIC_SCOPE, "sub", true, false, false); } + @Test + public void testPublicClientWithLightweightAccessTokenViaDirectGrant() throws Exception { + // Setup client policy with client-access-type=public + lightweight token executor + String json = (new ClientPoliciesUtil.ClientProfilesBuilder()).addProfile( + (new ClientPoliciesUtil.ClientProfileBuilder()).createProfile(PROFILE_NAME, "Lightweight Token for Public Clients") + .addExecutor(UseLightweightAccessTokenExecutorFactory.PROVIDER_ID, null) + .toRepresentation() + ).toString(); + updateProfiles(json); + + json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy( + (new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Public Client Policy", Boolean.TRUE) + .addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, + ClientPoliciesUtil.createClientAccessTypeConditionConfig(List.of(ClientAccessTypeConditionFactory.TYPE_PUBLIC))) + .addProfile(PROFILE_NAME) + .toRepresentation() + ).toString(); + updatePolicies(json); + + // Create public client with direct access grants + String publicClientId = generateSuffixedName("public-direct-grant-client"); + createClientByAdmin(publicClientId, (ClientRepresentation clientRep) -> { + clientRep.setPublicClient(Boolean.TRUE); + clientRep.setDirectAccessGrantsEnabled(Boolean.TRUE); + }); + + // Perform direct access grant (POST with username/password) + oauth.client(publicClientId); + AccessTokenResponse response = oauth.doPasswordGrantRequest(TEST_USER_NAME, TEST_USER_PASSWORD); + + // Verify lightweight token + AccessToken token = oauth.verifyToken(response.getAccessToken()); + AccessTokenContext ctx = testingClient.testing(REALM_NAME).getTokenContext(token.getId()); + Assert.assertEquals(AccessTokenContext.TokenType.LIGHTWEIGHT, ctx.getTokenType()); + Assert.assertEquals(TEST_USER_PASSWORD, ctx.getGrantType()); + } + private void removeSession(final String sessionId) { testingClient.testing().removeExpired(REALM_NAME); try { @@ -900,7 +937,7 @@ private void setUseLightweightAccessTokenExecutor() throws Exception { json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy( (new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Use Lightweight Access Token Policy", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, - createAnyClientConditionConfig()) + ClientPoliciesUtil.createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) .toRepresentation() ).toString(); From 01fca96e59c8628655b2140e059e0c2bcfb6e431 Mon Sep 17 00:00:00 2001 From: Marie Daly Date: Thu, 12 Feb 2026 11:39:52 +0000 Subject: [PATCH 2/2] Updates to test Signed-off-by: Marie Daly --- .../keycloak/testsuite/oidc/LightWeightAccessTokenTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java index 30c4f5f569b3..cb2d4faeb8fd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/LightWeightAccessTokenTest.java @@ -111,6 +111,7 @@ import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.NEW_ROLE_NAME; import static org.keycloak.protocol.oidc.mappers.RoleNameMapper.ROLE_CONFIG; import static org.keycloak.testsuite.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig; public class LightWeightAccessTokenTest extends AbstractClientPoliciesTest { private static final Logger logger = Logger.getLogger(LightWeightAccessTokenTest.class); @@ -937,7 +938,7 @@ private void setUseLightweightAccessTokenExecutor() throws Exception { json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy( (new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Use Lightweight Access Token Policy", Boolean.TRUE) .addCondition(AnyClientConditionFactory.PROVIDER_ID, - ClientPoliciesUtil.createAnyClientConditionConfig()) + createAnyClientConditionConfig()) .addProfile(PROFILE_NAME) .toRepresentation() ).toString();