From 869773e7b525e106b00a4b63aae50ef369f17c82 Mon Sep 17 00:00:00 2001 From: ss Date: Wed, 12 Aug 2026 22:33:00 -0600 Subject: [PATCH 01/11] Add OIDC support to Device Code flow and persist session information for token generation --- .../AuthorizationCodeGrantTypeHandler.java | 3 + .../org/forgerock/oauth2/core/DeviceCode.java | 45 ++++++++ .../core/DeviceCodeGrantTypeHandler.java | 61 ++++++++++- .../DeviceCodeVerificationResource.java | 35 ++++++ .../openam/oauth2/StatefulTokenStore.java | 2 +- .../openam/oauth2/StatelessTokenStore.java | 100 ++++++++++++++---- 6 files changed, 223 insertions(+), 23 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java index e33fe5c493..765b0c8cb1 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java @@ -103,6 +103,9 @@ public AccessToken handle(OAuth2Request request, ClientRegistration clientRegist throw new InvalidRequestException("Authorization code doesn't exist."); } + // Restore session from OAuth2Request + request.setSession(authorizationCode.getSessionId()); + final String codeVerifier = request.getParameter(OAuth2Constants.Custom.CODE_VERIFIER); // RFC 7636 §4.6: if the code was issued with a code_challenge, diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java index 4cda666fff..481e2e2e10 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java @@ -18,6 +18,8 @@ import static org.forgerock.json.JsonValueFunctions.setOf; import static org.forgerock.openam.oauth2.OAuth2Constants.CoreTokenParams.*; +import static org.forgerock.openam.oauth2.OAuth2Constants.Custom.SSO_TOKEN_ID; +import static org.forgerock.openam.oauth2.OAuth2Constants.JWTTokenParams.ACR; import static org.forgerock.openam.utils.CollectionUtils.newList; import static org.forgerock.openam.utils.Time.*; @@ -48,6 +50,7 @@ public class DeviceCode extends JsonValue implements Token { * @param token The JsonValue of the token. * @throws InvalidGrantException If the given token is not an Authorization Code token. */ + public DeviceCode(JsonValue token) throws InvalidGrantException { super(token); if (!OAuth2Constants.DeviceCode.DEVICE_CODE.equals(getTokenName()) || getTokenId() == null || @@ -115,6 +118,22 @@ public void setResourceOwnerId(String resourceOwnerId) { setStringProperty(OAuth2Constants.CoreTokenParams.USERNAME, resourceOwnerId); } + + /** + * Sets the Auth modules string. + */ + public void setAuthModules(String authModules) { + setStringProperty(AUTH_MODULES, authModules); + } + + /** + * Get the Auth modules string. + * @return The pipe-separated list of auth modules. + */ + public String getAuthModules() { + return getStringProperty(AUTH_MODULES); + } + /** * Gets the Client ID parameter. * @return The Client ID. @@ -138,6 +157,14 @@ public String getNonce() { public String getAcrValues() { return getStringProperty(OAuth2Constants.Params.ACR_VALUES); } + + /** + * Sets the ACR Values for device code object. + */ + public void setAcrValues(String acrValues) { + setStringProperty(OAuth2Constants.Params.ACR_VALUES, acrValues); + } + /** * Gets the Code Challenge Method parameter. @@ -338,6 +365,24 @@ public boolean isAuthorized() { return Boolean.valueOf(getStringProperty("AUTHORIZED")); } + + /** + * Sets the session id of the authenticating session. + * The session id. + */ + public void setSessionId(String sessionId) { + setStringProperty(SSO_TOKEN_ID, sessionId); + } + + /** + * Gets the session id of the authenticating session. + * + * @return The session id. + */ + public String getSessionId() { + return getStringProperty(SSO_TOKEN_ID); + } + /** * {@inheritDoc} */ diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java index ec67c700ef..984bee82c8 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java @@ -17,6 +17,7 @@ package org.forgerock.oauth2.core; +import static org.forgerock.oauth2.core.Utils.joinScope; import static org.forgerock.openam.oauth2.OAuth2Constants.DeviceCode.DEVICE_CODE; import static org.forgerock.openam.oauth2.OAuth2Constants.Params.REALM; import static org.forgerock.openam.utils.StringUtils.isEmpty; @@ -42,6 +43,9 @@ import org.forgerock.oauth2.core.exceptions.UnauthorizedClientException; import org.forgerock.openam.oauth2.OAuth2Constants; import org.forgerock.openam.oauth2.OAuth2UrisFactory; +import com.iplanet.sso.SSOToken; +import com.iplanet.sso.SSOTokenManager; +import com.iplanet.sso.SSOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,17 +61,19 @@ public class DeviceCodeGrantTypeHandler extends GrantTypeHandler { private final ClientRegistrationStore clientRegistrationStore; private final ClientAuthenticationFailureFactory failureFactory; private final GrantTypeAccessTokenGenerator accessTokenGenerator; + private final SSOTokenManager ssoTokenManager; @Inject public DeviceCodeGrantTypeHandler(OAuth2ProviderSettingsFactory providerSettingsFactory, ClientAuthenticator clientAuthenticator, TokenStore tokenStore, ClientRegistrationStore clientRegistrationStore, ClientAuthenticationFailureFactory failureFactory, - OAuth2UrisFactory urisFactory, GrantTypeAccessTokenGenerator accessTokenGenerator) { + OAuth2UrisFactory urisFactory, GrantTypeAccessTokenGenerator accessTokenGenerator, SSOTokenManager ssoTokenManager) { super(providerSettingsFactory, urisFactory, clientAuthenticator); this.tokenStore = tokenStore; this.clientRegistrationStore = clientRegistrationStore; this.failureFactory = failureFactory; this.accessTokenGenerator = accessTokenGenerator; + this.ssoTokenManager = ssoTokenManager; } @Override @@ -85,13 +91,14 @@ protected AccessToken handle(OAuth2Request request, ClientRegistration client, String clientId = client.getClientId(); DeviceCode deviceCode = tokenStore.readDeviceCode(clientId, code, request); - + if (deviceCode == null || !clientId.equals(deviceCode.getClientId()) || !request.getParameter(REALM).equals(deviceCode.getRealm())) { throw new AuthorizationDeclinedException(); } + AccessToken accessToken; try { if (deviceCode.isAuthorized()) { String grantType = request.getParameter(OAuth2Constants.Params.GRANT_TYPE); @@ -99,8 +106,56 @@ protected AccessToken handle(OAuth2Request request, ClientRegistration client, String resourceOwnerId = deviceCode.getResourceOwnerId(); String validatedClaims = providerSettings.validateRequestedClaims( deviceCode.getStringProperty(OAuth2Constants.Custom.CLAIMS)); - return generateAccessToken(providerSettings, grantType, clientId, resourceOwnerId, scope, + final String nonce = deviceCode.getNonce(); + + // Retore Session + String sessionId = deviceCode.getSessionId(); + + SSOToken token = null; + + if (sessionId != null) { + try { + token = ssoTokenManager.createSSOToken(sessionId); + + if (ssoTokenManager.isValidToken(token)) { + request.setSession(sessionId); + + } else { + logger.warn("Stored session is no longer valid"); + } + } catch (SSOException e) { + logger.warn("Unable to restore session {}", sessionId, e); + } + } + + accessToken = generateAccessToken(providerSettings, grantType, clientId, resourceOwnerId, scope, validatedClaims, request); + + + accessToken.addExtraData( + OAuth2Constants.Custom.NONCE, + nonce); + + + if (token != null && ssoTokenManager.isValidToken(token)) { + accessToken.addExtraData( + OAuth2Constants.Custom.SSO_TOKEN_ID, + sessionId + ); + } else { + logger.warn("Stored session is no longer valid"); + } + + + providerSettings.additionalDataToReturnFromTokenEndpoint( + accessToken, + request); + + accessToken.addExtraData( + OAuth2Constants.Custom.SSO_TOKEN_ID, + null); + + return accessToken; } if (deviceCode.getExpiryTime() < currentTimeMillis()) { diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java index a8b5789da2..63b29d2d22 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; +import com.iplanet.sso.SSOException; import com.iplanet.sso.SSOToken; import org.forgerock.oauth2.core.AuthorizationService; import org.forgerock.oauth2.core.ClientRegistration; @@ -71,6 +72,7 @@ import org.restlet.routing.Router; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.sun.identity.authentication.util.ISAuthConstants; /** * A restlet resource for user codes @@ -128,6 +130,7 @@ public DeviceCodeVerificationResource(XUIState xuiState, @Named("OAuth2Router") @Post public Representation verify(Representation body) throws ServerException, NotFoundException, InvalidGrantException, OAuth2RestletException { + final Request restletRequest = getRequest(); OAuth2Request request = requestFactory.create(restletRequest); @@ -168,7 +171,14 @@ public Representation verify(Representation body) throws ServerException, NotFou saveConsent(request); } if (consentGiven) { + ResourceOwner resourceOwner = resourceOwnerSessionValidator.validate(request); + deviceCode.setAcrValues(getAuthenticationContextClassReferenceFromRequest(request)); + SSOToken token = resourceOwnerSessionValidator.getResourceOwnerSession(request); + if (token != null) { + populateAuthenticationInfo(deviceCode, token); + } + deviceCode.setResourceOwnerId(resourceOwner.getId()); deviceCode.setAuthorized(true); tokenStore.updateDeviceCode(deviceCode, request); @@ -180,10 +190,16 @@ public Representation verify(Representation body) throws ServerException, NotFou } } else { ResourceOwner resourceOwner = resourceOwnerSessionValidator.validate(request); + deviceCode.setAcrValues(getAuthenticationContextClassReferenceFromRequest(request)); + SSOToken token = resourceOwnerSessionValidator.getResourceOwnerSession(request); + if (token != null) { + populateAuthenticationInfo(deviceCode, token); + } deviceCode.setResourceOwnerId(resourceOwner.getId()); deviceCode.setAuthorized(true); tokenStore.updateDeviceCode(deviceCode, request); } + } catch (IllegalArgumentException e) { if (e.getMessage().contains("client_id")) { throw new OAuth2RestletException(400, "invalid_request", e.getMessage(), @@ -300,4 +316,23 @@ private TemplateFactory getTemplateFactory(Context context) { protected void doCatch(Throwable throwable) { exceptionHandler.handle(throwable, getContext(), getRequest(), getResponse()); } + + + private void populateAuthenticationInfo(DeviceCode deviceCode, SSOToken token) { + if (token == null) { + return; + } + + deviceCode.setSessionId(token.getTokenID().toString()); + + try { + deviceCode.setAuthModules(token.getProperty(ISAuthConstants.AUTH_TYPE)); + } catch (SSOException e) { + logger.warn("Could not get list of auth modules from authentication", e); + } + } + + private String getAuthenticationContextClassReferenceFromRequest(OAuth2Request request) { + return (String) request.getRequest().getAttributes().get(OAuth2Constants.JWTTokenParams.ACR); + } } diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java index 725cab3a47..cd4437d9be 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java @@ -897,7 +897,7 @@ public DeviceCode createDeviceCode(Set scope, ResourceOwner resourceOwne Integer maxAge, String claims, OAuth2Request request, String codeChallenge, String codeChallengeMethod) throws ServerException, NotFoundException { - logger.message("DefaultOAuthTokenStoreImpl::Creating Authorization code"); + logger.message("DefaultOAuthTokenStoreImpl::Creating Device code"); final OAuth2ProviderSettings providerSettings = providerSettingsFactory.get(request); final String deviceCode = UUID.randomUUID().toString(); diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index 7b76e397ed..a4c3cb3293 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -201,20 +201,15 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S //realmAccess.put("roles", new HashSet<>(Arrays.asList( new String[] {"admin", "user"} ))); AuthorizationCode authCode = request.getToken(AuthorizationCode.class); + DeviceCode deviceCode = request.getToken(DeviceCode.class); + String sessionId = null; + if (authCode != null) { - String sessionId = authCode.getSessionId(); - if (StringUtils.isNotBlank(sessionId)) { - try { - final SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance(); - final SSOToken token = ssoTokenManager.createSSOToken(sessionId); - AMIdentity identity = IdUtils.getIdentity(token); - Set memberships = identity.getMemberships(IdType.GROUP); - Set roles = memberships.stream().map(m -> m.getName()).collect(Collectors.toSet());; - realmAccess.put("roles", roles); - } catch (SSOException | IdRepoException e) { - logger.error("Error retrieving session from AuthorizationCode", e); - } - } + validateSessionAndLoadRoles(authCode.getSessionId(), realmAccess); + } + + if (sessionId == null && deviceCode != null) { + validateSessionAndLoadRoles(deviceCode.getSessionId(), realmAccess); } String jwtId = UUID.randomUUID().toString(); @@ -227,7 +222,6 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S .nbf(newDate(currentTime.getMillis())) .iss(oAuth2UrisFactory.get(request).getIssuer()) .claim(SCOPE, org.apache.commons.lang.StringUtils.join(scope, " ")) - .claim("realm_access", realmAccess) .claim(CLAIMS, claims) .claim(REALM, realm) .claim(NONCE, nonce) @@ -239,6 +233,10 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S .claim(AUTH_GRANT_ID, refreshToken != null ? refreshToken.getAuthGrantId() : UUID.randomUUID().toString()) .claim(AUTH_TIME, authTime); + if (realmAccess.containsKey("roles")) { + claimsSetBuilder.claim("realm_access", realmAccess); + } + // Propagate authentication context (acr) and authentication modules (amr) into the // stateless JWT access token, mirroring the behaviour of createRefreshToken. The values // are sourced from the AuthorizationCode (authorization_code grant) or from the previous @@ -246,15 +244,33 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S // the access token payload without an extra /oauth2/tokeninfo round-trip. String authModules = null; String acr = null; + if (authCode != null) { authModules = authCode.getAuthModules(); acr = authCode.getAuthenticationContextClassReference(); + + logger.message("AuthorizationCode authModules = " + authModules); + logger.message("AuthorizationCode acr = " + acr); + + } else if (deviceCode != null) { + authModules = deviceCode.getAuthModules(); + acr = deviceCode.getAcrValues(); + + logger.message("DeviceCode authModules = " + authModules); + logger.message("DeviceCode acr = " + acr); + + } else { + RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); + + if (currentRefreshToken != null) { + authModules = currentRefreshToken.getAuthModules(); + acr = currentRefreshToken.getAuthenticationContextClassReference(); + + logger.message("RefreshToken authModules = " + authModules); + logger.message("RefreshToken acr = " + acr); + } } - RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); - if (currentRefreshToken != null) { - authModules = currentRefreshToken.getAuthModules(); - acr = currentRefreshToken.getAuthenticationContextClassReference(); - } + if (authModules != null) { claimsSetBuilder.claim(AUTH_MODULES, authModules); } @@ -273,10 +289,13 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S accessTokenContext.put(GRANT_TYPE, grantType); if (acr != null) { accessTokenContext.put(ACR, acr); + logger.message("setting DeviceCode acr acctoken context = " + acr); } if (authModules != null) { accessTokenContext.put("amr", authModules); + logger.message("setting DeviceCode authModules acctoken context = " + authModules); } + Map modifiedClaims = accessTokenModifier.getModifiedClaims(request, realm, resourceOwnerId, clientId, scope, accessTokenContext); for (Map.Entry entry : modifiedClaims.entrySet()) { @@ -536,6 +555,8 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String claimsSetBuilder.claim(NONCE, ((AuthorizationCode)token).getNonce()); } } + + String authModules = null; String acr = null; AuthorizationCode authorizationCode = request.getToken(AuthorizationCode.class); @@ -878,4 +899,45 @@ private JsonValue convertToken(StatelessToken token) { map.put(SCOPE, token.getScope()); return json(map); } + + private boolean validateSessionAndLoadRoles(String sessionId, Map> realmAccess) { + + if (StringUtils.isBlank(sessionId)) { + return false; + } + + logger.message("SessionId: " + sessionId); + + + try { + SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance(); + SSOToken token = ssoTokenManager.retrieveValidTokenWithoutResettingIdleTime(sessionId); + + if (token == null) { + logger.message("Ignoring expired or invalid session: " + sessionId); + return false; + } + + + AMIdentity identity = IdUtils.getIdentity(token); + + + Set memberships = identity.getMemberships(IdType.GROUP); + + + Set roles = memberships.stream() + .map(m -> m.getName()) + .collect(Collectors.toSet()); + + realmAccess.put("roles", roles); + + return true; + + } catch (SSOException | IdRepoException e) { + logger.message("Ignoring expired or invalid session: " + sessionId, e); + return false; + } + } + + } From 331b22d7cb3b781407b3ba5dd99b688731ff91f4 Mon Sep 17 00:00:00 2001 From: ss Date: Wed, 12 Aug 2026 22:41:27 -0600 Subject: [PATCH 02/11] remove tests logs --- .../forgerock/openam/oauth2/StatelessTokenStore.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index a4c3cb3293..19caeb2a28 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -249,16 +249,10 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S authModules = authCode.getAuthModules(); acr = authCode.getAuthenticationContextClassReference(); - logger.message("AuthorizationCode authModules = " + authModules); - logger.message("AuthorizationCode acr = " + acr); - } else if (deviceCode != null) { authModules = deviceCode.getAuthModules(); acr = deviceCode.getAcrValues(); - logger.message("DeviceCode authModules = " + authModules); - logger.message("DeviceCode acr = " + acr); - } else { RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); @@ -266,8 +260,6 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S authModules = currentRefreshToken.getAuthModules(); acr = currentRefreshToken.getAuthenticationContextClassReference(); - logger.message("RefreshToken authModules = " + authModules); - logger.message("RefreshToken acr = " + acr); } } @@ -289,11 +281,9 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S accessTokenContext.put(GRANT_TYPE, grantType); if (acr != null) { accessTokenContext.put(ACR, acr); - logger.message("setting DeviceCode acr acctoken context = " + acr); } if (authModules != null) { accessTokenContext.put("amr", authModules); - logger.message("setting DeviceCode authModules acctoken context = " + authModules); } Map modifiedClaims = accessTokenModifier.getModifiedClaims(request, realm, resourceOwnerId, From 8bf1fff76cb6bd4c0eaf0425402966b305404ac0 Mon Sep 17 00:00:00 2001 From: ss Date: Wed, 12 Aug 2026 22:53:52 -0600 Subject: [PATCH 03/11] remove tests logs --- .../org/forgerock/openam/oauth2/StatelessTokenStore.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index 19caeb2a28..5dc862521e 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -896,15 +896,12 @@ private boolean validateSessionAndLoadRoles(String sessionId, Map Date: Sat, 15 Aug 2026 17:07:18 -0600 Subject: [PATCH 04/11] change refresh token if secuence --- .../openam/oauth2/StatelessTokenStore.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index 5dc862521e..fcf5d5ace6 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -202,6 +202,7 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S AuthorizationCode authCode = request.getToken(AuthorizationCode.class); DeviceCode deviceCode = request.getToken(DeviceCode.class); + RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); String sessionId = null; if (authCode != null) { @@ -222,6 +223,7 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S .nbf(newDate(currentTime.getMillis())) .iss(oAuth2UrisFactory.get(request).getIssuer()) .claim(SCOPE, org.apache.commons.lang.StringUtils.join(scope, " ")) + .claim("realm_access", realmAccess) .claim(CLAIMS, claims) .claim(REALM, realm) .claim(NONCE, nonce) @@ -232,10 +234,6 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S .claim(AUDIT_TRACKING_ID, UUID.randomUUID().toString()) .claim(AUTH_GRANT_ID, refreshToken != null ? refreshToken.getAuthGrantId() : UUID.randomUUID().toString()) .claim(AUTH_TIME, authTime); - - if (realmAccess.containsKey("roles")) { - claimsSetBuilder.claim("realm_access", realmAccess); - } // Propagate authentication context (acr) and authentication modules (amr) into the // stateless JWT access token, mirroring the behaviour of createRefreshToken. The values @@ -245,28 +243,28 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S String authModules = null; String acr = null; + if (authCode != null) { authModules = authCode.getAuthModules(); acr = authCode.getAuthenticationContextClassReference(); - } else if (deviceCode != null) { authModules = deviceCode.getAuthModules(); acr = deviceCode.getAcrValues(); + } - } else { - RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); - - if (currentRefreshToken != null) { - authModules = currentRefreshToken.getAuthModules(); - acr = currentRefreshToken.getAuthenticationContextClassReference(); - - } + if (currentRefreshToken != null) { + + authModules = currentRefreshToken.getAuthModules(); + acr = currentRefreshToken.getAuthenticationContextClassReference(); + } if (authModules != null) { + claimsSetBuilder.claim(AUTH_MODULES, authModules); } if (acr != null) { + claimsSetBuilder.claim(ACR, acr); } @@ -543,6 +541,8 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String for(org.forgerock.oauth2.core.Token token : request.getTokens()) { if(token instanceof AuthorizationCode) { claimsSetBuilder.claim(NONCE, ((AuthorizationCode)token).getNonce()); + } else if(token instanceof DeviceCode) { + claimsSetBuilder.claim(NONCE, ((DeviceCode)token).getNonce()); } } @@ -550,15 +550,21 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String String authModules = null; String acr = null; AuthorizationCode authorizationCode = request.getToken(AuthorizationCode.class); + DeviceCode deviceCode = request.getToken(DeviceCode.class); + RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); + if (authorizationCode != null) { authModules = authorizationCode.getAuthModules(); acr = authorizationCode.getAuthenticationContextClassReference(); + } else if (deviceCode != null) { + authModules = deviceCode.getAuthModules(); + acr = deviceCode.getAcrValues(); } - RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); if (currentRefreshToken != null) { authModules = currentRefreshToken.getAuthModules(); acr = currentRefreshToken.getAuthenticationContextClassReference(); + } if (authModules != null) { @@ -927,4 +933,4 @@ private boolean validateSessionAndLoadRoles(String sessionId, Map Date: Sat, 22 Aug 2026 18:27:32 -0600 Subject: [PATCH 05/11] Avoid session restoration in Device Code/Authorization flow --- .../AuthorizationCodeGrantTypeHandler.java | 3 -- .../core/DeviceCodeGrantTypeHandler.java | 54 ++----------------- .../core/ResourceOwnerSessionValidator.java | 3 +- 3 files changed, 6 insertions(+), 54 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java index 765b0c8cb1..e33fe5c493 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/AuthorizationCodeGrantTypeHandler.java @@ -103,9 +103,6 @@ public AccessToken handle(OAuth2Request request, ClientRegistration clientRegist throw new InvalidRequestException("Authorization code doesn't exist."); } - // Restore session from OAuth2Request - request.setSession(authorizationCode.getSessionId()); - final String codeVerifier = request.getParameter(OAuth2Constants.Custom.CODE_VERIFIER); // RFC 7636 §4.6: if the code was issued with a code_challenge, diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java index 984bee82c8..a986b2a040 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCodeGrantTypeHandler.java @@ -17,7 +17,6 @@ package org.forgerock.oauth2.core; -import static org.forgerock.oauth2.core.Utils.joinScope; import static org.forgerock.openam.oauth2.OAuth2Constants.DeviceCode.DEVICE_CODE; import static org.forgerock.openam.oauth2.OAuth2Constants.Params.REALM; import static org.forgerock.openam.utils.StringUtils.isEmpty; @@ -43,9 +42,6 @@ import org.forgerock.oauth2.core.exceptions.UnauthorizedClientException; import org.forgerock.openam.oauth2.OAuth2Constants; import org.forgerock.openam.oauth2.OAuth2UrisFactory; -import com.iplanet.sso.SSOToken; -import com.iplanet.sso.SSOTokenManager; -import com.iplanet.sso.SSOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,19 +57,17 @@ public class DeviceCodeGrantTypeHandler extends GrantTypeHandler { private final ClientRegistrationStore clientRegistrationStore; private final ClientAuthenticationFailureFactory failureFactory; private final GrantTypeAccessTokenGenerator accessTokenGenerator; - private final SSOTokenManager ssoTokenManager; @Inject public DeviceCodeGrantTypeHandler(OAuth2ProviderSettingsFactory providerSettingsFactory, ClientAuthenticator clientAuthenticator, TokenStore tokenStore, ClientRegistrationStore clientRegistrationStore, ClientAuthenticationFailureFactory failureFactory, - OAuth2UrisFactory urisFactory, GrantTypeAccessTokenGenerator accessTokenGenerator, SSOTokenManager ssoTokenManager) { + OAuth2UrisFactory urisFactory, GrantTypeAccessTokenGenerator accessTokenGenerator) { super(providerSettingsFactory, urisFactory, clientAuthenticator); this.tokenStore = tokenStore; this.clientRegistrationStore = clientRegistrationStore; this.failureFactory = failureFactory; this.accessTokenGenerator = accessTokenGenerator; - this.ssoTokenManager = ssoTokenManager; } @Override @@ -108,53 +102,13 @@ protected AccessToken handle(OAuth2Request request, ClientRegistration client, deviceCode.getStringProperty(OAuth2Constants.Custom.CLAIMS)); final String nonce = deviceCode.getNonce(); - // Retore Session - String sessionId = deviceCode.getSessionId(); - - SSOToken token = null; - - if (sessionId != null) { - try { - token = ssoTokenManager.createSSOToken(sessionId); - - if (ssoTokenManager.isValidToken(token)) { - request.setSession(sessionId); - - } else { - logger.warn("Stored session is no longer valid"); - } - } catch (SSOException e) { - logger.warn("Unable to restore session {}", sessionId, e); - } - } - accessToken = generateAccessToken(providerSettings, grantType, clientId, resourceOwnerId, scope, - validatedClaims, request); - - - accessToken.addExtraData( - OAuth2Constants.Custom.NONCE, - nonce); - - - if (token != null && ssoTokenManager.isValidToken(token)) { - accessToken.addExtraData( - OAuth2Constants.Custom.SSO_TOKEN_ID, - sessionId - ); - } else { - logger.warn("Stored session is no longer valid"); - } - + validatedClaims, nonce, request); providerSettings.additionalDataToReturnFromTokenEndpoint( accessToken, request); - accessToken.addExtraData( - OAuth2Constants.Custom.SSO_TOKEN_ID, - null); - return accessToken; } @@ -185,9 +139,9 @@ protected AccessToken handle(OAuth2Request request, ClientRegistration client, } private AccessToken generateAccessToken(OAuth2ProviderSettings providerSettings, String grantType, String clientId, - String resourceOwnerId, Set scope, String validatedClaims, OAuth2Request request) + String resourceOwnerId, Set scope, String validatedClaims, String nonce, OAuth2Request request) throws ServerException, NotFoundException { return accessTokenGenerator.generateAccessToken(providerSettings, grantType, clientId, resourceOwnerId, null, - scope, validatedClaims, null, null, request); + scope, validatedClaims, null, nonce, request); } } diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/ResourceOwnerSessionValidator.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/ResourceOwnerSessionValidator.java index 32bffaceeb..d6caa62789 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/ResourceOwnerSessionValidator.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/ResourceOwnerSessionValidator.java @@ -199,7 +199,8 @@ public ResourceOwner validate(OAuth2Request request) throws ResourceOwnerAuthent throw new LoginRequiredException(); } } else if (OAuth2Constants.TokenEndpoint.PASSWORD.equals(request.getParameter(GRANT_TYPE)) - || OAuth2Constants.TokenEndpoint.CLIENT_CREDENTIALS.equals(request.getParameter(GRANT_TYPE))) { + || OAuth2Constants.TokenEndpoint.CLIENT_CREDENTIALS.equals(request.getParameter(GRANT_TYPE)) + || OAuth2Constants.TokenEndpoint.DEVICE_CODE.equals(request.getParameter(GRANT_TYPE))) { // If we're doing password grant type, the SSOToken will have been created and deleted again within // OpenAMResourceOwnerAuthenticator. The request will not have a session, and so the token will have // been null from the attempted creation in L148. From 0b6803f5a96ec597dc51c43265b4001c1e2136a2 Mon Sep 17 00:00:00 2001 From: ss Date: Sat, 22 Aug 2026 18:34:05 -0600 Subject: [PATCH 06/11] Remove unused session state from Device Code --- .../org/forgerock/oauth2/core/DeviceCode.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java index 481e2e2e10..e10f885adb 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java @@ -18,8 +18,6 @@ import static org.forgerock.json.JsonValueFunctions.setOf; import static org.forgerock.openam.oauth2.OAuth2Constants.CoreTokenParams.*; -import static org.forgerock.openam.oauth2.OAuth2Constants.Custom.SSO_TOKEN_ID; -import static org.forgerock.openam.oauth2.OAuth2Constants.JWTTokenParams.ACR; import static org.forgerock.openam.utils.CollectionUtils.newList; import static org.forgerock.openam.utils.Time.*; @@ -50,7 +48,6 @@ public class DeviceCode extends JsonValue implements Token { * @param token The JsonValue of the token. * @throws InvalidGrantException If the given token is not an Authorization Code token. */ - public DeviceCode(JsonValue token) throws InvalidGrantException { super(token); if (!OAuth2Constants.DeviceCode.DEVICE_CODE.equals(getTokenName()) || getTokenId() == null || @@ -365,23 +362,6 @@ public boolean isAuthorized() { return Boolean.valueOf(getStringProperty("AUTHORIZED")); } - - /** - * Sets the session id of the authenticating session. - * The session id. - */ - public void setSessionId(String sessionId) { - setStringProperty(SSO_TOKEN_ID, sessionId); - } - - /** - * Gets the session id of the authenticating session. - * - * @return The session id. - */ - public String getSessionId() { - return getStringProperty(SSO_TOKEN_ID); - } /** * {@inheritDoc} From 604c674718f14c76617a39a242291a235ef76035 Mon Sep 17 00:00:00 2001 From: ss Date: Sat, 22 Aug 2026 18:41:14 -0600 Subject: [PATCH 07/11] Prioritize Device Code authentication claims --- .../openam/oauth2/StatelessTokenStore.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index fcf5d5ace6..4b3a9df86b 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -250,13 +250,9 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S } else if (deviceCode != null) { authModules = deviceCode.getAuthModules(); acr = deviceCode.getAcrValues(); - } - - if (currentRefreshToken != null) { - + } else if (currentRefreshToken != null) { authModules = currentRefreshToken.getAuthModules(); acr = currentRefreshToken.getAuthenticationContextClassReference(); - } if (authModules != null) { @@ -559,12 +555,9 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String } else if (deviceCode != null) { authModules = deviceCode.getAuthModules(); acr = deviceCode.getAcrValues(); - } - - if (currentRefreshToken != null) { + } else if (currentRefreshToken != null) { authModules = currentRefreshToken.getAuthModules(); - acr = currentRefreshToken.getAuthenticationContextClassReference(); - + acr = currentRefreshToken.getAuthenticationContextClassReference(); } if (authModules != null) { From 011bf6aae176284d913b32002429c5ed95045965 Mon Sep 17 00:00:00 2001 From: ss Date: Sun, 23 Aug 2026 09:43:52 -0600 Subject: [PATCH 08/11] add amr and acr funcionallity to stateful tokens --- .../java/org/forgerock/openam/oauth2/StatefulTokenStore.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java index cd4437d9be..153e7a3a82 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatefulTokenStore.java @@ -407,6 +407,8 @@ private List getAMRFromAuthModules(OAuth2Request request, OAuth2Provider String authModules; if (request.getToken(AuthorizationCode.class) != null) { authModules = request.getToken(AuthorizationCode.class).getAuthModules(); + } else if (request.getToken(DeviceCode.class) != null) { + authModules = request.getToken(DeviceCode.class).getAuthModules(); } else if (request.getToken(RefreshToken.class) != null) { authModules = request.getToken(RefreshToken.class).getAuthModules(); } else { @@ -432,6 +434,8 @@ private List getAMRFromAuthModules(OAuth2Request request, OAuth2Provider private String getAuthenticationContextClassReference(OAuth2Request request) { if (request.getToken(AuthorizationCode.class) != null) { return request.getToken(AuthorizationCode.class).getAuthenticationContextClassReference(); + } else if(request.getToken(DeviceCode.class) != null){ + return request.getToken(DeviceCode.class).getAcrValues(); } else if (request.getToken(RefreshToken.class) != null) { return request.getToken(RefreshToken.class).getAuthenticationContextClassReference(); } else { From 5572c7518618102db3afc9b5b0bbd9206a97b8da Mon Sep 17 00:00:00 2001 From: ss Date: Sun, 23 Aug 2026 09:47:08 -0600 Subject: [PATCH 09/11] git commit -m "fix(oauth2): map auth modules to configured AMR values in stateless tokens" \ -m "Map internal OpenAM authentication module names to their configured AMR values before adding them to stateless JWT tokens. Resolve authModules from the appropriate token context and use the OAuth2 provider AMR mappings to populate the amr claim instead of exposing internal authentication module names." --- .../openam/oauth2/StatelessTokenStore.java | 135 ++++++++++-------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java index 4b3a9df86b..15ce50b2fc 100644 --- a/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java +++ b/openam-oauth2/src/main/java/org/forgerock/openam/oauth2/StatelessTokenStore.java @@ -17,7 +17,6 @@ package org.forgerock.openam.oauth2; -import static com.sun.identity.shared.DateUtils.stringToDate; import static org.forgerock.json.JsonValue.json; import static org.forgerock.openam.oauth2.OAuth2Constants.Bearer.BEARER; import static org.forgerock.openam.oauth2.OAuth2Constants.CoreTokenParams.*; @@ -92,6 +91,8 @@ import org.forgerock.openam.cts.api.tokens.Token; import org.forgerock.openam.cts.exceptions.CoreTokenException; import org.forgerock.openam.oauth2.OAuth2Constants.ProofOfPossession; +import org.forgerock.openam.oauth2.OAuth2Constants.TokenEndpoint; +import org.forgerock.openam.rest.jakarta.servlet.ServletUtils; import org.forgerock.openam.tokens.CoreTokenField; import org.forgerock.openam.utils.RealmNormaliser; import org.forgerock.openam.utils.StringUtils; @@ -100,6 +101,7 @@ import org.forgerock.util.encode.Base64; import org.forgerock.util.query.QueryFilter; import org.joda.time.Duration; +import org.restlet.Request; /** * Stateless implementation of the OAuth2 Token Store. @@ -206,11 +208,19 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S String sessionId = null; if (authCode != null) { - validateSessionAndLoadRoles(authCode.getSessionId(), realmAccess); - } - - if (sessionId == null && deviceCode != null) { - validateSessionAndLoadRoles(deviceCode.getSessionId(), realmAccess); + sessionId = authCode.getSessionId(); + if (StringUtils.isNotBlank(sessionId)) { + try { + final SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance(); + final SSOToken token = ssoTokenManager.createSSOToken(sessionId); + AMIdentity identity = IdUtils.getIdentity(token); + Set memberships = identity.getMemberships(IdType.GROUP); + Set roles = memberships.stream().map(m -> m.getName()).collect(Collectors.toSet());; + realmAccess.put("roles", roles); + } catch (SSOException | IdRepoException e) { + logger.error("Error retrieving session from AuthorizationCode", e); + } + } } String jwtId = UUID.randomUUID().toString(); @@ -244,20 +254,24 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S String acr = null; - if (authCode != null) { - authModules = authCode.getAuthModules(); - acr = authCode.getAuthenticationContextClassReference(); - } else if (deviceCode != null) { - authModules = deviceCode.getAuthModules(); - acr = deviceCode.getAcrValues(); - } else if (currentRefreshToken != null) { + if (TokenEndpoint.REFRESH_TOKEN.equals(grantType) && currentRefreshToken != null) { authModules = currentRefreshToken.getAuthModules(); acr = currentRefreshToken.getAuthenticationContextClassReference(); + + } else if (TokenEndpoint.DEVICE_CODE.equals(grantType) && deviceCode != null) { + authModules = deviceCode.getAuthModules(); + acr = deviceCode.getAcrValues(); + + } else if (authCode != null) { + authModules = authCode.getAuthModules(); + acr = authCode.getAuthenticationContextClassReference(); } + + if (authModules != null) { - claimsSetBuilder.claim(AUTH_MODULES, authModules); + claimsSetBuilder.claim("amr", getAMRFromAuthModules(authModules, providerSettings)); } if (acr != null) { @@ -277,7 +291,7 @@ public AccessToken createAccessToken(String grantType, String accessTokenType, S accessTokenContext.put(ACR, acr); } if (authModules != null) { - accessTokenContext.put("amr", authModules); + accessTokenContext.put("amr", getAMRFromAuthModules(authModules, providerSettings)); } Map modifiedClaims = accessTokenModifier.getModifiedClaims(request, realm, resourceOwnerId, @@ -549,19 +563,22 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String DeviceCode deviceCode = request.getToken(DeviceCode.class); RefreshToken currentRefreshToken = request.getToken(RefreshToken.class); - if (authorizationCode != null) { - authModules = authorizationCode.getAuthModules(); - acr = authorizationCode.getAuthenticationContextClassReference(); - } else if (deviceCode != null) { + if (TokenEndpoint.REFRESH_TOKEN.equals(grantType) && currentRefreshToken != null) { + authModules = currentRefreshToken.getAuthModules(); + acr = currentRefreshToken.getAuthenticationContextClassReference(); + + } else if (TokenEndpoint.DEVICE_CODE.equals(grantType) && deviceCode != null) { authModules = deviceCode.getAuthModules(); acr = deviceCode.getAcrValues(); - } else if (currentRefreshToken != null) { - authModules = currentRefreshToken.getAuthModules(); - acr = currentRefreshToken.getAuthenticationContextClassReference(); + + } else if (authorizationCode != null) { + authModules = authorizationCode.getAuthModules(); + acr = authorizationCode.getAuthenticationContextClassReference(); } + if (authModules != null) { - claimsSetBuilder.claim(AUTH_MODULES, authModules); + claimsSetBuilder.claim("amr", getAMRFromAuthModules(authModules, providerSettings)); } if (acr != null) { claimsSetBuilder.claim(ACR, acr); @@ -584,7 +601,7 @@ public RefreshToken createRefreshToken(String grantType, String clientId, String refreshTokenContext.put(ACR, acr); } if (authModules != null) { - refreshTokenContext.put("amr", authModules); + refreshTokenContext.put("amr", getAMRFromAuthModules(authModules, providerSettings)); } Map modifiedRefreshClaims = accessTokenModifier.getModifiedClaims(request, realm, resourceOwnerId, clientId, scope, refreshTokenContext); @@ -874,6 +891,39 @@ protected void validateTokenRealm(String tokenRealm, OAuth2Request request) thro throw new NotFoundException(e.getMessage()); } } + + private String getAuthModulesFromSSOToken(OAuth2Request request) { + String authModules = null; + try { + final SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance(); + SSOToken token = ssoTokenManager.createSSOToken(ServletUtils.getRequest(request.getRequest())); + if (token != null) { + authModules = token.getProperty(ISAuthConstants.AUTH_TYPE); + } + } catch (SSOException e) { + logger.warning("Could not get list of auth modules from authentication", e); + } + return authModules; + } + + private List getAMRFromAuthModules(String authModules, OAuth2ProviderSettings providerSettings) throws ServerException { + List amr = null; + + if (authModules != null) { + Map amrMappings = providerSettings.getAMRAuthModuleMappings(); + if (!amrMappings.isEmpty()) { + amr = new ArrayList(); + List modulesUsed = Arrays.asList(authModules.split("\\|")); + for (Map.Entry amrToModuleMapping : amrMappings.entrySet()) { + if (modulesUsed.contains(amrToModuleMapping.getValue())) { + amr.add(amrToModuleMapping.getKey()); + } + } + } + } + + return amr; + } private JsonValue convertToken(StatelessToken token) { Map map = new HashMap<>(); @@ -889,41 +939,4 @@ private JsonValue convertToken(StatelessToken token) { return json(map); } - private boolean validateSessionAndLoadRoles(String sessionId, Map> realmAccess) { - - if (StringUtils.isBlank(sessionId)) { - return false; - } - - try { - SSOTokenManager ssoTokenManager = SSOTokenManager.getInstance(); - SSOToken token = ssoTokenManager.retrieveValidTokenWithoutResettingIdleTime(sessionId); - - if (token == null) { - - return false; - } - - - AMIdentity identity = IdUtils.getIdentity(token); - - - Set memberships = identity.getMemberships(IdType.GROUP); - - - Set roles = memberships.stream() - .map(m -> m.getName()) - .collect(Collectors.toSet()); - - realmAccess.put("roles", roles); - - return true; - - } catch (SSOException | IdRepoException e) { - logger.message("Ignoring expired or invalid session: ", e); - return false; - } - } - - } \ No newline at end of file From 04eaaed8a5d27ab453e0758e22f8ab88a486fa7f Mon Sep 17 00:00:00 2001 From: ss Date: Sun, 23 Aug 2026 09:48:37 -0600 Subject: [PATCH 10/11] test(oauth2): add AMR mapping coverage for stateless tokens Add test coverage for propagating ACR and mapped AMR values from authorization codes and refresh tokens into stateless access tokens. Verify that refresh token authentication context takes precedence over the authorization code and that internal authModules values are not exposed in the resulting token. --- .../oauth2/StatelessTokenStoreTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/openam-oauth2/src/test/java/org/forgerock/openam/oauth2/StatelessTokenStoreTest.java b/openam-oauth2/src/test/java/org/forgerock/openam/oauth2/StatelessTokenStoreTest.java index 54b5a97c38..824fd87bd0 100644 --- a/openam-oauth2/src/test/java/org/forgerock/openam/oauth2/StatelessTokenStoreTest.java +++ b/openam-oauth2/src/test/java/org/forgerock/openam/oauth2/StatelessTokenStoreTest.java @@ -17,6 +17,7 @@ package org.forgerock.openam.oauth2; import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.forgerock.json.JsonValue.*; import static org.mockito.ArgumentMatchers.any; @@ -145,6 +146,11 @@ public void whenAuthorizationCodePresentAcrAndAmrGetAddedToAccessToken() throws // Given givenBaseProviderSettings(); given(utils.getConfirmationKey(request)).willReturn(null); + + Map amrMappings = new HashMap<>(); + amrMappings.put("amr1", "DataStore"); + + given(settings.getAMRAuthModuleMappings()).willReturn(amrMappings); AuthorizationCode authorizationCode = mock(AuthorizationCode.class); given(authorizationCode.getAuthModules()).willReturn("DataStore"); @@ -158,7 +164,7 @@ public void whenAuthorizationCodePresentAcrAndAmrGetAddedToAccessToken() throws // Then assertThat(token.getTokenInfo().get("acr")).isEqualTo("urn:mace:incommon:iap:silver"); - assertThat(token.getTokenInfo().get("authModules")).isEqualTo("DataStore"); + assertThat(token.getTokenInfo().get("amr")).isEqualTo(singletonList("amr1")); } @Test @@ -167,6 +173,11 @@ public void whenRefreshTokenPresentAcrAndAmrGetAddedToAccessToken() throws Excep givenBaseProviderSettings(); given(utils.getConfirmationKey(request)).willReturn(null); + Map amrMappings = new HashMap<>(); + amrMappings.put("amr2", "LDAP"); + + given(settings.getAMRAuthModuleMappings()).willReturn(amrMappings); + RefreshToken currentRefreshToken = mock(RefreshToken.class); given(currentRefreshToken.getAuthModules()).willReturn("LDAP"); given(currentRefreshToken.getAuthenticationContextClassReference()).willReturn("urn:mace:incommon:iap:bronze"); @@ -178,7 +189,7 @@ public void whenRefreshTokenPresentAcrAndAmrGetAddedToAccessToken() throws Excep // Then assertThat(token.getTokenInfo().get("acr")).isEqualTo("urn:mace:incommon:iap:bronze"); - assertThat(token.getTokenInfo().get("authModules")).isEqualTo("LDAP"); + assertThat(token.getTokenInfo().get("amr")).isEqualTo(singletonList("amr2")); } @Test @@ -186,6 +197,12 @@ public void whenRefreshTokenPresentItOverridesAuthorizationCodeAcrAndAmr() throw // Given givenBaseProviderSettings(); given(utils.getConfirmationKey(request)).willReturn(null); + + Map amrMappings = new HashMap<>(); + amrMappings.put("amr1", "DataStore"); + amrMappings.put("amr2", "LDAP"); + + given(settings.getAMRAuthModuleMappings()).willReturn(amrMappings); AuthorizationCode authorizationCode = mock(AuthorizationCode.class); given(authorizationCode.getAuthModules()).willReturn("DataStore"); @@ -204,7 +221,7 @@ public void whenRefreshTokenPresentItOverridesAuthorizationCodeAcrAndAmr() throw // Then assertThat(token.getTokenInfo().get("acr")).isEqualTo("acr-from-refresh"); - assertThat(token.getTokenInfo().get("authModules")).isEqualTo("LDAP"); + assertThat(token.getTokenInfo().get("amr")).isEqualTo(singletonList("amr2")); } @Test @@ -219,7 +236,6 @@ public void whenNoAcrOrAmrAvailableTheyAreNotAddedToAccessToken() throws Excepti // Then assertThat(token.getTokenInfo()).doesNotContainKey("acr"); - assertThat(token.getTokenInfo()).doesNotContainKey("authModules"); } @Test From 82d2808fc6b1ef6aa6d913b48e2302c121b8e4bb Mon Sep 17 00:00:00 2001 From: ss Date: Sun, 23 Aug 2026 09:51:18 -0600 Subject: [PATCH 11/11] remove set session --- .../org/forgerock/oauth2/core/DeviceCode.java | 15 ++++++++------- .../restlet/DeviceCodeVerificationResource.java | 3 --- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java index e10f885adb..615028d129 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/core/DeviceCode.java @@ -21,8 +21,10 @@ import static org.forgerock.openam.utils.CollectionUtils.newList; import static org.forgerock.openam.utils.Time.*; +import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Set; @@ -116,21 +118,20 @@ public void setResourceOwnerId(String resourceOwnerId) { } - /** - * Sets the Auth modules string. - */ + public void setAuthModules(String authModules) { - setStringProperty(AUTH_MODULES, authModules); + setStringProperty(AUTH_MODULES, authModules); } - /** - * Get the Auth modules string. + + /** + * Get the auth modules string. * @return The pipe-separated list of auth modules. */ public String getAuthModules() { return getStringProperty(AUTH_MODULES); } - + /** * Gets the Client ID parameter. * @return The Client ID. diff --git a/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java b/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java index 63b29d2d22..855c65fa0d 100644 --- a/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java +++ b/openam-oauth2/src/main/java/org/forgerock/oauth2/restlet/DeviceCodeVerificationResource.java @@ -178,7 +178,6 @@ public Representation verify(Representation body) throws ServerException, NotFou if (token != null) { populateAuthenticationInfo(deviceCode, token); } - deviceCode.setResourceOwnerId(resourceOwner.getId()); deviceCode.setAuthorized(true); tokenStore.updateDeviceCode(deviceCode, request); @@ -323,8 +322,6 @@ private void populateAuthenticationInfo(DeviceCode deviceCode, SSOToken token) { return; } - deviceCode.setSessionId(token.getTokenID().toString()); - try { deviceCode.setAuthModules(token.getProperty(ISAuthConstants.AUTH_TYPE)); } catch (SSOException e) {