Skip to content

Commit

Permalink
Merge pull request #2592 from mpmadhavig/jwt-claims
Browse files Browse the repository at this point in the history
  • Loading branch information
mpmadhavig authored Oct 28, 2024
2 parents b013e4e + e69efc6 commit 16fe5aa
Show file tree
Hide file tree
Showing 16 changed files with 426 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -643,8 +643,6 @@ public static class OIDCConfigProperties {
public static final String IS_SUBJECT_TOKEN_ENABLED = "isSubjectTokenEnabled";
public static final String SUBJECT_TOKEN_EXPIRY_TIME = "subjectTokenExpiryTime";
public static final int SUBJECT_TOKEN_EXPIRY_TIME_VALUE = 180;
public static final String IS_ACCESS_TOKEN_CLAIMS_SEPARATION_ENABLED =
"isAccessTokenClaimsSeparationEnabled";
public static final String PREVENT_TOKEN_REUSE = "PreventTokenReuse";
public static final boolean DEFAULT_VALUE_FOR_PREVENT_TOKEN_REUSE = true;
// Name of the {@code JWTClientAuthenticatorConfig} resource type in the Configuration Management API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,6 @@
<xs:sequence>
<xs:element minOccurs="0" name="OAuthVersion" nillable="true" type="xs:string"/>
<xs:element maxOccurs="unbounded" minOccurs="0" name="accessTokenClaims" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="accessTokenClaimsSeparationEnabled" type="xs:boolean"/>
<xs:element minOccurs="0" name="applicationAccessTokenExpiryTime" type="xs:long"/>
<xs:element minOccurs="0" name="applicationName" nillable="true" type="xs:string"/>
<xs:element maxOccurs="unbounded" minOccurs="0" name="audiences" nillable="true" type="xs:string"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public OAuthConsumerAppDTO getOAuthApplicationData(String consumerKey, String te
OAuthAppDO app = getOAuthApp(consumerKey, tenantDomain);
if (app != null) {
if (isAccessTokenClaimsSeparationFeatureEnabled() &&
!app.isAccessTokenClaimsSeparationEnabled()) {
!isAccessTokenClaimsSeparationEnabledForApp(consumerKey, tenantDomain)) {
// Add requested claims as access token claims if the app is not in the new access token
// claims feature.
addAccessTokenClaims(app, tenantDomain);
Expand Down Expand Up @@ -536,7 +536,6 @@ OAuthConsumerAppDTO registerAndRetrieveOAuthApplicationData(OAuthConsumerAppDTO
if (isAccessTokenClaimsSeparationFeatureEnabled()) {
validateAccessTokenClaims(application, tenantDomain);
app.setAccessTokenClaims(application.getAccessTokenClaims());
app.setAccessTokenClaimsSeparationEnabled(true);
}
}
dao.addOAuthApplication(app);
Expand Down Expand Up @@ -977,27 +976,32 @@ void updateConsumerApplication(OAuthConsumerAppDTO consumerAppDTO, boolean enabl
if (isAccessTokenClaimsSeparationFeatureEnabled()) {
// We check if the AT claims separation enabled at server level and
// the app level. If both are enabled, we validate the claims and update the app.
if (oAuthAppDO.isAccessTokenClaimsSeparationEnabled()) {
validateAccessTokenClaims(consumerAppDTO, tenantDomain);
oAuthAppDO.setAccessTokenClaims(consumerAppDTO.getAccessTokenClaims());
try {
if (isAccessTokenClaimsSeparationEnabledForApp(oAuthAppDO.getOauthConsumerKey(), tenantDomain)) {
validateAccessTokenClaims(consumerAppDTO, tenantDomain);
oAuthAppDO.setAccessTokenClaims(consumerAppDTO.getAccessTokenClaims());
}
} catch (IdentityOAuth2Exception e) {
throw new IdentityOAuthAdminException("Error while updating existing OAuth application to " +
"the new JWT access token OIDC claims separation model. Application : " +
oAuthAppDO.getApplicationName() + " Tenant : " + tenantDomain, e);
}
// We only trigger the access token claims migration if the following conditions are met.
// 1. The AT claims separation is enabled at server level.
// 2. The AT claims separation is not enabled at app level.
// 3. User tries to enable AT claims separation at app level with update app.
if (!oAuthAppDO.isAccessTokenClaimsSeparationEnabled() &&
consumerAppDTO.isAccessTokenClaimsSeparationEnabled()) {
// Add requested claims as access token claims.
try {
// 3. The access token claims are empty.
try {
if (!isAccessTokenClaimsSeparationEnabledForApp(oAuthAppDO.getOauthConsumerKey(),
tenantDomain) && oAuthAppDO.getAccessTokenClaims().length == 0) {
// Add requested claims as access token claims.
addAccessTokenClaims(oAuthAppDO, tenantDomain);
} catch (IdentityOAuth2Exception e) {
throw new IdentityOAuthAdminException("Error while updating existing OAuth application to " +
"the new JWT access token OIDC claims separation model. Application : " +
oAuthAppDO.getApplicationName() + " Tenant : " + tenantDomain, e);
}

} catch (IdentityOAuth2Exception e) {
throw new IdentityOAuthAdminException("Error while updating existing OAuth application to " +
"the new JWT access token OIDC claims separation model. Application : " +
oAuthAppDO.getApplicationName() + " Tenant : " + tenantDomain, e);
}
oAuthAppDO.setAccessTokenClaimsSeparationEnabled(consumerAppDTO
.isAccessTokenClaimsSeparationEnabled());
}
}
dao.updateConsumerApplication(oAuthAppDO);
Expand Down Expand Up @@ -2867,4 +2871,12 @@ private boolean isAccessTokenClaimsSeparationFeatureEnabled() {

return Boolean.parseBoolean(IdentityUtil.getProperty(ENABLE_CLAIMS_SEPARATION_FOR_ACCESS_TOKEN));
}

private boolean isAccessTokenClaimsSeparationEnabledForApp(String consumerKey, String tenantDomain)
throws IdentityOAuth2Exception {

ServiceProvider serviceProvider = OAuth2Util.getServiceProvider(consumerKey, tenantDomain);
return OAuth2Util.isAppVersionAllowed(serviceProvider.getApplicationVersion(),
ApplicationConstants.ApplicationVersion.APP_VERSION_V2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,6 @@ public static OAuthConsumerAppDTO buildConsumerAppDTO(OAuthAppDO appDO) {
dto.setSubjectTokenEnabled(appDO.isSubjectTokenEnabled());
dto.setSubjectTokenExpiryTime(appDO.getSubjectTokenExpiryTime());
dto.setAccessTokenClaims(appDO.getAccessTokenClaims());
dto.setAccessTokenClaimsSeparationEnabled(appDO.isAccessTokenClaimsSeparationEnabled());
return dto;
}

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_ENCRYPTION_ALGORITHM;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_ENCRYPTION_METHOD;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_SIGNATURE_ALGORITHM;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.IS_ACCESS_TOKEN_CLAIMS_SEPARATION_ENABLED;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.IS_CERTIFICATE_BOUND_ACCESS_TOKEN;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.IS_FAPI_CONFORMANT_APP;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.IS_PUSH_AUTH;
Expand Down Expand Up @@ -1044,13 +1043,6 @@ private void addOrUpdateOIDCSpProperty(OAuthAppDO oauthAppDO,
SUBJECT_TOKEN_EXPIRY_TIME, String.valueOf(oauthAppDO.getSubjectTokenExpiryTime()),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);

if (isAccessTokenClaimsSeparationFeatureEnabled()) {
addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties,
IS_ACCESS_TOKEN_CLAIMS_SEPARATION_ENABLED,
String.valueOf(oauthAppDO.isAccessTokenClaimsSeparationEnabled()),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);
}

addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties,
HYBRID_FLOW_ENABLED, String.valueOf(oauthAppDO.isHybridFlowEnabled()),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);
Expand Down Expand Up @@ -1779,12 +1771,6 @@ private void addServiceProviderOIDCProperties(Connection connection,
addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
SUBJECT_TOKEN_EXPIRY_TIME, String.valueOf(consumerAppDO.getSubjectTokenExpiryTime()));

if (isAccessTokenClaimsSeparationFeatureEnabled()) {
addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
IS_ACCESS_TOKEN_CLAIMS_SEPARATION_ENABLED,
String.valueOf(consumerAppDO.isAccessTokenClaimsSeparationEnabled()));
}

addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
HYBRID_FLOW_ENABLED,
String.valueOf(consumerAppDO.isHybridFlowEnabled()));
Expand Down Expand Up @@ -1965,13 +1951,6 @@ private void setSpOIDCProperties(Map<String, List<String>> spOIDCProperties, OAu
oauthApp.setSubjectTokenExpiryTime(Integer.parseInt(subjectTokenExpiryTime));
}

String isAccessTokenClaimsSeparationEnabled = getFirstPropertyValue(spOIDCProperties,
IS_ACCESS_TOKEN_CLAIMS_SEPARATION_ENABLED);
if (isAccessTokenClaimsSeparationEnabled != null) {
oauthApp.setAccessTokenClaimsSeparationEnabled(
Boolean.parseBoolean(isAccessTokenClaimsSeparationEnabled));
}

boolean hybridFlowEnabled = Boolean.parseBoolean(getFirstPropertyValue(spOIDCProperties,
HYBRID_FLOW_ENABLED));
oauthApp.setHybridFlowEnabled(hybridFlowEnabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ public class OAuthAppDO extends InboundConfigurationProtocol implements Serializ
private boolean subjectTokenEnabled;
private int subjectTokenExpiryTime;
private String[] accessTokenClaims;
private boolean accessTokenClaimsSeparationEnabled;

public AuthenticatedUser getAppOwner() {

Expand Down Expand Up @@ -535,14 +534,4 @@ public void setAccessTokenClaims(String[] accessTokenClaims) {

this.accessTokenClaims = accessTokenClaims;
}

public boolean isAccessTokenClaimsSeparationEnabled() {

return accessTokenClaimsSeparationEnabled;
}

public void setAccessTokenClaimsSeparationEnabled(boolean accessTokenClaimsSeparationEnabled) {

this.accessTokenClaimsSeparationEnabled = accessTokenClaimsSeparationEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public class OAuthConsumerAppDTO implements InboundProtocolConfigurationDTO {
private boolean subjectTokenEnabled;
private int subjectTokenExpiryTime;
private String[] accessTokenClaims;
private boolean accessTokenClaimsSeparationEnabled;

// CORS origin related properties. This will be used by the CORS management service
@IgnoreNullElement
Expand Down Expand Up @@ -540,15 +539,5 @@ public void setAccessTokenClaims(String[] accessTokenClaims) {

this.accessTokenClaims = accessTokenClaims;
}

public boolean isAccessTokenClaimsSeparationEnabled() {

return accessTokenClaimsSeparationEnabled;
}

public void setAccessTokenClaimsSeparationEnabled(boolean accessTokenClaimsSeparationEnabled) {

this.accessTokenClaimsSeparationEnabled = accessTokenClaimsSeparationEnabled;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.common.model.ServiceProviderProperty;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
import org.wso2.carbon.identity.application.mgt.ApplicationConstants;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants;
Expand Down Expand Up @@ -459,8 +460,9 @@ private OAuth2AccessTokenRespDTO validateGrantAndIssueToken(OAuth2AccessTokenReq
ServiceProvider serviceProvider = getServiceProvider(tokReqMsgCtx.getOauth2AccessTokenReqDTO());
boolean useClientIdAsSubClaimForAppTokensEnabledServerConfig = OAuthServerConfiguration.getInstance()
.isUseClientIdAsSubClaimForAppTokensEnabled();
boolean useClientIdAsSubClaimForAppTokensEnabled = OAuth2Util
.isAllowedToStopUsingAppOwnerForTokenIdentification(serviceProvider.getApplicationVersion());
boolean useClientIdAsSubClaimForAppTokensEnabled =
OAuth2Util.isAppVersionAllowed(serviceProvider.getApplicationVersion(),
ApplicationConstants.ApplicationVersion.APP_VERSION_V1);
if (authorizedUser.getAuthenticatedSubjectIdentifier() == null) {
if ((!isOfTypeApplicationUser && (useClientIdAsSubClaimForAppTokensEnabled
|| useClientIdAsSubClaimForAppTokensEnabledServerConfig))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,6 @@ public class OAuth2Util {
ApplicationConstants.MY_ACCOUNT_APPLICATION_CLIENT_ID,
ApplicationConstants.CONSOLE_APPLICATION_CLIENT_ID);

public static final String ALLOWED_VERSION_TO_STOP_USING_APP_OWNER_FOR_TOKEN_IDENTIFICATION = "v1.0.0";

private OAuth2Util() {

}
Expand Down Expand Up @@ -5629,16 +5627,15 @@ public static boolean isPairwiseSubEnabledForAccessTokens() {
}

/**
* Compare the app version with allowed minimum version.
* Compare the app version with allowed minimum app version.
*
* @param appVersion App version.
* @return True if the app version is greater than or equal to the allowed minimum version.
* @return True if the app version is greater than or equal to the allowed minimum app version.
*/
public static boolean isAllowedToStopUsingAppOwnerForTokenIdentification(String appVersion) {
public static boolean isAppVersionAllowed(String appVersion, String allowedAppVersion) {

String[] appVersionDigits = appVersion.substring(1).split("\\.");
String[] allowedVersionDigits = ALLOWED_VERSION_TO_STOP_USING_APP_OWNER_FOR_TOKEN_IDENTIFICATION.substring(1)
.split("\\.");
String[] allowedVersionDigits = allowedAppVersion.substring(1).split("\\.");

for (int i = 0; i < appVersionDigits.length; i++) {
if (appVersionDigits[i].equals(allowedVersionDigits[i])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.mgt.ApplicationConstants;
import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants;
import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
Expand Down Expand Up @@ -578,7 +579,8 @@ private OAuth2IntrospectionResponseDTO validateAccessToken(OAuth2TokenValidation
String consumerKey = accessTokenDO.getConsumerKey();
ServiceProvider serviceProvider = OAuth2Util.getServiceProvider(consumerKey, appResidentTenantDomain);
boolean removeUsernameFromAppTokenEnabled = OAuth2Util
.isAllowedToStopUsingAppOwnerForTokenIdentification(serviceProvider.getApplicationVersion());
.isAppVersionAllowed(serviceProvider.getApplicationVersion(),
ApplicationConstants.ApplicationVersion.APP_VERSION_V1);
boolean isAppTokenType = StringUtils.equals(OAuthConstants.UserType.APPLICATION, tokenType);

// should be in seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@

package org.wso2.carbon.identity.openidconnect.util;

import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.mgt.ApplicationConstants;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration;
import org.wso2.carbon.identity.oauth.dao.OAuthAppDO;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.openidconnect.CustomClaimsCallbackHandler;

import static org.wso2.carbon.identity.oauth.common.OAuthConstants.ENABLE_CLAIMS_SEPARATION_FOR_ACCESS_TOKEN;
Expand All @@ -30,11 +35,21 @@
*/
public class ClaimHandlerUtil {

public static CustomClaimsCallbackHandler getClaimsCallbackHandler(OAuthAppDO oAuthAppDO) {
/**
* Get the claims callback handler based on the application configuration.
*
* @param oAuthAppDO OAuth application data object.
* @return CustomClaimsCallbackHandler.
*/
public static CustomClaimsCallbackHandler getClaimsCallbackHandler(OAuthAppDO oAuthAppDO)
throws IdentityOAuth2Exception {

// If JWT access token OIDC claims separation is enabled and the application is configured to separate OIDC
// claims, use the JWTAccessTokenOIDCClaimsHandler to handle custom claims.
if (isAccessTokenClaimsSeparationFeatureEnabled() && oAuthAppDO.isAccessTokenClaimsSeparationEnabled()) {
int appTenantId = IdentityTenantUtil.getLoginTenantId();
String tenantDomain = IdentityTenantUtil.getTenantDomain(appTenantId);
if (isAccessTokenClaimsSeparationFeatureEnabled() &&
isAccessTokenClaimsSeparationEnabledForApp(oAuthAppDO, tenantDomain)) {
return OAuthServerConfiguration.getInstance().getJWTAccessTokenOIDCClaimsHandler();
}
return OAuthServerConfiguration.getInstance().getOpenIDConnectCustomClaimsCallbackHandler();
Expand All @@ -44,4 +59,13 @@ private static boolean isAccessTokenClaimsSeparationFeatureEnabled() {

return Boolean.parseBoolean(IdentityUtil.getProperty(ENABLE_CLAIMS_SEPARATION_FOR_ACCESS_TOKEN));
}

private static boolean isAccessTokenClaimsSeparationEnabledForApp(OAuthAppDO oAuthAppDO, String tenantDomain)
throws IdentityOAuth2Exception {

ServiceProvider serviceProvider = OAuth2Util.getServiceProvider(oAuthAppDO.getOauthConsumerKey(), tenantDomain);
String appVersion = serviceProvider.getApplicationVersion();

return OAuth2Util.isAppVersionAllowed(appVersion, ApplicationConstants.ApplicationVersion.APP_VERSION_V2);
}
}
Loading

0 comments on commit 16fe5aa

Please sign in to comment.