diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultRefreshTokenGrantProcessor.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultRefreshTokenGrantProcessor.java index 144038132ff..5a2c0e16c8d 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultRefreshTokenGrantProcessor.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/tokenprocessor/DefaultRefreshTokenGrantProcessor.java @@ -29,6 +29,7 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; +import org.wso2.carbon.identity.oauth2.model.AccessTokenExtendedAttributes; import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO; import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; @@ -36,6 +37,7 @@ import java.sql.Timestamp; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -118,6 +120,16 @@ public AccessTokenDO createAccessTokenBean(OAuthTokenReqMessageContext tokReqMsg tokReqMsgCtx.setConsentedToken(true); } } + if (log.isDebugEnabled()) { + log.debug("Setting access token extended attributes for token request in refresh token flow for client: " + + tokenReq.getClientId() + " with token id: " + tokenId); + } + if (tokenReq.getAccessTokenExtendedAttributes() != null && + tokenReq.getAccessTokenExtendedAttributes().getParameters() != null) { + accessTokenDO.setAccessTokenExtendedAttributes( + new AccessTokenExtendedAttributes( + new HashMap<>(tokenReq.getAccessTokenExtendedAttributes().getParameters()))); + } return accessTokenDO; } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Constants.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Constants.java index 78650cd05d6..a1870fc019a 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Constants.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Constants.java @@ -53,6 +53,7 @@ public static class TokenTypes { public static final String OAUTH_CODE_PERSISTENCE_ENABLE = "OAuth.EnableAuthCodePersistence"; public static final String OAUTH_ENABLE_REVOKE_TOKEN_HEADERS = "OAuth.EnableRevokeTokenHeadersInResponse"; public static final String IMPERSONATED_REFRESH_TOKEN_ENABLE = "OAuth.ImpersonatedRefreshToken.Enable"; + public static final boolean DEFAULT_IMPERSONATED_REFRESH_TOKEN_ENABLED = true; public static final String CONSOLE_CALLBACK_URL_FROM_SERVER_CONFIGS = "Console.CallbackURL"; public static final String MY_ACCOUNT_CALLBACK_URL_FROM_SERVER_CONFIGS = "MyAccount.CallbackURL"; public static final String TENANT_DOMAIN_PLACEHOLDER = "{TENANT_DOMAIN}"; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java index 2f6282de21a..9eb812658dc 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuer.java @@ -695,6 +695,10 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe tokenReqMessageContext.getOauth2AccessTokenReqDTO().getAccessTokenExtendedAttributes() .getParameters(); if (customClaims != null && !customClaims.isEmpty()) { + customClaims.remove(OAuthConstants.IMPERSONATING_ACTOR); + if (log.isDebugEnabled()) { + log.debug("Processing custom claims for JWT token. Total claims count: " + customClaims.size()); + } for (Map.Entry entry : customClaims.entrySet()) { jwtClaimsSetBuilder.claim(entry.getKey(), entry.getValue()); } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java index 919fa97a823..8e72565e8c3 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandler.java @@ -90,7 +90,6 @@ import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenBindings.NONE; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE; import static org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration.JWT_TOKEN_TYPE; -import static org.wso2.carbon.identity.oauth2.OAuth2Constants.IMPERSONATED_REFRESH_TOKEN_ENABLE; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.EXTENDED_REFRESH_TOKEN_DEFAULT_TIME; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.JWT; @@ -937,8 +936,13 @@ private OAuth2AccessTokenRespDTO createResponseWithTokenBean(OAuthTokenReqMessag } if (supportedGrantTypes.contains(OAuthConstants.GrantTypes.REFRESH_TOKEN)) { if (!tokenReqMessageContext.isImpersonationRequest() - || Boolean.parseBoolean(IdentityUtil.getProperty(IMPERSONATED_REFRESH_TOKEN_ENABLE))) { + || OAuth2Util.isImpersonatedRefreshTokenEnabled()) { tokenRespDTO.setRefreshToken(existingAccessTokenDO.getRefreshToken()); + } else { + if (log.isDebugEnabled()) { + log.debug("Impersonation request is not allowed to have a refresh token for client_id : " + + consumerKey + ", therefore not issuing a refresh token."); + } } } else { if (log.isDebugEnabled()) { diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java index 3cc18614552..495915fba93 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/RefreshGrantHandler.java @@ -276,6 +276,7 @@ private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqM tokReqMsgCtx.setScope(validationBean.getScope()); tokReqMsgCtx.getOauth2AccessTokenReqDTO().setAccessTokenExtendedAttributes( validationBean.getAccessTokenExtendedAttributes()); + propagateImpersonationInfo(tokReqMsgCtx); if (StringUtils.isNotBlank(validationBean.getTokenBindingReference()) && !NONE .equals(validationBean.getTokenBindingReference())) { Optional tokenBindingOptional = OAuthTokenPersistenceFactory.getInstance() @@ -306,6 +307,24 @@ private void setPropertiesForTokenGeneration(OAuthTokenReqMessageContext tokReqM } } + private void propagateImpersonationInfo(OAuthTokenReqMessageContext tokenReqMessageContext) { + + log.debug("Checking for impersonation information in token request"); + if (tokenReqMessageContext != null && tokenReqMessageContext.getOauth2AccessTokenReqDTO() != null && + tokenReqMessageContext.getOauth2AccessTokenReqDTO().getAccessTokenExtendedAttributes() != null) { + String impersonator = tokenReqMessageContext.getOauth2AccessTokenReqDTO() + .getAccessTokenExtendedAttributes().getParameters() + .get(OAuthConstants.IMPERSONATING_ACTOR); + if (StringUtils.isNotBlank(impersonator)) { + tokenReqMessageContext.setImpersonationRequest(true); + tokenReqMessageContext.addProperty(OAuthConstants.IMPERSONATING_ACTOR, impersonator); + if (log.isDebugEnabled()) { + log.debug("Impersonation request identified for the user: " + impersonator); + } + } + } + } + /** * Return session context identifier from authorization grant cache. For authorization code flow, we mapped it * against auth_code. For refresh token grant, we map the cache against the access token. diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java index 05ae809f35d..7ae229a408c 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/OAuth2Util.java @@ -5882,6 +5882,23 @@ public static HashSet getSupportedAuthenticatio return supportedClientAuthMethods; } + /** + * Check if impersonated refresh token is enabled. + * + * @return True if impersonated refresh token is enabled. + */ + public static boolean isImpersonatedRefreshTokenEnabled() { + + if (IdentityUtil.getProperty(OAuth2Constants.IMPERSONATED_REFRESH_TOKEN_ENABLE) != null) { + return Boolean.parseBoolean(IdentityUtil.getProperty(OAuth2Constants.IMPERSONATED_REFRESH_TOKEN_ENABLE)); + } + if (log.isDebugEnabled()) { + log.debug("Impersonated refresh token configuration not found, " + + "using default: " + OAuth2Constants.DEFAULT_IMPERSONATED_REFRESH_TOKEN_ENABLED); + } + return OAuth2Constants.DEFAULT_IMPERSONATED_REFRESH_TOKEN_ENABLED; + } + /** * Check if token persistence is enabled. * diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java index 7710576c7a9..febe2bf190a 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/JWTTokenIssuerTest.java @@ -58,6 +58,7 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.model.AccessTokenExtendedAttributes; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; import org.wso2.carbon.identity.oauth2.token.handlers.claims.JWTAccessTokenClaimProvider; import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler; @@ -335,6 +336,13 @@ public Object[][] provideClaimSetData() { OAuth2AccessTokenReqDTO tokenReqDTO = new OAuth2AccessTokenReqDTO(); tokenReqDTO.setGrantType(APPLICATION_ACCESS_TOKEN_GRANT_TYPE); tokenReqDTO.setTenantDomain("super.wso2"); + AccessTokenExtendedAttributes accessTokenExtendedAttributes = new AccessTokenExtendedAttributes(); + accessTokenExtendedAttributes.setExtendedToken(true); + HashMap params = new HashMap<>(); + params.put("testExtendingKey", "testExtendingValue"); + params.put(OAuthConstants.IMPERSONATING_ACTOR, "DUMMY_ACTOR"); + accessTokenExtendedAttributes.setParameters(params); + tokenReqDTO.setAccessTokenExtendedAttributes(accessTokenExtendedAttributes); OAuthTokenReqMessageContext tokenReqMessageContext = new OAuthTokenReqMessageContext(tokenReqDTO); AuthenticatedUser authenticatedUserForTokenReq = new AuthenticatedUser(authenticatedUserForAuthz); tokenReqMessageContext.setAuthorizedUser(authenticatedUserForTokenReq); @@ -438,6 +446,12 @@ public void testCreateJWTClaimSet(Object authzReqMessageContext, assertNotNull(jwtClaimSet.getIssueTime()); assertNotNull(jwtClaimSet.getExpirationTime()); + if (tokenReqMessageContext != null) { + assertNotNull(jwtClaimSet.getClaim("testExtendingKey")); + assertEquals(jwtClaimSet.getClaim("testExtendingKey"), "testExtendingValue"); + assertNull(jwtClaimSet.getClaim(OAuthConstants.IMPERSONATING_ACTOR)); + } + if (tokenReqMessageContext != null && ((OAuthTokenReqMessageContext) tokenReqMessageContext).getProperty(EXPIRY_TIME_JWT) != null) { diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/util/OAuth2UtilTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/util/OAuth2UtilTest.java index 8e433d03c23..bc4d16a95d8 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/util/OAuth2UtilTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/util/OAuth2UtilTest.java @@ -3177,6 +3177,26 @@ public void testGetAppResidentTenantDomain(String appResidentOrgId, String expec assertEquals(OAuth2Util.getAppResidentTenantDomain(), expected); } + @DataProvider(name = "impersonatedRefreshTokenEnabledProvider") + public Object[][] impersonatedRefreshTokenEnabledProvider() { + return new Object[][]{ + {"true", true}, + {"false", false}, + {null, true} + }; + } + + @Test(dataProvider = "impersonatedRefreshTokenEnabledProvider") + public void testIsImpersonatedRefreshTokenEnabled(String configValue, boolean expected) { + + try (MockedStatic identityUtil = mockStatic(IdentityUtil.class)) { + + identityUtil.when(() -> IdentityUtil.getProperty(OAuth2Constants.IMPERSONATED_REFRESH_TOKEN_ENABLE)) + .thenReturn(configValue); + assertEquals(OAuth2Util.isImpersonatedRefreshTokenEnabled(), expected); + } + } + @Test(expectedExceptions = IdentityOAuth2Exception.class, expectedExceptionsMessageRegExp = "Error occurred while resolving the tenant domain for the " + "organization id.")