diff --git a/components/org.wso2.carbon.identity.api.server.dcr/pom.xml b/components/org.wso2.carbon.identity.api.server.dcr/pom.xml index e606a8e69c8..01e06755ffb 100644 --- a/components/org.wso2.carbon.identity.api.server.dcr/pom.xml +++ b/components/org.wso2.carbon.identity.api.server.dcr/pom.xml @@ -5,12 +5,12 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../../pom.xml org.wso2.carbon.identity.api.server.dcr - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT WSO2 Carbon - User DCR Rest API WSO2 Carbon - User DCR Rest API diff --git a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/ApplicationDTO.java b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/ApplicationDTO.java index a66d1be7792..f7d980a4e11 100644 --- a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/ApplicationDTO.java +++ b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/ApplicationDTO.java @@ -65,6 +65,7 @@ public class ApplicationDTO { private String jwksUri = null; private String tokenEndpointAuthMethod = null; + private Boolean tokenEndpointAllowReusePvtKeyJwt = null; private String tokenEndpointAuthSigningAlg = null; private String sectorIdentifierUri = null; private String idTokenSignedResponseAlg = null; @@ -292,6 +293,17 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + @ApiModelProperty(value = "") + @JsonProperty("token_endpoint_allow_reuse_pvt_key_jwt") + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } @ApiModelProperty(value = "") @JsonProperty("token_endpoint_auth_signing_alg") diff --git a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/RegistrationRequestDTO.java b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/RegistrationRequestDTO.java index 92e34409e4d..e42227c3b0e 100644 --- a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/RegistrationRequestDTO.java +++ b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/RegistrationRequestDTO.java @@ -49,6 +49,7 @@ public class RegistrationRequestDTO { private String extTokenType = null; private String tokenEndpointAuthMethod = null; private String tokenEndpointAuthSigningAlg = null; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String sectorIdentifierUri = null; private String idTokenSignedResponseAlg = null; private String idTokenEncryptedResponseAlg = null; @@ -332,6 +333,18 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + @ApiModelProperty(value = "") + @JsonProperty("token_endpoint_allow_reuse_pvt_key_jwt") + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + @ApiModelProperty(value = "") @JsonProperty("token_endpoint_auth_signing_alg") diff --git a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/UpdateRequestDTO.java b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/UpdateRequestDTO.java index 81471cc2377..085eb32d260 100644 --- a/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/UpdateRequestDTO.java +++ b/components/org.wso2.carbon.identity.api.server.dcr/src/gen/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/dto/UpdateRequestDTO.java @@ -36,6 +36,7 @@ public class UpdateRequestDTO { private boolean extPublicClient; private String extTokenType = null; private String tokenEndpointAuthMethod = null; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String tokenEndpointAuthSigningAlg = null; private String sectorIdentifierUri = null; private String idTokenSignedResponseAlg = null; @@ -241,6 +242,18 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + @ApiModelProperty(value = "") + @JsonProperty("token_endpoint_allow_reuse_pvt_key_jwt") + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + @ApiModelProperty(value = "") @JsonProperty("token_endpoint_auth_signing_alg") public String getTokenEndpointAuthSigningAlg() { diff --git a/components/org.wso2.carbon.identity.api.server.dcr/src/main/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/util/DCRMUtils.java b/components/org.wso2.carbon.identity.api.server.dcr/src/main/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/util/DCRMUtils.java index 8e46d6c25f8..23e87ffa559 100644 --- a/components/org.wso2.carbon.identity.api.server.dcr/src/main/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/util/DCRMUtils.java +++ b/components/org.wso2.carbon.identity.api.server.dcr/src/main/java/org/wso2/carbon/identity/oauth2/dcr/endpoint/util/DCRMUtils.java @@ -81,6 +81,8 @@ public static ApplicationRegistrationRequest getApplicationRegistrationRequest( appRegistrationRequest.setExtTokenType(registrationRequestDTO.getExtTokenType()); appRegistrationRequest.setJwksURI(registrationRequestDTO.getJwksUri()); appRegistrationRequest.setTokenEndpointAuthMethod(registrationRequestDTO.getTokenEndpointAuthMethod()); + appRegistrationRequest.setTokenEndpointAllowReusePvtKeyJwt(registrationRequestDTO + .isTokenEndpointAllowReusePvtKeyJwt()); appRegistrationRequest.setTokenEndpointAuthSignatureAlgorithm (registrationRequestDTO.getTokenEndpointAuthSigningAlg()); appRegistrationRequest.setSectorIdentifierURI(registrationRequestDTO.getSectorIdentifierUri()); @@ -125,6 +127,8 @@ public static ApplicationUpdateRequest getApplicationUpdateRequest(UpdateRequest applicationUpdateRequest.setExtTokenType(updateRequestDTO.getExtTokenType()); applicationUpdateRequest.setJwksURI(updateRequestDTO.getJwksUri()); applicationUpdateRequest.setTokenEndpointAuthMethod(updateRequestDTO.getTokenEndpointAuthMethod()); + applicationUpdateRequest.setTokenEndpointAllowReusePvtKeyJwt( + updateRequestDTO.isTokenEndpointAllowReusePvtKeyJwt()); applicationUpdateRequest.setTokenEndpointAuthSignatureAlgorithm (updateRequestDTO.getTokenEndpointAuthSigningAlg()); applicationUpdateRequest.setSectorIdentifierURI(updateRequestDTO.getSectorIdentifierUri()); @@ -235,6 +239,7 @@ public static ApplicationDTO getApplicationDTOFromApplication(Application applic applicationDTO.setExtTokenType(application.getExtTokenType()); applicationDTO.setJwksUri(application.getJwksURI()); applicationDTO.setTokenEndpointAuthMethod(application.getTokenEndpointAuthMethod()); + applicationDTO.setTokenEndpointAllowReusePvtKeyJwt(application.isTokenEndpointAllowReusePvtKeyJwt()); applicationDTO.setTokenEndpointAuthSigningAlg(application.getTokenEndpointAuthSignatureAlgorithm()); applicationDTO.setSectorIdentifierUri(application.getSectorIdentifierURI()); applicationDTO.setIdTokenSignedResponseAlg(application.getIdTokenSignatureAlgorithm()); diff --git a/components/org.wso2.carbon.identity.api.server.oauth.scope/pom.xml b/components/org.wso2.carbon.identity.api.server.oauth.scope/pom.xml index 87610eaab66..4f92e80798c 100644 --- a/components/org.wso2.carbon.identity.api.server.oauth.scope/pom.xml +++ b/components/org.wso2.carbon.identity.api.server.oauth.scope/pom.xml @@ -5,12 +5,12 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../.. org.wso2.carbon.identity.api.server.oauth.scope - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT WSO2 Carbon - Identity OAuth 2.0 Scope Rest APIs Rest APIs for OAuth 2.0 Scope Handling diff --git a/components/org.wso2.carbon.identity.client.attestation.filter/pom.xml b/components/org.wso2.carbon.identity.client.attestation.filter/pom.xml index 932607d1239..14e0069e407 100644 --- a/components/org.wso2.carbon.identity.client.attestation.filter/pom.xml +++ b/components/org.wso2.carbon.identity.client.attestation.filter/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../../pom.xml diff --git a/components/org.wso2.carbon.identity.discovery/pom.xml b/components/org.wso2.carbon.identity.discovery/pom.xml index 7efbf85bd86..51331e7b91a 100644 --- a/components/org.wso2.carbon.identity.discovery/pom.xml +++ b/components/org.wso2.carbon.identity.discovery/pom.xml @@ -21,7 +21,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.ciba/pom.xml b/components/org.wso2.carbon.identity.oauth.ciba/pom.xml index 3ac3e3dee52..6fd3b1acc67 100644 --- a/components/org.wso2.carbon.identity.oauth.ciba/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.ciba/pom.xml @@ -20,7 +20,7 @@ identity-inbound-auth-oauth org.wso2.carbon.identity.inbound.auth.oauth2 - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../../pom.xml diff --git a/components/org.wso2.carbon.identity.oauth.client.authn.filter/pom.xml b/components/org.wso2.carbon.identity.oauth.client.authn.filter/pom.xml index aa83a622f61..4e90b7cdd41 100644 --- a/components/org.wso2.carbon.identity.oauth.client.authn.filter/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.client.authn.filter/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.common/pom.xml b/components/org.wso2.carbon.identity.oauth.common/pom.xml index adcab23ad94..6b9308c67d2 100644 --- a/components/org.wso2.carbon.identity.oauth.common/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.common/pom.xml @@ -23,7 +23,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java b/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java index 78437a236b2..21b025ec806 100644 --- a/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java +++ b/components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java @@ -622,6 +622,7 @@ public static class OIDCConfigProperties { public static final String TOKEN_BINDING_VALIDATION = "tokenBindingValidation"; public static final String TOKEN_BINDING_TYPE_NONE = "None"; public static final String TOKEN_AUTH_METHOD = "tokenEndpointAuthMethod"; + public static final String TOKEN_EP_ALLOW_REUSE_PVT_KEY_JWT = "tokenEndpointAllowReusePvtKeyJwt"; public static final String TOKEN_AUTH_SIGNATURE_ALGORITHM = "tokenEndpointAuthSigningAlg"; public static final String SECTOR_IDENTIFIER_URI = "sectorIdentifierUri"; public static final String ID_TOKEN_SIGNATURE_ALGORITHM = "idTokenSignedResponseAlg"; @@ -636,7 +637,14 @@ 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 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. + public static final String JWT_CONFIGURATION_RESOURCE_TYPE_NAME = "PK_JWT_CONFIGURATION"; + // Name of the {@code JWTClientAuthenticatorConfig} resource (per tenant) in the Configuration Management API. + public static final String JWT_CONFIGURATION_RESOURCE_NAME = "TENANT_PK_JWT_CONFIGURATION"; + public static final String PVT_KEY_JWT_CLIENT_AUTHENTICATOR_CLASS_NAME = "PrivateKeyJWTClientAuthenticator"; + public static final String ENABLE_TOKEN_REUSE = "EnableTokenReuse"; private OIDCConfigProperties() { } @@ -710,6 +718,7 @@ public static class ActionIDs { public static final String SCOPE_VALIDATION = "validate-scope"; public static final String ISSUE_ACCESS_TOKEN = "issue-access-token"; + public static final String ISSUE_SUBJECT_TOKEN = "issue-subject-token"; public static final String ISSUE_ID_TOKEN = "issue-id-token"; public static final String VALIDATE_AUTHORIZATION_CODE = "validate-authz-code"; public static final String ISSUE_AUTHZ_CODE = "issue-authz-code"; @@ -771,6 +780,7 @@ public static class InputKeys { public static final String CALLBACK_URI = "callback URI"; public static final String PROMPT = "prompt"; public static final String APP_STATE = "app state"; + public static final String IMPERSONATOR = "impersonator"; public static final String REQUESTED_AUTHORIZATION_DETAILS = "requested authorization details"; } diff --git a/components/org.wso2.carbon.identity.oauth.dcr.endpoint/pom.xml b/components/org.wso2.carbon.identity.oauth.dcr.endpoint/pom.xml index 52232a9cede..f34b5e35c82 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr.endpoint/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.dcr.endpoint/pom.xml @@ -6,7 +6,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.dcr/pom.xml b/components/org.wso2.carbon.identity.oauth.dcr/pom.xml index 8db5f9b176c..a18677673ef 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.dcr/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/Application.java b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/Application.java index bb555c1f16c..7f0d3907f23 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/Application.java +++ b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/Application.java @@ -46,6 +46,7 @@ public class Application implements Serializable { private String extTokenType = null; private String jwksURI = null; private String tokenEndpointAuthMethod = null; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String tokenEndpointAuthSignatureAlgorithm = null; private String sectorIdentifierURI = null; private String idTokenSignatureAlgorithm = null; @@ -253,6 +254,16 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + public String getTokenEndpointAuthSignatureAlgorithm() { return tokenEndpointAuthSignatureAlgorithm; diff --git a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationRegistrationRequest.java b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationRegistrationRequest.java index af1666e6514..068fa186379 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationRegistrationRequest.java +++ b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationRegistrationRequest.java @@ -52,6 +52,7 @@ public class ApplicationRegistrationRequest implements Serializable { private String jwksURI; private String softwareStatement; private String tokenEndpointAuthMethod; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String tokenEndpointAuthSignatureAlgorithm; private String sectorIdentifierURI; private String idTokenSignatureAlgorithm; @@ -380,6 +381,16 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + public String getTokenEndpointAuthSignatureAlgorithm() { return tokenEndpointAuthSignatureAlgorithm; diff --git a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationUpdateRequest.java b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationUpdateRequest.java index b98772d5dd9..443821cd55c 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationUpdateRequest.java +++ b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/bean/ApplicationUpdateRequest.java @@ -48,6 +48,7 @@ public class ApplicationUpdateRequest implements Serializable { private String jwksURI = null; private String softwareStatement; private String tokenEndpointAuthMethod; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String tokenEndpointAuthSignatureAlgorithm; private String sectorIdentifierURI; private String idTokenSignatureAlgorithm; @@ -305,6 +306,16 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + public String getTokenEndpointAuthSignatureAlgorithm() { return tokenEndpointAuthSignatureAlgorithm; diff --git a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/service/DCRMService.java b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/service/DCRMService.java index 6aa00ca66aa..994bd068fa6 100644 --- a/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/service/DCRMService.java +++ b/components/org.wso2.carbon.identity.oauth.dcr/src/main/java/org/wso2/carbon/identity/oauth/dcr/service/DCRMService.java @@ -351,6 +351,7 @@ public Application updateApplication(ApplicationUpdateRequest updateRequest, Str if (updateRequest.getTokenEndpointAuthMethod() != null) { appDTO.setTokenEndpointAuthMethod(updateRequest.getTokenEndpointAuthMethod()); } + appDTO.setTokenEndpointAllowReusePvtKeyJwt(updateRequest.isTokenEndpointAllowReusePvtKeyJwt()); if (updateRequest.getTokenEndpointAuthSignatureAlgorithm() != null) { appDTO.setTokenEndpointAuthSignatureAlgorithm (updateRequest.getTokenEndpointAuthSignatureAlgorithm()); @@ -670,6 +671,7 @@ private Application buildResponse(OAuthConsumerAppDTO createdApp, String tenantD application.setExtTokenType(createdApp.getTokenType()); application.setJwksURI(createdApp.getJwksURI()); application.setTokenEndpointAuthMethod(createdApp.getTokenEndpointAuthMethod()); + application.setTokenEndpointAllowReusePvtKeyJwt(createdApp.isTokenEndpointAllowReusePvtKeyJwt()); application.setTokenEndpointAuthSignatureAlgorithm(createdApp.getTokenEndpointAuthSignatureAlgorithm()); application.setSectorIdentifierURI(createdApp.getSectorIdentifierURI()); application.setIdTokenSignatureAlgorithm(createdApp.getIdTokenSignatureAlgorithm()); @@ -764,6 +766,7 @@ private OAuthConsumerAppDTO createOAuthApp(ApplicationRegistrationRequest regist if (registrationRequest.getTokenEndpointAuthMethod() != null) { oAuthConsumerApp.setTokenEndpointAuthMethod(registrationRequest.getTokenEndpointAuthMethod()); } + oAuthConsumerApp.setTokenEndpointAllowReusePvtKeyJwt(registrationRequest.isTokenEndpointAllowReusePvtKeyJwt()); if (registrationRequest.getTokenEndpointAuthSignatureAlgorithm() != null) { oAuthConsumerApp.setTokenEndpointAuthSignatureAlgorithm (registrationRequest.getTokenEndpointAuthSignatureAlgorithm()); diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/pom.xml b/components/org.wso2.carbon.identity.oauth.endpoint/pom.xml index 564cacdef7b..62caca312d5 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.endpoint/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java index b521a4c7990..a7176750d10 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpoint.java @@ -36,6 +36,7 @@ import org.wso2.carbon.identity.oauth.client.authn.filter.OAuthClientAuthenticatorProxy; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.endpoint.OAuthRequestWrapper; import org.wso2.carbon.identity.oauth.endpoint.exception.InvalidApplicationClientException; import org.wso2.carbon.identity.oauth.endpoint.exception.InvalidRequestParentException; @@ -53,6 +54,7 @@ import org.wso2.carbon.user.core.util.UserCoreUtil; import org.wso2.carbon.utils.DiagnosticLog; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -159,6 +161,7 @@ protected Response issueAccessToken(HttpServletRequest request, HttpServletRespo startSuperTenantFlow(); } validateRepeatedParams(request, paramMap); + validateSensitiveDataInQueryParams(request); HttpServletRequestWrapper httpRequest = new OAuthRequestWrapper(request, paramMap); CarbonOAuthTokenRequest oauthRequest = buildCarbonOAuthTokenRequest(httpRequest); OAuthClientAuthnContext oauthClientAuthnContext = oauthRequest.getoAuthClientAuthnContext(); @@ -231,6 +234,20 @@ private void validateRepeatedParams(HttpServletRequest request, Map param.split("=")[0]) + .anyMatch(OAuthServerConfiguration.getInstance().getRestrictedQueryParameters()::contains); + if (containsSensitiveData) { + throw new TokenEndpointBadRequestException("Invalid request with sensitive data in the URL."); + } + } + } + private void validateOAuthApplication(OAuthClientAuthnContext oAuthClientAuthnContext) throws InvalidApplicationClientException { diff --git a/components/org.wso2.carbon.identity.oauth.endpoint/src/test/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpointTest.java b/components/org.wso2.carbon.identity.oauth.endpoint/src/test/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpointTest.java index c3478d22d60..e2833a9ce9d 100644 --- a/components/org.wso2.carbon.identity.oauth.endpoint/src/test/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpointTest.java +++ b/components/org.wso2.carbon.identity.oauth.endpoint/src/test/java/org/wso2/carbon/identity/oauth/endpoint/token/OAuth2TokenEndpointTest.java @@ -1,7 +1,7 @@ /* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). * - * WSO2 Inc. licenses this file to you under the Apache License, + * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at @@ -15,6 +15,7 @@ * specific language governing permissions and limitations * under the License. */ + package org.wso2.carbon.identity.oauth.endpoint.token; import org.apache.axiom.util.base64.Base64Utils; @@ -366,6 +367,8 @@ public Object[][] testTokenErrorResponseDataProvider() { OAuth2ErrorCodes.INVALID_CLIENT}, {OAuth2ErrorCodes.SERVER_ERROR, null, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, OAuth2ErrorCodes.SERVER_ERROR}, + {OAuth2ErrorCodes.ACCESS_DENIED, null, HttpServletResponse.SC_BAD_REQUEST, + OAuth2ErrorCodes.ACCESS_DENIED}, {SQL_ERROR, null, HttpServletResponse.SC_BAD_GATEWAY, OAuth2ErrorCodes.SERVER_ERROR}, {TOKEN_ERROR, null, HttpServletResponse.SC_BAD_REQUEST, TOKEN_ERROR}, {TOKEN_ERROR, headers1, HttpServletResponse.SC_BAD_REQUEST, TOKEN_ERROR}, diff --git a/components/org.wso2.carbon.identity.oauth.extension/pom.xml b/components/org.wso2.carbon.identity.oauth.extension/pom.xml index c724c16dc3b..9a9b8847542 100644 --- a/components/org.wso2.carbon.identity.oauth.extension/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.extension/pom.xml @@ -19,7 +19,7 @@ identity-inbound-auth-oauth org.wso2.carbon.identity.inbound.auth.oauth2 - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.par/pom.xml b/components/org.wso2.carbon.identity.oauth.par/pom.xml index e4faa8da7b4..1cb452762f9 100644 --- a/components/org.wso2.carbon.identity.oauth.par/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.par/pom.xml @@ -23,7 +23,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.scope.endpoint/pom.xml b/components/org.wso2.carbon.identity.oauth.scope.endpoint/pom.xml index 87e3ed30b93..40762e7c3dc 100644 --- a/components/org.wso2.carbon.identity.oauth.scope.endpoint/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.scope.endpoint/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.stub/pom.xml b/components/org.wso2.carbon.identity.oauth.stub/pom.xml index e63467eaba4..bf3ef314f2a 100644 --- a/components/org.wso2.carbon.identity.oauth.stub/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.stub/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth.stub/src/main/resources/OAuthAdminService.wsdl b/components/org.wso2.carbon.identity.oauth.stub/src/main/resources/OAuthAdminService.wsdl old mode 100644 new mode 100755 index 8b9539eddcd..a1b5a188711 --- a/components/org.wso2.carbon.identity.oauth.stub/src/main/resources/OAuthAdminService.wsdl +++ b/components/org.wso2.carbon.identity.oauth.stub/src/main/resources/OAuthAdminService.wsdl @@ -432,6 +432,7 @@ + diff --git a/components/org.wso2.carbon.identity.oauth.ui/pom.xml b/components/org.wso2.carbon.identity.oauth.ui/pom.xml index 29f20f0fb6f..25d49f86afd 100644 --- a/components/org.wso2.carbon.identity.oauth.ui/pom.xml +++ b/components/org.wso2.carbon.identity.oauth.ui/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oauth/pom.xml b/components/org.wso2.carbon.identity.oauth/pom.xml index 99450b2ae1c..e21e52cae67 100644 --- a/components/org.wso2.carbon.identity.oauth/pom.xml +++ b/components/org.wso2.carbon.identity.oauth/pom.xml @@ -23,7 +23,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 @@ -107,6 +107,10 @@ org.wso2.orbit.org.opensaml opensaml + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.action.execution + org.wso2.carbon.identity.framework org.wso2.carbon.identity.event diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImpl.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImpl.java old mode 100644 new mode 100755 index 068ac62c57c..3129f554594 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImpl.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImpl.java @@ -107,6 +107,7 @@ import static org.wso2.carbon.identity.oauth.OAuthUtil.handleErrorWithExceptionType; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OauthAppStates.APP_STATE_ACTIVE; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OauthAppStates.APP_STATE_DELETED; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.PRIVATE_KEY_JWT; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenBindings.NONE; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.buildScopeString; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.getTenantId; @@ -429,6 +430,13 @@ OAuthConsumerAppDTO registerAndRetrieveOAuthApplicationData(OAuthConsumerAppDTO } app.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); } + Boolean tokenEndpointAllowReusePvtKeyJwt = application.isTokenEndpointAllowReusePvtKeyJwt(); + if (isInvalidTokenEPReusePvtKeyJwtRequest(tokenEndpointAuthMethod, + tokenEndpointAllowReusePvtKeyJwt)) { + throw handleClientError(INVALID_REQUEST, "Requested client authentication method " + + "incompatible with the Private Key JWT Reuse config value."); + } + app.setTokenEndpointAllowReusePvtKeyJwt(tokenEndpointAllowReusePvtKeyJwt); String tokenEndpointAuthSigningAlgorithm = application.getTokenEndpointAuthSignatureAlgorithm(); if (StringUtils.isNotEmpty(tokenEndpointAuthSigningAlgorithm)) { if (isFAPIConformanceEnabled) { @@ -855,6 +863,13 @@ void updateConsumerApplication(OAuthConsumerAppDTO consumerAppDTO, boolean enabl } oAuthAppDO.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); + Boolean tokenEndpointAllowReusePvtKeyJwt = consumerAppDTO.isTokenEndpointAllowReusePvtKeyJwt(); + if (isInvalidTokenEPReusePvtKeyJwtRequest(tokenEndpointAuthMethod, tokenEndpointAllowReusePvtKeyJwt)) { + throw handleClientError(INVALID_REQUEST, "Requested client authentication method " + + "incompatible with the Private Key JWT Reuse config value."); + } + oAuthAppDO.setTokenEndpointAllowReusePvtKeyJwt(tokenEndpointAllowReusePvtKeyJwt); + String tokenEndpointAuthSignatureAlgorithm = consumerAppDTO.getTokenEndpointAuthSignatureAlgorithm(); if (StringUtils.isNotEmpty(tokenEndpointAuthSignatureAlgorithm)) { if (isFAPIConformanceEnabled) { @@ -2492,6 +2507,24 @@ private void handleInternalTokenRevocation(String consumerKey, Properties proper } } + /** + * Return whether the request of updating the tokenEndpointAllowReusePvtKeyJwt is valid. + * + * @param tokenEndpointAuthMethod token endpoint client authentication method. + * @param tokenEndpointAllowReusePvtKeyJwt During client authentication whether to reuse private key JWT. + * @return True if tokenEndpointAuthMethod and tokenEndpointAllowReusePvtKeyJwt is NOT in the correct format. + */ + private boolean isInvalidTokenEPReusePvtKeyJwtRequest(String tokenEndpointAuthMethod, + Boolean tokenEndpointAllowReusePvtKeyJwt) { + + if (StringUtils.isNotBlank(tokenEndpointAuthMethod)) { + if (tokenEndpointAuthMethod.equals(PRIVATE_KEY_JWT)) { + return tokenEndpointAllowReusePvtKeyJwt == null; + } + } + return tokenEndpointAllowReusePvtKeyJwt != null; + } + /** * FAPI validation to restrict the token endpoint authentication methods. * Link - https://openid.net/specs/openid-financial-api-part-2-1_0.html#authorization-server (5.2.2 - 14) diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java old mode 100644 new mode 100755 index 2fe5e743a01..ccda0801918 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java @@ -37,6 +37,11 @@ import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.base.IdentityConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; +import org.wso2.carbon.identity.configuration.mgt.core.exception.ConfigurationManagementException; +import org.wso2.carbon.identity.configuration.mgt.core.model.Attribute; +import org.wso2.carbon.identity.configuration.mgt.core.model.Resource; +import org.wso2.carbon.identity.core.handler.AbstractIdentityHandler; +import org.wso2.carbon.identity.core.model.IdentityEventListenerConfig; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.cache.OAuthCache; @@ -77,6 +82,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -89,6 +95,12 @@ import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.CURRENT_TOKEN_IDENTIFIER; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.PRESERVE_LOGGED_IN_SESSION_AT_PASSWORD_UPDATE; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.ORGANIZATION_LOGIN_HOME_REALM_IDENTIFIER; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.DEFAULT_VALUE_FOR_PREVENT_TOKEN_REUSE; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ENABLE_TOKEN_REUSE; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.JWT_CONFIGURATION_RESOURCE_NAME; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.JWT_CONFIGURATION_RESOURCE_TYPE_NAME; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.PREVENT_TOKEN_REUSE; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.PVT_KEY_JWT_CLIENT_AUTHENTICATOR_CLASS_NAME; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenBindings.NONE; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.UserType.FEDERATED_USER_DOMAIN_PREFIX; @@ -536,6 +548,7 @@ public static OAuthConsumerAppDTO buildConsumerAppDTO(OAuthAppDO appDO) { .isTokenRevocationWithIDPSessionTerminationEnabled()); dto.setTokenBindingValidationEnabled(appDO.isTokenBindingValidationEnabled()); dto.setTokenEndpointAuthMethod(appDO.getTokenEndpointAuthMethod()); + dto.setTokenEndpointAllowReusePvtKeyJwt(appDO.isTokenEndpointAllowReusePvtKeyJwt()); dto.setTokenEndpointAuthSignatureAlgorithm(appDO.getTokenEndpointAuthSignatureAlgorithm()); dto.setSectorIdentifierURI(appDO.getSectorIdentifierURI()); dto.setIdTokenSignatureAlgorithm(appDO.getIdTokenSignatureAlgorithm()); @@ -1158,6 +1171,31 @@ private static User getUserFromTenant(String username, String userId, int tenant return user; } + /** + * Get user from tenant by user id. + * + * @param userId The user id. + * @param tenantId The tenant id where user resides. + * @return User object from tenant userStoreManager. + * @throws IdentityOAuth2Exception Error when user cannot be resolved. + */ + public static User getUserFromTenant(String userId, int tenantId) + throws IdentityOAuth2Exception { + + User user = null; + try { + AbstractUserStoreManager userStoreManager = + (AbstractUserStoreManager) OAuthComponentServiceHolder.getInstance() + .getRealmService().getTenantUserRealm(tenantId).getUserStoreManager(); + if (StringUtils.isNotEmpty(userId) && userStoreManager.isExistingUserWithID(userId)) { + user = getApplicationUser(userStoreManager.getUser(userId, null)); + } + return user; + } catch (org.wso2.carbon.user.api.UserStoreException e) { + throw new IdentityOAuth2Exception("Error finding user in tenant.", e); + } + } + private static User getApplicationUser(org.wso2.carbon.user.core.common.User coreUser) { User user = new User(); @@ -1197,4 +1235,73 @@ private static void setOrganizationSSOUserDetails(AuthenticatedUser authenticate authenticatedUser.setFederatedIdPName(orgSsoIdp.getIdentityProviderName()); } } + + /** + * Get the value of the Tenant configuration of Reuse Private key JWT from the tenant configuration. + * + * @param tokenEPAllowReusePvtKeyJwtValue Value of the tokenEPAllowReusePvtKeyJwt configuration. + * @param tokenAuthMethod Token authentication method. + * @return Value of the tokenEPAllowReusePvtKeyJwt configuration. + * @throws IdentityOAuth2ServerException IdentityOAuth2ServerException exception. + */ + public static String getValueOfTokenEPAllowReusePvtKeyJwt(String tokenEPAllowReusePvtKeyJwtValue, + String tokenAuthMethod) + throws IdentityOAuth2ServerException { + + if (tokenEPAllowReusePvtKeyJwtValue == null && StringUtils.isNotBlank(tokenAuthMethod) + && OAuthConstants.PRIVATE_KEY_JWT.equals(tokenAuthMethod)) { + try { + tokenEPAllowReusePvtKeyJwtValue = readTenantConfigurationPvtKeyJWTReuse(); + } catch (ConfigurationManagementException e) { + throw new IdentityOAuth2ServerException("Unable to retrieve JWT Authenticator tenant configuration.", + e); + } + if (tokenEPAllowReusePvtKeyJwtValue == null) { + tokenEPAllowReusePvtKeyJwtValue = readServerConfigurationPvtKeyJWTReuse(); + if (tokenEPAllowReusePvtKeyJwtValue == null) { + tokenEPAllowReusePvtKeyJwtValue = String.valueOf(DEFAULT_VALUE_FOR_PREVENT_TOKEN_REUSE); + } + } + } + return tokenEPAllowReusePvtKeyJwtValue; + } + + private static String readTenantConfigurationPvtKeyJWTReuse() throws ConfigurationManagementException { + + String tokenEPAllowReusePvtKeyJwtTenantConfig = null; + Resource resource = OAuthComponentServiceHolder.getInstance().getConfigurationManager() + .getResource(JWT_CONFIGURATION_RESOURCE_TYPE_NAME, JWT_CONFIGURATION_RESOURCE_NAME); + + if (resource != null) { + tokenEPAllowReusePvtKeyJwtTenantConfig = resource.getAttributes().stream() + .filter(attribute -> ENABLE_TOKEN_REUSE.equals(attribute.getKey())) + .map(Attribute::getValue) + .findFirst() + .orElse(null); + } + return tokenEPAllowReusePvtKeyJwtTenantConfig; + } + + private static String readServerConfigurationPvtKeyJWTReuse() { + + String tokenEPAllowReusePvtKeyJwtTenantConfig = null; + IdentityEventListenerConfig identityEventListenerConfig = IdentityUtil.readEventListenerProperty( + AbstractIdentityHandler.class.getName(), PVT_KEY_JWT_CLIENT_AUTHENTICATOR_CLASS_NAME); + + if (identityEventListenerConfig != null + && Boolean.parseBoolean(identityEventListenerConfig.getEnable())) { + if (identityEventListenerConfig.getProperties() != null) { + for (Map.Entry property : identityEventListenerConfig.getProperties().entrySet()) { + String key = (String) property.getKey(); + String value = (String) property.getValue(); + if (Objects.equals(key, PREVENT_TOKEN_REUSE)) { + boolean preventTokenReuse = Boolean.parseBoolean(value); + tokenEPAllowReusePvtKeyJwtTenantConfig = String.valueOf(!preventTokenReuse); + break; + } + } + } + } + return tokenEPAllowReusePvtKeyJwtTenantConfig; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenRequestBuilder.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenRequestBuilder.java new file mode 100644 index 00000000000..f41249469ea --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenRequestBuilder.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action; + +import com.nimbusds.jwt.JWTClaimsSet; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.action.execution.ActionExecutionRequestBuilder; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionRequestBuilderException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest; +import org.wso2.carbon.identity.action.execution.model.ActionType; +import org.wso2.carbon.identity.action.execution.model.AllowedOperation; +import org.wso2.carbon.identity.action.execution.model.Event; +import org.wso2.carbon.identity.action.execution.model.Operation; +import org.wso2.carbon.identity.action.execution.model.Organization; +import org.wso2.carbon.identity.action.execution.model.Request; +import org.wso2.carbon.identity.action.execution.model.Tenant; +import org.wso2.carbon.identity.action.execution.model.User; +import org.wso2.carbon.identity.action.execution.model.UserStore; +import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.oauth.action.model.AccessToken; +import org.wso2.carbon.identity.oauth.action.model.PreIssueAccessTokenEvent; +import org.wso2.carbon.identity.oauth.action.model.TokenRequest; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; +import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; +import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; +import org.wso2.carbon.identity.oauth2.model.HttpRequestHeader; +import org.wso2.carbon.identity.oauth2.model.RequestParameter; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.openidconnect.CustomClaimsCallbackHandler; +import org.wso2.carbon.identity.openidconnect.OIDCClaimUtil; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * This class is responsible for building the action execution request for the pre issue access token action. + */ +public class PreIssueAccessTokenRequestBuilder implements ActionExecutionRequestBuilder { + + public static final String CLAIMS_PATH_PREFIX = "/accessToken/claims/"; + public static final String SCOPES_PATH_PREFIX = "/accessToken/scopes/"; + private static final Log LOG = LogFactory.getLog(PreIssueAccessTokenRequestBuilder.class); + + @Override + public ActionType getSupportedActionType() { + + return ActionType.PRE_ISSUE_ACCESS_TOKEN; + } + + @Override + public ActionExecutionRequest buildActionExecutionRequest(Map eventContext) + throws ActionExecutionRequestBuilderException { + + OAuthTokenReqMessageContext tokenMessageContext = + (OAuthTokenReqMessageContext) eventContext.get("tokenMessageContext"); + + Map additionalClaimsToAddToToken = getAdditionalClaimsToAddToToken(tokenMessageContext); + + ActionExecutionRequest.Builder actionRequestBuilder = new ActionExecutionRequest.Builder(); + actionRequestBuilder.actionType(getSupportedActionType()); + actionRequestBuilder.event(getEvent(tokenMessageContext, additionalClaimsToAddToToken)); + actionRequestBuilder.allowedOperations(getAllowedOperations(additionalClaimsToAddToToken)); + + return actionRequestBuilder.build(); + } + + private Event getEvent(OAuthTokenReqMessageContext tokenMessageContext, Map claimsToAdd) + throws ActionExecutionRequestBuilderException { + + OAuth2AccessTokenReqDTO tokenReqDTO = tokenMessageContext.getOauth2AccessTokenReqDTO(); + AuthenticatedUser authorizedUser = tokenMessageContext.getAuthorizedUser(); + + PreIssueAccessTokenEvent.Builder eventBuilder = new PreIssueAccessTokenEvent.Builder(); + eventBuilder.tenant(new Tenant(String.valueOf(IdentityTenantUtil.getTenantId(tokenReqDTO.getTenantDomain())), + tokenReqDTO.getTenantDomain())); + + boolean isAuthorizedForUser = isAccessTokenAuthorizedForUser(tokenReqDTO.getGrantType(), tokenMessageContext); + if (isAuthorizedForUser) { + setUserForEventBuilder(eventBuilder, authorizedUser, tokenReqDTO.getClientId(), tokenReqDTO.getGrantType()); + setOrganizationForEventBuilder(eventBuilder, authorizedUser, tokenReqDTO.getClientId(), + tokenReqDTO.getGrantType()); + eventBuilder.userStore(new UserStore(authorizedUser.getUserStoreDomain())); + } + + eventBuilder.accessToken(getAccessToken(tokenMessageContext, claimsToAdd)); + eventBuilder.request(getRequest(tokenReqDTO)); + + return eventBuilder.build(); + } + + private void setUserForEventBuilder(PreIssueAccessTokenEvent.Builder eventBuilder, AuthenticatedUser user, + String clientID, String grantType) { + + try { + eventBuilder.user(new User(user.getUserId())); + } catch (UserIdNotFoundException e) { + if (LOG.isDebugEnabled()) { + // todo: fall back to a different identifier like username. + // Verify based on when this exception is thrown. + LOG.debug(String.format( + "Error occurred while retrieving user id of the authorized user for application: " + clientID + + "for grantType: " + grantType), e); + } + } + } + + private void setOrganizationForEventBuilder(PreIssueAccessTokenEvent.Builder eventBuilder, AuthenticatedUser user, + String clientID, String grantType) { + + try { + String organizationId = user.getUserResidentOrganization(); + if (organizationId != null && !organizationId.isEmpty()) { + String organizationName = OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .getOrganizationNameById(user.getUserResidentOrganization()); + eventBuilder.organization(new Organization(user.getUserResidentOrganization(), + organizationName)); + } + } catch (OrganizationManagementException e) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format( + "Error occurred while retrieving organization name of the authorized user for application: " + + clientID + "for grantType: " + grantType), e); + } + } + } + + private Request getRequest(OAuth2AccessTokenReqDTO tokenRequestDTO) { + + TokenRequest.Builder tokenRequestBuilder = new TokenRequest.Builder(); + tokenRequestBuilder.clientId(tokenRequestDTO.getClientId()); + tokenRequestBuilder.grantType(tokenRequestDTO.getGrantType()); + tokenRequestBuilder.scopes(Arrays.asList(tokenRequestDTO.getScope())); + + HttpRequestHeader[] httpHeaders = tokenRequestDTO.getHttpRequestHeaders(); + if (httpHeaders != null) { + for (HttpRequestHeader header : httpHeaders) { + tokenRequestBuilder.addAdditionalHeader(header.getName(), header.getValue()); + } + } + + RequestParameter[] requestParameters = tokenRequestDTO.getRequestParameters(); + if (requestParameters != null) { + for (RequestParameter parameter : requestParameters) { + tokenRequestBuilder.addAdditionalParam(parameter.getKey(), parameter.getValue()); + } + } + + return tokenRequestBuilder.build(); + } + + private boolean isAccessTokenAuthorizedForUser(String grantType, OAuthTokenReqMessageContext tokReqMsgCtx) + throws ActionExecutionRequestBuilderException { + + AuthorizationGrantHandler grantHandler = + OAuthServerConfiguration.getInstance().getSupportedGrantTypes().get(grantType); + + try { + return grantHandler.isOfTypeApplicationUser(tokReqMsgCtx); + } catch (IdentityOAuth2Exception e) { + throw new ActionExecutionRequestBuilderException( + "Failed to determine the authorized entity of the token for grant type: " + + grantType, e); + } + } + + private AccessToken getAccessToken(OAuthTokenReqMessageContext + tokenMessageContext, Map claimsToAdd) + throws ActionExecutionRequestBuilderException { + + try { + OAuthAppDO oAuthAppDO = getAppInformation(tokenMessageContext); + String issuer = getIssuer(tokenMessageContext); + List audience = getAudience(tokenMessageContext, oAuthAppDO); + String tokenType = oAuthAppDO.getTokenType(); + + AccessToken.Builder accessTokenBuilder = new AccessToken.Builder(); + + handleStandardClaims(tokenMessageContext, tokenType, issuer, audience, accessTokenBuilder); + handleSubjectClaim(tokenMessageContext.getAuthorizedUser(), oAuthAppDO, accessTokenBuilder); + handleTokenBindingClaims(tokenMessageContext, accessTokenBuilder); + claimsToAdd.forEach(accessTokenBuilder::addClaim); + return accessTokenBuilder.build(); + } catch (IdentityOAuth2Exception | InvalidOAuthClientException e) { + throw new ActionExecutionRequestBuilderException( + "Failed to generate pre issue access token action request for application: " + + tokenMessageContext.getOauth2AccessTokenReqDTO().getClientId() + " grant type: " + + tokenMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), e); + } + } + + private OAuthAppDO getAppInformation(OAuthTokenReqMessageContext tokenMessageContext) + throws InvalidOAuthClientException, IdentityOAuth2Exception { + + return OAuth2Util.getAppInformationByClientId( + tokenMessageContext.getOauth2AccessTokenReqDTO().getClientId(), + tokenMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain()); + } + + private String getIssuer(OAuthTokenReqMessageContext tokenMessageContext) throws IdentityOAuth2Exception { + + return OAuth2Util.getIdTokenIssuer(tokenMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain()); + } + + private List getAudience(OAuthTokenReqMessageContext tokenMessageContext, OAuthAppDO oAuthAppDO) { + + if (tokenMessageContext.isPreIssueAccessTokenActionsExecuted()) { + return tokenMessageContext.getAudiences(); + } else { + return OAuth2Util.getOIDCAudience(oAuthAppDO.getOauthConsumerKey(), oAuthAppDO); + } + } + + private void handleStandardClaims(OAuthTokenReqMessageContext tokenMessageContext, String tokenType, + String issuer, List audience, AccessToken.Builder accessTokenBuilder) { + + accessTokenBuilder.tokenType(tokenType) + .addClaim(AccessToken.ClaimNames.ISS.getName(), issuer) + .addClaim(AccessToken.ClaimNames.CLIENT_ID.getName(), + tokenMessageContext.getOauth2AccessTokenReqDTO().getClientId()) + .addClaim(AccessToken.ClaimNames.AUTHORIZED_USER_TYPE.getName(), + String.valueOf(tokenMessageContext.getProperty(OAuthConstants.UserType.USER_TYPE))) + .addClaim(AccessToken.ClaimNames.EXPIRES_IN.getName(), + tokenMessageContext.getValidityPeriod() / 1000) + .addClaim(AccessToken.ClaimNames.AUD.getName(), audience) + .scopes(Arrays.asList(tokenMessageContext.getScope())); + } + + private void handleSubjectClaim(AuthenticatedUser authorizedUser, OAuthAppDO oAuthAppDO, + AccessToken.Builder accessTokenBuilder) throws IdentityOAuth2Exception { + + String sub = authorizedUser.getAuthenticatedSubjectIdentifier(); + if (OAuth2Util.isPairwiseSubEnabledForAccessTokens()) { + sub = OIDCClaimUtil.getSubjectClaim(sub, oAuthAppDO); + accessTokenBuilder.addClaim(AccessToken.ClaimNames.SUBJECT_TYPE.getName(), + OIDCClaimUtil.getSubjectType(oAuthAppDO).getValue()); + } + accessTokenBuilder.addClaim(AccessToken.ClaimNames.SUB.getName(), sub); + } + + private Map getAdditionalClaimsToAddToToken(OAuthTokenReqMessageContext tokenMessageContext) + throws ActionExecutionRequestBuilderException { + /* + Directly return custom claims if pre-issue access token actions have been executed. + This is to ensure that the custom claims added are incorporated in the refresh token flow. + Moreover, this execution expects that the claim handlers executed at the token issuance flow + does not incorporate any additional custom rules based on refresh grant. + */ + if (tokenMessageContext.isPreIssueAccessTokenActionsExecuted()) { + return tokenMessageContext.getAdditionalAccessTokenClaims(); + } + + try { + CustomClaimsCallbackHandler claimsCallBackHandler = + OAuthServerConfiguration.getInstance().getOpenIDConnectCustomClaimsCallbackHandler(); + JWTClaimsSet claimsSet = + claimsCallBackHandler.handleCustomClaims(new JWTClaimsSet.Builder(), tokenMessageContext); + return Optional.ofNullable(claimsSet).map(JWTClaimsSet::getClaims).orElseGet(HashMap::new); + } catch (IdentityOAuth2Exception e) { + throw new ActionExecutionRequestBuilderException( + "Failed to retrieve OIDC claim set for the access token for grant type: " + + tokenMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), e); + } + } + + private void handleTokenBindingClaims(OAuthTokenReqMessageContext tokenMessageContext, + AccessToken.Builder accessTokenBuilder) { + + if (tokenMessageContext.getTokenBinding() != null) { + accessTokenBuilder.addClaim(AccessToken.ClaimNames.TOKEN_BINDING_REF.getName(), + tokenMessageContext.getTokenBinding().getBindingReference()) + .addClaim(AccessToken.ClaimNames.TOKEN_BINDING_TYPE.getName(), + tokenMessageContext.getTokenBinding().getBindingType()); + } + } + + public List getAllowedOperations(Map oidcClaims) { + + List removeOrReplacePaths = getRemoveOrReplacePaths(oidcClaims); + + List replacePaths = new ArrayList<>(removeOrReplacePaths); + replacePaths.add(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.EXPIRES_IN.getName()); + + AllowedOperation addOperation = + createAllowedOperation(Operation.ADD, Arrays.asList(CLAIMS_PATH_PREFIX, SCOPES_PATH_PREFIX, + CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.AUD.getName() + "/")); + AllowedOperation removeOperation = createAllowedOperation(Operation.REMOVE, removeOrReplacePaths); + AllowedOperation replaceOperation = createAllowedOperation(Operation.REPLACE, replacePaths); + + return Arrays.asList(addOperation, removeOperation, replaceOperation); + } + + private List getRemoveOrReplacePaths(Map oidcClaims) { + + List removeOrReplacePaths = oidcClaims.entrySet().stream() + .filter(entry -> entry.getValue() instanceof String || entry.getValue() instanceof Number || + entry.getValue() instanceof Boolean || entry.getValue() instanceof List || + entry.getValue() instanceof String[]) + .map(this::generatePathForClaim) + .collect(Collectors.toList()); + + removeOrReplacePaths.add(SCOPES_PATH_PREFIX); + removeOrReplacePaths.add(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.AUD.getName() + "/"); + return removeOrReplacePaths; + } + + private String generatePathForClaim(Map.Entry entry) { + + String basePath = CLAIMS_PATH_PREFIX + entry.getKey(); + if (entry.getValue() instanceof List || entry.getValue() instanceof String[]) { + basePath += "/"; + } + return basePath; + } + + private AllowedOperation createAllowedOperation(Operation op, List paths) { + + AllowedOperation operation = new AllowedOperation(); + operation.setOp(op); + operation.setPaths(new ArrayList<>(paths)); + return operation; + } + +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenResponseProcessor.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenResponseProcessor.java new file mode 100644 index 00000000000..2ff54fb5063 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/PreIssueAccessTokenResponseProcessor.java @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.action.execution.ActionExecutionResponseProcessor; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionResponseProcessorException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; +import org.wso2.carbon.identity.action.execution.model.ActionType; +import org.wso2.carbon.identity.action.execution.model.Event; +import org.wso2.carbon.identity.action.execution.model.PerformableOperation; +import org.wso2.carbon.identity.oauth.action.model.AccessToken; +import org.wso2.carbon.identity.oauth.action.model.ClaimPathInfo; +import org.wso2.carbon.identity.oauth.action.model.OperationExecutionResult; +import org.wso2.carbon.identity.oauth.action.model.PreIssueAccessTokenEvent; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is responsible for processing the response received from the action execution + * of the pre issue access token. + */ +public class PreIssueAccessTokenResponseProcessor implements ActionExecutionResponseProcessor { + + private static final Log LOG = LogFactory.getLog(PreIssueAccessTokenResponseProcessor.class); + private static final String SCOPE_PATH_PREFIX = "/accessToken/scopes/"; + private static final String CLAIMS_PATH_PREFIX = "/accessToken/claims/"; + private static final Pattern NQCHAR_PATTERN = Pattern.compile("^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$"); + private static final Pattern STRING_OR_URI_PATTERN = + Pattern.compile("^([a-zA-Z][a-zA-Z0-9+.-]*://[^\\s/$.?#].\\S*)|(^[a-zA-Z0-9.-]+$)"); + private static final String LAST_ELEMENT_CHARACTER = "-"; + private static final char PATH_SEPARATOR = '/'; + + @Override + public ActionType getSupportedActionType() { + + return ActionType.PRE_ISSUE_ACCESS_TOKEN; + } + + @Override + public ActionExecutionStatus processSuccessResponse(Map eventContext, Event event, + ActionInvocationSuccessResponse actionInvocationSuccessResponse) + throws ActionExecutionResponseProcessorException { + + OAuthTokenReqMessageContext tokenMessageContext = + (OAuthTokenReqMessageContext) eventContext.get("tokenMessageContext"); + PreIssueAccessTokenEvent preIssueAccessTokenEvent = (PreIssueAccessTokenEvent) event; + List operationsToPerform = actionInvocationSuccessResponse.getOperations(); + + AccessToken requestAccessToken = preIssueAccessTokenEvent.getAccessToken(); + AccessToken.Builder responseAccessTokenBuilder = preIssueAccessTokenEvent.getAccessToken().copy(); + List operationExecutionResultList = new ArrayList<>(); + + if (operationsToPerform != null) { + for (PerformableOperation operation : operationsToPerform) { + switch (operation.getOp()) { + case ADD: + operationExecutionResultList.add( + handleAddOperation(operation, requestAccessToken, responseAccessTokenBuilder)); + break; + case REMOVE: + operationExecutionResultList.add( + handleRemoveOperation(operation, requestAccessToken, responseAccessTokenBuilder)); + break; + case REPLACE: + operationExecutionResultList.add( + handleReplaceOperation(operation, requestAccessToken, responseAccessTokenBuilder)); + break; + default: + break; + } + } + } + + logOperationExecutionResults(getSupportedActionType(), operationExecutionResultList); + + AccessToken responseAccessToken = responseAccessTokenBuilder.build(); + updateTokenMessageContext(tokenMessageContext, responseAccessToken); + + return new ActionExecutionStatus(ActionExecutionStatus.Status.SUCCESS, eventContext); + } + + private void logOperationExecutionResults(ActionType actionType, + List operationExecutionResultList) { + + //todo: need to add to diagnostic logs + if (LOG.isDebugEnabled()) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + try { + String executionSummary = objectMapper.writeValueAsString(operationExecutionResultList); + LOG.debug(String.format("Processed response for action type: %s. Results of operations performed: %s", + actionType, executionSummary)); + } catch (JsonProcessingException e) { + LOG.debug("Error occurred while logging operation execution results.", e); + } + } + } + + @Override + public ActionExecutionStatus processErrorResponse(Map map, Event event, + ActionInvocationErrorResponse actionInvocationErrorResponse) + throws ActionExecutionResponseProcessorException { + + //todo: need to implement to process the error so that if a processable error is received + // it is communicated to the client. + // we will look into this as we go along with other extension types validating the way to model this. + return null; + } + + private void updateTokenMessageContext(OAuthTokenReqMessageContext tokenMessageContext, + AccessToken responseAccessToken) { + + tokenMessageContext.setScope(responseAccessToken.getScopes().toArray(new String[0])); + + String expiresInClaimName = CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.EXPIRES_IN.getName(); + responseAccessToken.getClaims().stream() + .filter(claim -> expiresInClaimName.equals(claim.getName())) + .findFirst() + .map(claim -> Long.parseLong(claim.getValue().toString()) * 1000) + .ifPresent(tokenMessageContext::setValidityPeriod); + + Optional.ofNullable(responseAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName())) + .map(AccessToken.Claim::getValue) + .ifPresent(value -> { + List audienceList; + if (value instanceof List) { + audienceList = (List) value; + } else { + audienceList = Collections.emptyList(); + } + tokenMessageContext.setAudiences(audienceList); + }); + + Map customClaims = new HashMap<>(); + for (AccessToken.Claim claim : responseAccessToken.getClaims()) { + if (!AccessToken.ClaimNames.contains(claim.getName())) { + customClaims.put(claim.getName(), claim.getValue()); + } + } + tokenMessageContext.setAdditionalAccessTokenClaims(customClaims); + + tokenMessageContext.setPreIssueAccessTokenActionsExecuted(true); + } + + private OperationExecutionResult handleAddOperation(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + if (operation.getPath().startsWith(SCOPE_PATH_PREFIX)) { + return addScope(operation, responseAccessToken); + } else if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX)) { + return addClaim(operation, requestAccessToken, responseAccessToken); + } + + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Unknown path."); + } + + private OperationExecutionResult addScope(PerformableOperation operation, + AccessToken.Builder responseAccessToken) { + + List authorizedScopes = + responseAccessToken.getScopes() != null ? responseAccessToken.getScopes() : new ArrayList<>(); + + int index = validateIndex(operation.getPath(), authorizedScopes.size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String scopeToAdd = operation.getValue().toString(); + if (authorizedScopes.contains(scopeToAdd) || !validateNQChar(scopeToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Scope exists or is invalid."); + } + + authorizedScopes.add(scopeToAdd); + responseAccessToken.scopes(authorizedScopes); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, "Scope added."); + } + + private OperationExecutionResult addClaim(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + List claims = requestAccessToken.getClaims(); + + if (claims == null || claims.isEmpty()) { + // todo: not sure why this is here. If it's an add we don't need to check for empty claims rather just add. + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Claims not found."); + } + + if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.AUD.getName())) { + return addAudience(operation, requestAccessToken, responseAccessToken); + } else { + return addToOtherClaims(operation, requestAccessToken, responseAccessToken); + } + } + + private OperationExecutionResult addAudience(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + AccessToken.Claim audience = requestAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + if (audience != null && audience.getValue() != null && audience.getValue() instanceof List) { + List audienceList = (List) audience.getValue(); + + int index = validateIndex(operation.getPath(), audienceList.size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String audienceToAdd = operation.getValue().toString(); + if (!isValidStringOrURI(audienceToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Audience is invalid."); + } + + AccessToken.Claim responseAudience = + responseAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + List responseAudienceList = (List) responseAudience.getValue(); + if (responseAudienceList.contains(audienceToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Audience already exists."); + } + + responseAudienceList.add(audienceToAdd); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Audience added."); + } + + //todo: In the add path it should be possible to add audience irrespective of the fact the access token + // included a set of audiences or not. Need to recheck this. + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Audience claim not found."); + } + + private OperationExecutionResult addToOtherClaims(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + int index = validateIndex(operation.getPath(), requestAccessToken.getClaims().size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + Object claimToAdd = operation.getValue(); + ObjectMapper objectMapper = new ObjectMapper(); + try { + AccessToken.Claim claim = objectMapper.convertValue(claimToAdd, AccessToken.Claim.class); + if (requestAccessToken.getClaim(claim.getName()) != null) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "An access token claim already exists."); + } + + Object claimValue = claim.getValue(); + if (isValidPrimitiveValue(claimValue) || isValidListValue(claimValue)) { + responseAccessToken.addClaim(claim.getName(), claimValue); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, "Claim added."); + + } else { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid claim value."); + } + } catch (IllegalArgumentException e) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid claim."); + } + } + + private OperationExecutionResult handleRemoveOperation(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + if (operation.getPath().startsWith(SCOPE_PATH_PREFIX)) { + return removeScope(operation, requestAccessToken, responseAccessToken); + } else if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX)) { + return removeClaim(operation, requestAccessToken, responseAccessToken); + } + + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Unknown path."); + } + + private OperationExecutionResult removeScope(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + if (requestAccessToken.getScopes() == null || requestAccessToken.getScopes().isEmpty()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "No scopes to remove."); + } + + int index = validateIndex(operation.getPath(), requestAccessToken.getScopes().size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String scopeToRemove = requestAccessToken.getScopes().get(index); + boolean removed = responseAccessToken.getScopes().remove(scopeToRemove); + if (removed) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Scope removed."); + } else { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Failed to remove scope."); + } + } + + private OperationExecutionResult removeClaim(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + List claims = requestAccessToken.getClaims(); + if (claims == null || claims.isEmpty()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "No claims to remove."); + } + + if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.AUD.getName())) { + return removeAudience(operation, requestAccessToken, responseAccessToken); + } else { + return removeOtherClaims(operation, requestAccessToken, responseAccessToken); + } + } + + private OperationExecutionResult removeAudience(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + AccessToken.Claim audience = requestAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + if (audience != null && audience.getValue() != null && audience.getValue() instanceof List) { + List audienceList = (List) audience.getValue(); + + int index = validateIndex(operation.getPath(), audienceList.size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String audienceToRemove = audienceList.get(index); + AccessToken.Claim responseAudience = + responseAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + List responseAudienceList = (List) responseAudience.getValue(); + boolean removed = responseAudienceList.remove(audienceToRemove); + if (removed) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Audience removed."); + } + } + + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Audience not found."); + } + + private OperationExecutionResult removeOtherClaims(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + ClaimPathInfo claimPathInfo = parseOperationPath(operation.getPath()); + AccessToken.Claim claim = requestAccessToken.getClaim(claimPathInfo.getClaimName()); + if (claim == null) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, "Claim not found."); + } + + if (claimPathInfo.getIndex() != -1) { + return removeClaimValueAtIndexFromArrayTypeClaim(operation, claimPathInfo, claim, + responseAccessToken); + } else { + return removePrimitiveTypeClaim(operation, claimPathInfo, responseAccessToken); + } + } + + private OperationExecutionResult removeClaimValueAtIndexFromArrayTypeClaim(PerformableOperation operation, + ClaimPathInfo claimPathInfo, + AccessToken.Claim claim, + AccessToken.Builder + responseAccessToken) { + + if (!(claim.getValue() instanceof List)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Claim to remove the value from is not an array."); + } + + List claimValueList = (List) claim.getValue(); + if (claimPathInfo.getIndex() < 0 || claimPathInfo.getIndex() >= claimValueList.size()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String claimValueToRemove = claimValueList.get(claimPathInfo.getIndex()); + + AccessToken.Claim claimInResponse = + responseAccessToken.getClaim(claimPathInfo.getClaimName()); + List claimValueListInResponse = (List) claimInResponse.getValue(); + boolean removed = claimValueListInResponse.remove(claimValueToRemove); + + if (removed) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Claim value removed."); + } else { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Failed to remove claim value."); + } + } + + private OperationExecutionResult removePrimitiveTypeClaim(PerformableOperation operation, + ClaimPathInfo claimPathInfo, + AccessToken.Builder responseAccessToken) { + + boolean claimRemoved = + responseAccessToken.getClaims().removeIf(claim -> claim.getName().equals(claimPathInfo.getClaimName())); + + if (claimRemoved) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Claim removed."); + } else { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Failed to remove claim."); + } + } + + private OperationExecutionResult handleReplaceOperation(PerformableOperation operation, + AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + if (operation.getPath().startsWith(SCOPE_PATH_PREFIX)) { + return replaceScope(operation, requestAccessToken, responseAccessToken); + } else if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX)) { + return replaceClaim(operation, requestAccessToken, responseAccessToken); + } + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Unknown path."); + } + + private OperationExecutionResult replaceScope(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + List scopes = requestAccessToken.getScopes(); + if (scopes == null || scopes.isEmpty()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "No scopes."); + } + + int index = validateIndex(operation.getPath(), scopes.size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String scopeToAdd = operation.getValue().toString(); + if (!validateNQChar(scopeToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid scope."); + } + + if (scopes.contains(scopeToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Scope already exists."); + } + + String scopeToReplace = scopes.get(index); + responseAccessToken.getScopes().remove(scopeToReplace); + responseAccessToken.getScopes().add(scopeToAdd); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, "Scope replaced."); + } + + private OperationExecutionResult replaceClaim(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + List claims = requestAccessToken.getClaims(); + + if (claims == null || claims.isEmpty()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "No claims to replace."); + } + + if (operation.getPath().equals(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.EXPIRES_IN.getName())) { + return replaceExpiresIn(operation, responseAccessToken); + } else if (operation.getPath().startsWith(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.AUD.getName())) { + return replaceAudience(operation, requestAccessToken, responseAccessToken); + } else { + return replaceOtherClaims(operation, requestAccessToken, responseAccessToken); + } + } + + private OperationExecutionResult replaceExpiresIn(PerformableOperation operation, + AccessToken.Builder responseAccessToken) { + + long expiresIn; + try { + expiresIn = Long.parseLong(operation.getValue().toString()); + } catch (NumberFormatException e) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid expiry time format."); + } + + if (expiresIn <= 0) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid expiry time. Must be positive."); + } + + responseAccessToken.getClaims().removeIf( + claim -> claim.getName() + .equals(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.EXPIRES_IN.getName())); + responseAccessToken.addClaim(CLAIMS_PATH_PREFIX + AccessToken.ClaimNames.EXPIRES_IN.getName(), + expiresIn); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Expiry time updated."); + } + + private OperationExecutionResult replaceOtherClaims(PerformableOperation operation, AccessToken requestAccessToken, + AccessToken.Builder responseAccessToken) { + + ClaimPathInfo claimPathInfo = parseOperationPath(operation.getPath()); + AccessToken.Claim claim = requestAccessToken.getClaim(claimPathInfo.getClaimName()); + + if (claim == null) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Claim not found."); + } + + if (claimPathInfo.getIndex() != -1) { + return replaceClaimValueAtIndexFromArrayTypeClaim(operation, claimPathInfo, claim, responseAccessToken); + } else { + return replacePrimitiveTypeClaim(operation, claimPathInfo, responseAccessToken); + } + } + + private OperationExecutionResult replaceClaimValueAtIndexFromArrayTypeClaim(PerformableOperation operation, + ClaimPathInfo claimPathInfo, + AccessToken.Claim claim, + AccessToken.Builder + responseAccessToken) { + + if (!(claim.getValue() instanceof List)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Claim to replace the value is not an array."); + } + + List claimValueList = (List) claim.getValue(); + if (claimPathInfo.getIndex() < 0 || claimPathInfo.getIndex() >= claimValueList.size()) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, "Invalid index."); + } + + Object claimValue = operation.getValue(); + if (!isValidPrimitiveValue(claimValue)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid claim value. Must be a valid string, number or boolean."); + } + + // Replace claim value in the response access token + AccessToken.Claim claimInResponse = responseAccessToken.getClaim(claimPathInfo.getClaimName()); + List claimValueListInResponse = (List) claimInResponse.getValue(); + String claimToReplace = claimValueList.get(claimPathInfo.getIndex()); + if (claimValueListInResponse.contains(claimValue.toString())) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Claim value already exists."); + } + claimValueListInResponse.remove(claimToReplace); + claimValueListInResponse.add(claimValue.toString()); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Claim value replaced."); + } + + private OperationExecutionResult replacePrimitiveTypeClaim(PerformableOperation operation, + ClaimPathInfo claimPathInfo, + AccessToken.Builder responseAccessToken) { + + boolean claimRemoved = responseAccessToken.getClaims() + .removeIf(claim -> claim.getName().equals(claimPathInfo.getClaimName())); + if (claimRemoved) { + responseAccessToken.addClaim(claimPathInfo.getClaimName(), + operation.getValue()); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Claim replaced."); + } else { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Failed to replace claim."); + } + } + + private OperationExecutionResult replaceAudience(PerformableOperation operation, AccessToken + requestAccessToken, + AccessToken.Builder responseAccessToken) { + + AccessToken.Claim audience = requestAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + if (audience != null && audience.getValue() != null && audience.getValue() instanceof List) { + List audienceList = (List) audience.getValue(); + + int index = validateIndex(operation.getPath(), audienceList.size()); + if (index == -1) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid index."); + } + + String audienceToAdd = operation.getValue().toString(); + if (!isValidStringOrURI(audienceToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Invalid Audience. Must be a valid string or URI."); + } + + if (audienceList.contains(audienceToAdd)) { + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Audience to replace already exists."); + } + + String audienceToReplace = audienceList.get(index); + + AccessToken.Claim responseAudience = + responseAccessToken.getClaim(AccessToken.ClaimNames.AUD.getName()); + List responseAudienceList = (List) responseAudience.getValue(); + responseAudienceList.remove(audienceToReplace); + responseAudienceList.add(audienceToAdd); + return new OperationExecutionResult(operation, OperationExecutionResult.Status.SUCCESS, + "Audience replaced."); + } + + return new OperationExecutionResult(operation, OperationExecutionResult.Status.FAILURE, + "Audience claim not found."); + } + + private ClaimPathInfo parseOperationPath(String operationPath) { + + String[] pathSegments = operationPath.split("/"); + String lastSegment = pathSegments[pathSegments.length - 1]; + String claimName; + int index = -1; + + try { + // Attempt to parse the last segment as an integer to check if it's an index + index = Integer.parseInt(lastSegment); + // If parsing succeeds, the last segment is an index, so the claim name is the second last segment + claimName = pathSegments[pathSegments.length - 2]; + } catch (NumberFormatException e) { + // If parsing fails, the last segment is not an index, so it's the claim name itself + claimName = lastSegment; + } + + return new ClaimPathInfo(claimName, index); + } + + private boolean isValidPrimitiveValue(Object value) { + + return value instanceof String || value instanceof Number || value instanceof Boolean; + } + + private boolean isValidListValue(Object value) { + + if (!(value instanceof List)) { + return false; + } + List list = (List) value; + return list.stream().allMatch(item -> item instanceof String); + } + + private int validateIndex(String operationPath, int listSize) { + + String indexPart = operationPath.substring(operationPath.lastIndexOf(PATH_SEPARATOR) + 1); + if (LAST_ELEMENT_CHARACTER.equals(indexPart)) { + return listSize > 0 ? listSize - 1 : 0; + } + + try { + int index = Integer.parseInt(indexPart); + if (index >= 0 && index < listSize) { + return index; + } else { + LOG.info("Index is out of bounds: " + indexPart); + return -1; + } + } catch (NumberFormatException ignored) { + LOG.info("Extracted index is not a valid integer. Index: " + indexPart); + } + + LOG.info("Invalid index: " + indexPart); + return -1; + } + + private boolean validateNQChar(String input) { + + Matcher matcher = NQCHAR_PATTERN.matcher(input); + return matcher.matches(); + } + + private boolean isValidStringOrURI(String input) { + + Matcher matcher = STRING_OR_URI_PATTERN.matcher(input); + return matcher.matches(); + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/AccessToken.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/AccessToken.java new file mode 100644 index 00000000000..48ae5300554 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/AccessToken.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * This class represents the model of the access token sent in the json request + * to the API endpoint of the pre issue access token action. + */ +public class AccessToken { + + private final String tokenType; + List scopes; + List claims; + + private AccessToken(Builder builder) { + + this.tokenType = builder.tokenType; + this.scopes = builder.scopes; + this.claims = builder.claims; + } + + public String getTokenType() { + + return tokenType; + } + + public List getScopes() { + + return scopes; + } + + public List getClaims() { + + return claims; + } + + public Claim getClaim(String name) { + + if (claims != null) { + for (Claim claim : claims) { + if (claim.getName().equals(name)) { + return claim; + } + } + } + + return null; + } + + public AccessToken.Builder copy() { + + return new Builder() + .tokenType(this.tokenType) + .scopes(new ArrayList<>(this.scopes)) + .claims(this.claims.stream().map(Claim::copy).collect(Collectors.toList())); + } + + /** + * Enum for standard claim names. + */ + public enum ClaimNames { + + SUB("sub"), + ISS("iss"), + AUD("aud"), + CLIENT_ID("client_id"), + AUTHORIZED_USER_TYPE("aut"), + EXPIRES_IN("expires_in"), + + TOKEN_BINDING_REF("binding_ref"), + TOKEN_BINDING_TYPE("binding_type"), + SUBJECT_TYPE("subject_type"); + + private final String name; + + ClaimNames(String name) { + + this.name = name; + } + + public static boolean contains(String name) { + + return Arrays.stream(ClaimNames.values()) + .anyMatch(claimName -> claimName.name.equals(name)); + } + + public String getName() { + + return name; + } + } + + /** + * Model class for claims. + */ + public static class Claim { + + private String name; + private Object value; + + public Claim() { + + } + + public Claim(String name, Object value) { + + this.name = name; + this.value = value; + } + + public String getName() { + + return name; + } + + public void setName(String name) { + + this.name = name; + } + + public Object getValue() { + + return value; + } + + public void setValue(Object value) { + + this.value = value; + } + + public Claim copy() { + + return new Claim(this.name, deepCopyValue(this.value)); + } + + private Object deepCopyValue(Object value) { + + if (value instanceof List) { + List originalList = (List) value; + List copiedList = new ArrayList<>(); + for (Object item : originalList) { + copiedList.add(deepCopyValue(item)); // Recursive for nested lists + } + return copiedList; + } else if (value instanceof Map) { + Map originalMap = (Map) value; + Map copiedMap = new HashMap<>(); + for (Map.Entry entry : originalMap.entrySet()) { + copiedMap.put(entry.getKey(), deepCopyValue(entry.getValue())); // Recursive for nested maps + } + return copiedMap; + } else if (value.getClass().isArray()) { + return Arrays.copyOf((Object[]) value, ((Object[]) value).length); + } else { + // For immutable types or types not requiring deep copy + return value; + } + } + } + + /** + * Builder for AccessToken. + */ + public static class Builder { + + private String tokenType; + private List scopes = new ArrayList<>(); + private List claims = new ArrayList<>(); + + public Builder tokenType(String tokenType) { + + this.tokenType = tokenType; + return this; + } + + public Builder scopes(List scopes) { + + this.scopes = scopes; + return this; + } + + public Builder claims(List claims) { + + this.claims = claims; + return this; + } + + public Builder addClaim(String name, Object value) { + + this.claims.add(new Claim(name, value)); + return this; + } + + public Builder addScope(String scope) { + + this.scopes.add(scope); + return this; + } + + public String getTokenType() { + + return tokenType; + } + + public List getScopes() { + + return scopes; + } + + public List getClaims() { + + return claims; + } + + public Claim getClaim(String name) { + + if (claims != null) { + for (Claim claim : claims) { + if (claim.getName().equals(name)) { + return claim; + } + } + } + + return null; + } + + public AccessToken build() { + + return new AccessToken(this); + } + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/ClaimPathInfo.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/ClaimPathInfo.java new file mode 100644 index 00000000000..e2d4e216982 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/ClaimPathInfo.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action.model; + +/** + * This class is used to identify the claim and the index of the value of the claim to be updated in the + * {@link AccessToken}. The claim value of the {@link AccessToken} is an array in this case. + */ +public class ClaimPathInfo { + + private final String claimName; + private final int index; + + public ClaimPathInfo(String claimName, int index) { + + this.claimName = claimName; + this.index = index; + } + + public String getClaimName() { + + return claimName; + } + + public int getIndex() { + + return index; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/OperationExecutionResult.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/OperationExecutionResult.java new file mode 100644 index 00000000000..3d820c4f426 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/OperationExecutionResult.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action.model; + +import org.wso2.carbon.identity.action.execution.model.PerformableOperation; + +/** + * This class represents the result of the execution of an operation. + * It contains the operation that was executed, the status of the execution and a message. + * This is used to summarize the operations performed based on action response. + */ +public class OperationExecutionResult { + + private final PerformableOperation operation; + private final Status status; + private final String message; + + public OperationExecutionResult(PerformableOperation operation, Status status, String message) { + + this.operation = operation; + this.status = status; + this.message = message; + } + + public PerformableOperation getOperation() { + + return operation; + } + + public Status getStatus() { + + return status; + } + + public String getMessage() { + + return message; + } + + /** + * Enum to represent the status of the operation execution. + */ + public enum Status { + SUCCESS, FAILURE + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/PreIssueAccessTokenEvent.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/PreIssueAccessTokenEvent.java new file mode 100644 index 00000000000..631451c56cd --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/PreIssueAccessTokenEvent.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action.model; + +import org.wso2.carbon.identity.action.execution.model.Event; +import org.wso2.carbon.identity.action.execution.model.Organization; +import org.wso2.carbon.identity.action.execution.model.Request; +import org.wso2.carbon.identity.action.execution.model.Tenant; +import org.wso2.carbon.identity.action.execution.model.User; +import org.wso2.carbon.identity.action.execution.model.UserStore; + +/** + * This class models the event at a pre issue access token trigger. + * PreIssueAccessTokenEvent is the entity that represents the event that is sent to the Action + * over {@link org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest}. + */ +public class PreIssueAccessTokenEvent extends Event { + + private final AccessToken accessToken; + + private PreIssueAccessTokenEvent(Builder builder) { + + this.accessToken = builder.accessToken; + this.request = builder.request; + this.organization = builder.organization; + this.tenant = builder.tenant; + this.user = builder.user; + this.userStore = builder.userStore; + } + + public AccessToken getAccessToken() { + + return accessToken; + } + + /** + * Builder for the PreIssueAccessTokenEvent. + */ + public static class Builder { + + private AccessToken accessToken; + private Request request; + private Organization organization; + private Tenant tenant; + private User user; + + private UserStore userStore; + + public Builder accessToken(AccessToken accessToken) { + + this.accessToken = accessToken; + return this; + } + + public Builder request(Request request) { + + this.request = request; + return this; + } + + public Builder organization(Organization organization) { + + this.organization = organization; + return this; + } + + public Builder tenant(Tenant tenant) { + + this.tenant = tenant; + return this; + } + + public Builder user(User user) { + + this.user = user; + return this; + } + + public Builder userStore(UserStore userStore) { + + this.userStore = userStore; + return this; + } + + public PreIssueAccessTokenEvent build() { + + return new PreIssueAccessTokenEvent(this); + } + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/TokenRequest.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/TokenRequest.java new file mode 100644 index 00000000000..7e0472607cf --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/action/model/TokenRequest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth.action.model; + +import org.wso2.carbon.identity.action.execution.model.Request; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class models the request at a pre issue access token trigger. + * TokenRequest is the entity that represents the request that is sent to Action + * over {@link org.wso2.carbon.identity.action.execution.model.ActionExecutionRequest}. + */ +public class TokenRequest extends Request { + + private static final List headersToAvoid = new ArrayList<>(); + private static final List paramsToAvoid = new ArrayList<>(); + + static { + headersToAvoid.add("authorization"); + headersToAvoid.add("cookie"); + headersToAvoid.add("set-cookie"); + headersToAvoid.add("accept-encoding"); + headersToAvoid.add("accept-language"); + headersToAvoid.add("content-length"); + headersToAvoid.add("content-type"); + // parameters from authorization code grant + paramsToAvoid.add("code"); + paramsToAvoid.add("redirect_uri"); + paramsToAvoid.add("grant_type"); + paramsToAvoid.add("scope"); + // parameters from password grant + paramsToAvoid.add("username"); + paramsToAvoid.add("password"); + // parameters from refresh token grant + paramsToAvoid.add("refresh_token"); + // parameters used for client authentication for token endpoint + paramsToAvoid.add("client_id"); + paramsToAvoid.add("client_secret"); + paramsToAvoid.add("client_assertion_type"); + paramsToAvoid.add("client_assertion"); + } + + private final String clientId; + private final String grantType; + private final String redirectUri; + private final List scopes; + + private TokenRequest(Builder builder) { + + this.clientId = builder.clientId; + this.grantType = builder.grantType; + this.redirectUri = builder.redirectUri; + this.scopes = builder.scopes; + this.additionalHeaders = builder.additionalHeaders; + this.additionalParams = builder.additionalParams; + } + + public String getClientId() { + + return clientId; + } + + public String getGrantType() { + + return grantType; + } + + public String getRedirectUri() { + + return redirectUri; + } + + public List getScopes() { + + return scopes; + } + + /** + * Builder for TokenRequest. + */ + public static class Builder { + + private final Map additionalHeaders = new HashMap<>(); + private final Map additionalParams = new HashMap<>(); + private String clientId; + private String grantType; + private String redirectUri; + private List scopes = new ArrayList<>(); + + public Builder clientId(String clientId) { + + this.clientId = clientId; + return this; + } + + public Builder grantType(String grantType) { + + this.grantType = grantType; + return this; + } + + public Builder redirectUri(String redirectUri) { + + this.redirectUri = redirectUri; + return this; + } + + public Builder scopes(List scopes) { + + this.scopes = scopes; + return this; + } + + public Builder addAdditionalHeader(String key, String[] value) { + + if (!headersToAvoid.contains(key.toLowerCase())) { + this.additionalHeaders.put(key, value); + } + return this; + } + + public Builder addAdditionalParam(String key, String[] value) { + + if (!paramsToAvoid.contains(key)) { + this.additionalParams.put(key, value); + } + return this; + } + + public TokenRequest build() { + + return new TokenRequest(this); + } + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/cache/AuthorizationGrantCacheEntry.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/cache/AuthorizationGrantCacheEntry.java index 5f562c565fd..41e81160253 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/cache/AuthorizationGrantCacheEntry.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/cache/AuthorizationGrantCacheEntry.java @@ -84,6 +84,12 @@ public class AuthorizationGrantCacheEntry extends CacheEntry { private List federatedTokens; + private List audiences; + + private Map customClaims; + + private boolean isPreIssueAccessTokenActionsExecuted; + public String getSubjectClaim() { return subjectClaim; } @@ -144,6 +150,10 @@ public AuthorizationGrantCacheEntry(Map userAttributes) { this.userAttributes = userAttributes; } + public AuthorizationGrantCacheEntry() { + + } + public String getNonceValue() { return nonceValue; } @@ -350,4 +360,34 @@ public void setApiBasedAuthRequest(boolean apiBasedAuthRequest) { isApiBasedAuthRequest = apiBasedAuthRequest; } + + public List getAudiences() { + + return audiences; + } + + public void setAudiences(List audiences) { + + this.audiences = audiences; + } + + public Map getCustomClaims() { + + return customClaims; + } + + public void setCustomClaims(Map customClaims) { + + this.customClaims = customClaims; + } + + public boolean isPreIssueAccessTokenActionsExecuted() { + + return isPreIssueAccessTokenActionsExecuted; + } + + public void setPreIssueAccessTokenActionsExecuted(boolean preIssueAccessTokenActionsExecuted) { + + isPreIssueAccessTokenActionsExecuted = preIssueAccessTokenActionsExecuted; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/config/OAuthServerConfiguration.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/config/OAuthServerConfiguration.java index f02fb82bd19..d9fd4e6557e 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/config/OAuthServerConfiguration.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/config/OAuthServerConfiguration.java @@ -322,6 +322,8 @@ public class OAuthServerConfiguration { private Boolean roleBasedScopeIssuerEnabledConfig = false; private String scopeMetadataExtensionImpl = null; + private final List restrictedQueryParameters = new ArrayList<>(); + private OAuthServerConfiguration() { buildOAuthServerConfiguration(); } @@ -529,6 +531,9 @@ private void buildOAuthServerConfiguration() { // Read config for scope metadata extension implementation. parseScopeMetadataExtensionImpl(oauthElem); + + // Read config for restricted query parameters in oauth requests + parseRestrictedQueryParameters(oauthElem); } /** @@ -1453,6 +1458,10 @@ public boolean isUseMultiValueSeparatorForAuthContextToken() { return useMultiValueSeparatorForAuthContextToken; } + public List getRestrictedQueryParameters() { + return restrictedQueryParameters; + } + public TokenPersistenceProcessor getPersistenceProcessor() throws IdentityOAuth2Exception { if (persistenceProcessor == null) { synchronized (this) { @@ -3741,6 +3750,20 @@ private void parseScopeMetadataExtensionImpl(OMElement oauthConfigElem) { } } + private void parseRestrictedQueryParameters(OMElement oauthConfigElem) { + + OMElement restrictedQueryParametersElem = oauthConfigElem.getFirstChildWithName( + getQNameWithIdentityNS(ConfigElements.RESTRICTED_QUERY_PARAMETERS_ELEMENT)); + if (restrictedQueryParametersElem != null) { + Iterator paramIterator = restrictedQueryParametersElem.getChildrenWithName(getQNameWithIdentityNS( + ConfigElements.RESTRICTED_QUERY_PARAMETER_ELEMENT)); + while (paramIterator.hasNext()) { + OMElement paramElement = (OMElement) paramIterator.next(); + restrictedQueryParameters.add(paramElement.getText()); + } + } + } + /** * Get scope metadata service extension impl class. * @@ -4016,6 +4039,8 @@ private class ConfigElements { private static final String USE_LEGACY_PERMISSION_ACCESS_FOR_USER_BASED_AUTH = "UseLegacyPermissionAccessForUserBasedAuth"; private static final String SCOPE_METADATA_EXTENSION_IMPL = "ScopeMetadataService"; + private static final String RESTRICTED_QUERY_PARAMETERS_ELEMENT = "RestrictedQueryParameters"; + private static final String RESTRICTED_QUERY_PARAMETER_ELEMENT = "Parameter"; } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDAO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDAO.java old mode 100644 new mode 100755 index 0608a7a8c99..dfb0bc88f2a --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDAO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDAO.java @@ -47,6 +47,7 @@ import org.wso2.carbon.identity.oauth.tokenprocessor.PlainTextPersistenceProcessor; import org.wso2.carbon.identity.oauth.tokenprocessor.TokenPersistenceProcessor; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ServerException; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; @@ -102,6 +103,7 @@ import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_BINDING_TYPE; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_BINDING_TYPE_NONE; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_BINDING_VALIDATION; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_EP_ALLOW_REUSE_PVT_KEY_JWT; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_REVOCATION_WITH_IDP_SESSION_TERMINATION; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.TOKEN_TYPE; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.OPENID_CONNECT_AUDIENCE; @@ -982,6 +984,10 @@ private void addOrUpdateOIDCSpProperty(OAuthAppDO oauthAppDO, TOKEN_AUTH_METHOD, oauthAppDO.getTokenEndpointAuthMethod(), prepStatementForPropertyAdd, preparedStatementForPropertyUpdate); + addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties, + TOKEN_EP_ALLOW_REUSE_PVT_KEY_JWT, String.valueOf(oauthAppDO.isTokenEndpointAllowReusePvtKeyJwt()), + prepStatementForPropertyAdd, preparedStatementForPropertyUpdate); + addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties, TOKEN_AUTH_SIGNATURE_ALGORITHM, oauthAppDO.getTokenEndpointAuthSignatureAlgorithm(), prepStatementForPropertyAdd, preparedStatementForPropertyUpdate); @@ -1636,6 +1642,12 @@ private void addServiceProviderOIDCProperties(Connection connection, addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty, TOKEN_AUTH_METHOD, consumerAppDO.getTokenEndpointAuthMethod()); + if (consumerAppDO.isTokenEndpointAllowReusePvtKeyJwt() != null) { + addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty, + TOKEN_EP_ALLOW_REUSE_PVT_KEY_JWT, + String.valueOf(consumerAppDO.isTokenEndpointAllowReusePvtKeyJwt())); + } + addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty, TOKEN_AUTH_SIGNATURE_ALGORITHM, consumerAppDO.getTokenEndpointAuthSignatureAlgorithm()); @@ -1732,7 +1744,8 @@ private Map> getSpOIDCProperties(Connection connection, return spOIDCProperties; } - private void setSpOIDCProperties(Map> spOIDCProperties, OAuthAppDO oauthApp) { + private void setSpOIDCProperties(Map> spOIDCProperties, OAuthAppDO oauthApp) + throws IdentityOAuth2ServerException { // Handle OIDC audience values if (isOIDCAudienceEnabled() && @@ -1795,6 +1808,11 @@ private void setSpOIDCProperties(Map> spOIDCProperties, OAu if (tokenAuthMethod != null) { oauthApp.setTokenEndpointAuthMethod(tokenAuthMethod); } + String tokenEPAllowReusePvtKeyJwt = OAuthUtil.getValueOfTokenEPAllowReusePvtKeyJwt( + getFirstPropertyValue(spOIDCProperties, TOKEN_EP_ALLOW_REUSE_PVT_KEY_JWT), tokenAuthMethod); + if (tokenEPAllowReusePvtKeyJwt != null) { + oauthApp.setTokenEndpointAllowReusePvtKeyJwt(Boolean.parseBoolean(tokenEPAllowReusePvtKeyJwt)); + } String tokenSignatureAlgorithm = getFirstPropertyValue(spOIDCProperties, TOKEN_AUTH_SIGNATURE_ALGORITHM); if (tokenSignatureAlgorithm != null) { oauthApp.setTokenEndpointAuthSignatureAlgorithm(tokenSignatureAlgorithm); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDO.java index b48b74bae72..50a1f48db45 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dao/OAuthAppDO.java @@ -81,6 +81,7 @@ public class OAuthAppDO extends InboundConfigurationProtocol implements Serializ private boolean tokenRevocationWithIDPSessionTerminationEnabled; private boolean tokenBindingValidationEnabled; private String tokenEndpointAuthMethod; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String tokenEndpointAuthSignatureAlgorithm; private String sectorIdentifierURI; private String idTokenSignatureAlgorithm; @@ -383,6 +384,16 @@ public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; } + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + public String getTokenEndpointAuthSignatureAlgorithm() { return tokenEndpointAuthSignatureAlgorithm; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dto/OAuthConsumerAppDTO.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dto/OAuthConsumerAppDTO.java index f93e9acc4f7..fba94088c12 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dto/OAuthConsumerAppDTO.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/dto/OAuthConsumerAppDTO.java @@ -67,6 +67,7 @@ public class OAuthConsumerAppDTO implements InboundProtocolConfigurationDTO { private boolean tokenBindingValidationEnabled; private String tokenEndpointAuthMethod; private String tokenEndpointAuthSignatureAlgorithm; + private Boolean tokenEndpointAllowReusePvtKeyJwt; private String sectorIdentifierURI; private String idTokenSignatureAlgorithm; private String requestObjectSignatureAlgorithm; @@ -384,6 +385,16 @@ public void setTokenEndpointAuthSignatureAlgorithm(String tokenEndpointAuthSigna this.tokenEndpointAuthSignatureAlgorithm = tokenEndpointAuthSignatureAlgorithm; } + public Boolean isTokenEndpointAllowReusePvtKeyJwt() { + + return tokenEndpointAllowReusePvtKeyJwt; + } + + public void setTokenEndpointAllowReusePvtKeyJwt(Boolean tokenEndpointAllowReusePvtKeyJwt) { + + this.tokenEndpointAllowReusePvtKeyJwt = tokenEndpointAllowReusePvtKeyJwt; + } + public String getSectorIdentifierURI() { return sectorIdentifierURI; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthComponentServiceHolder.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthComponentServiceHolder.java index 5a91f8a5158..bab0212fbd5 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthComponentServiceHolder.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthComponentServiceHolder.java @@ -20,8 +20,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.action.execution.ActionExecutorService; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService; +import org.wso2.carbon.identity.configuration.mgt.core.ConfigurationManager; import org.wso2.carbon.identity.cors.mgt.core.CORSManagementService; import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl; import org.wso2.carbon.identity.oauth.OauthInboundAuthConfigHandler; @@ -55,11 +57,11 @@ */ public class OAuthComponentServiceHolder { + private static final Log log = LogFactory.getLog(OAuthComponentServiceHolder.class); private static OAuthComponentServiceHolder instance = new OAuthComponentServiceHolder(); private RealmService realmService; private OAuthEventInterceptor oAuthEventInterceptorHandlerProxy; private OAuth2Service oauth2Service; - private static final Log log = LogFactory.getLog(OAuthComponentServiceHolder.class); private OAuth2ScopeService oauth2ScopeService; private List tokenBindingMetaDataDTOs = new ArrayList<>(); private OAuthAdminServiceImpl oAuthAdminService; @@ -80,6 +82,18 @@ public class OAuthComponentServiceHolder { private AuthorizedAPIManagementService authorizedAPIManagementService; private IdpManager idpManager; private OrganizationUserSharingService organizationUserSharingService; + private ConfigurationManager configurationManager; + + private ActionExecutorService actionExecutorService; + + private OAuthComponentServiceHolder() { + + } + + public static OAuthComponentServiceHolder getInstance() { + + return instance; + } /** * Get the list of scope validator implementations available. @@ -91,6 +105,16 @@ public List getScopeValidators() { return scopeValidators; } + /** + * Set a list of scope validator implementations. + * + * @param scopeValidators List of Scope validator implementation. + */ + public void setScopeValidators(List scopeValidators) { + + this.scopeValidators = scopeValidators; + } + /** * Add scope validator implementation. * @@ -111,16 +135,6 @@ public void removeScopeValidator(ScopeValidator scopeValidator) { scopeValidators.remove(scopeValidator); } - /** - * Set a list of scope validator implementations. - * - * @param scopeValidators List of Scope validator implementation. - */ - public void setScopeValidators(List scopeValidators) { - - this.scopeValidators = scopeValidators; - } - /** * Get the list of scope validation handler implementations available. * @@ -161,15 +175,6 @@ public void setScopeValidatorPolicyHandlers(List scopeVa this.scopeValidationHandlers = scopeValidationHandlers; } - private OAuthComponentServiceHolder() { - - } - - public static OAuthComponentServiceHolder getInstance() { - - return instance; - } - public RealmService getRealmService() { return realmService; @@ -181,18 +186,22 @@ public void setRealmService(RealmService realmService) { } public void addOauthEventInterceptorProxy(OAuthEventInterceptor oAuthEventInterceptorHandlerProxy) { + this.oAuthEventInterceptorHandlerProxy = oAuthEventInterceptorHandlerProxy; } public OAuthEventInterceptor getOAuthEventInterceptorProxy() { + return this.oAuthEventInterceptorHandlerProxy; } public OAuth2Service getOauth2Service() { + return oauth2Service; } public void setOauth2Service(OAuth2Service oauth2Service) { + this.oauth2Service = oauth2Service; } @@ -254,6 +263,16 @@ public void removeOAuthApplicationMgtListener(OAuthApplicationMgtListener oAuthA .remove(oAuthApplicationMgtListener.getExecutionOrder(), oAuthApplicationMgtListener); } + /** + * Get RoleManagementService instance. + * + * @return RoleManagementService instance. + */ + public RoleManagementService getRoleManagementService() { + + return roleManagementService; + } + /** * Set RoleManagementService instance. * @@ -269,9 +288,9 @@ public void setRoleManagementService(RoleManagementService roleManagementService * * @return RoleManagementService instance. */ - public RoleManagementService getRoleManagementService() { + public org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService getRoleV2ManagementService() { - return roleManagementService; + return roleV2ManagementService; } /** @@ -285,16 +304,6 @@ public void setRoleV2ManagementService( this.roleV2ManagementService = roleManagementService; } - /** - * Get RoleManagementService instance. - * - * @return RoleManagementService instance. - */ - public org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService getRoleV2ManagementService() { - - return roleV2ManagementService; - } - /** * Get OrganizationUserResidentResolverService instance. * @@ -389,7 +398,7 @@ public void setAccessTokenDAOService(AccessTokenDAO accessTokenDAOService) { /** * Get TokenManagementDAO instance. * - * @return TokenManagementDAO {@link TokenManagementDAO} instance. + * @return TokenManagementDAO {@link TokenManagementDAO} instance. */ public TokenManagementDAO getTokenManagementDAOService() { @@ -408,6 +417,7 @@ public void setTokenManagementDAOService(TokenManagementDAO tokenManagementDAOSe /** * Get ApplicationManagementService instance. + * * @return ApplicationManagementService instance. */ public ApplicationManagementService getApplicationManagementService() { @@ -417,6 +427,7 @@ public ApplicationManagementService getApplicationManagementService() { /** * Set ApplicationManagementService instance. + * * @param applicationManagementService ApplicationManagementService instance. */ public void setApplicationManagementService(ApplicationManagementService applicationManagementService) { @@ -426,6 +437,7 @@ public void setApplicationManagementService(ApplicationManagementService applica /** * Get OAuthProtocolApplicationService instance. + * * @return OAuthProtocolApplicationService instance. */ public OauthInboundAuthConfigHandler getOAuthInboundConfigHandler() { @@ -435,6 +447,7 @@ public OauthInboundAuthConfigHandler getOAuthInboundConfigHandler() { /** * Set OAuthProtocolApplicationService instance. + * * @param oauthInboundAuthConfigHandler OAuthProtocolApplicationService instance. */ public void setOAuthInboundConfigHandler(OauthInboundAuthConfigHandler oauthInboundAuthConfigHandler) { @@ -511,4 +524,34 @@ public void setOrganizationUserSharingService(OrganizationUserSharingService org this.organizationUserSharingService = organizationUserSharingService; } + + public ActionExecutorService getActionExecutorService() { + + return actionExecutorService; + } + + public void setActionExecutorService(ActionExecutorService actionExecutorService) { + + this.actionExecutorService = actionExecutorService; + } + + /** + * Get the ConfigurationManager instance. + * + * @return ConfigurationManager The ConfigurationManager instance. + */ + public ConfigurationManager getConfigurationManager() { + + return configurationManager; + } + + /** + * Set the ConfigurationManager instance. + * + * @param configurationManager ConfigurationManager The ConfigurationManager instance. + */ + public void setConfigurationManager(ConfigurationManager configurationManager) { + + this.configurationManager = configurationManager; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthServiceComponent.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthServiceComponent.java index a253d8836d3..3d24ba3dc91 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthServiceComponent.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/internal/OAuthServiceComponent.java @@ -26,15 +26,21 @@ import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; +import org.wso2.carbon.identity.action.execution.ActionExecutionRequestBuilder; +import org.wso2.carbon.identity.action.execution.ActionExecutionResponseProcessor; +import org.wso2.carbon.identity.action.execution.ActionExecutorService; import org.wso2.carbon.identity.application.authentication.framework.UserSessionManagementService; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService; import org.wso2.carbon.identity.application.mgt.inbound.protocol.ApplicationInboundAuthConfigHandler; +import org.wso2.carbon.identity.configuration.mgt.core.ConfigurationManager; import org.wso2.carbon.identity.core.util.IdentityCoreInitializedEvent; import org.wso2.carbon.identity.cors.mgt.core.CORSManagementService; import org.wso2.carbon.identity.event.handler.AbstractEventHandler; import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl; import org.wso2.carbon.identity.oauth.OauthInboundAuthConfigHandler; +import org.wso2.carbon.identity.oauth.action.PreIssueAccessTokenRequestBuilder; +import org.wso2.carbon.identity.oauth.action.PreIssueAccessTokenResponseProcessor; import org.wso2.carbon.identity.oauth.cache.OAuthCache; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.common.token.bindings.TokenBinderInfo; @@ -69,6 +75,7 @@ public class OAuthServiceComponent { private ServiceRegistration serviceRegistration = null; protected void activate(ComponentContext context) { + try { // initialize the OAuth Server configuration OAuthServerConfiguration oauthServerConfig = OAuthServerConfiguration.getInstance(); @@ -109,6 +116,8 @@ protected void activate(ComponentContext context) { authProtocolApplicationService); context.getBundleContext().registerService(ApplicationInboundAuthConfigHandler.class, authProtocolApplicationService, null); + + registerActionRequestBuilderAndResponseProcessor(context); // Note : DO NOT add any activation related code below this point, // to make sure the server doesn't start up if any activation failures occur @@ -122,6 +131,15 @@ protected void activate(ComponentContext context) { } } + private void registerActionRequestBuilderAndResponseProcessor(ComponentContext context) { + + context.getBundleContext() + .registerService(ActionExecutionRequestBuilder.class, new PreIssueAccessTokenRequestBuilder(), null); + context.getBundleContext() + .registerService(ActionExecutionResponseProcessor.class, new PreIssueAccessTokenResponseProcessor(), + null); + } + protected void deactivate(ComponentContext context) { if (serviceRegistration != null) { @@ -263,10 +281,10 @@ protected void setIdentityCoreInitializedEventService(IdentityCoreInitializedEve } @Reference(name = "token.binding.service", - service = TokenBinderInfo.class, - cardinality = ReferenceCardinality.MULTIPLE, - policy = ReferencePolicy.DYNAMIC, - unbind = "unsetTokenBinderInfo") + service = TokenBinderInfo.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetTokenBinderInfo") protected void setTokenBinderInfo(TokenBinderInfo tokenBinderInfo) { if (log.isDebugEnabled()) { @@ -284,10 +302,10 @@ protected void unsetTokenBinderInfo(TokenBinderInfo tokenBinderInfo) { } @Reference(name = "oauth.application.mgt.listener", - service = OAuthApplicationMgtListener.class, - cardinality = ReferenceCardinality.MULTIPLE, - policy = ReferencePolicy.DYNAMIC, - unbind = "unsetOAuthApplicationMgtListener") + service = OAuthApplicationMgtListener.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetOAuthApplicationMgtListener") protected void setOAuthApplicationMgtListener(OAuthApplicationMgtListener oAuthApplicationMgtListener) { if (log.isDebugEnabled()) { @@ -521,4 +539,56 @@ protected void unsetOrganizationUserSharingService(OrganizationUserSharingServic } OAuthComponentServiceHolder.getInstance().setOrganizationUserSharingService(null); } + + /** + * Set the ConfigurationManager. + * + * @param configurationManager The {@code ConfigurationManager} instance. + */ + @Reference( + name = "resource.configuration.manager", + service = ConfigurationManager.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unregisterConfigurationManager" + ) + protected void registerConfigurationManager(ConfigurationManager configurationManager) { + + if (log.isDebugEnabled()) { + log.debug("Registering the ConfigurationManager in JWT Client Authenticator ManagementService."); + } + OAuthComponentServiceHolder.getInstance().setConfigurationManager(configurationManager); + } + + /** + * Unset the ConfigurationManager. + * + * @param configurationManager The {@code ConfigurationManager} instance. + */ + protected void unregisterConfigurationManager(ConfigurationManager configurationManager) { + + if (log.isDebugEnabled()) { + log.debug("Unregistering the ConfigurationManager in JWT Client Authenticator ManagementService."); + } + OAuthComponentServiceHolder.getInstance().setConfigurationManager(null); + } + + @Reference( + name = "action.execution.service", + service = ActionExecutorService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unregisterActionExecutorService" + ) + protected void registerActionExecutionService(ActionExecutorService actionExecutorService) { + + log.debug("Registering the ActionExecutorService in OAuthServiceComponent."); + OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService); + } + + protected void unregisterActionExecutorService(ActionExecutorService actionExecutorService) { + + log.debug("Unregistering the ActionExecutorService in OAuthServiceComponent."); + OAuthComponentServiceHolder.getInstance().setActionExecutorService(null); + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/utils/Constants.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/utils/Constants.java index abbc5e19a02..019943244eb 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/utils/Constants.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/utils/Constants.java @@ -24,6 +24,7 @@ */ public class Constants { + public static final String IMPERSONATION_SCOPE_NAME = "internal_user_impersonate"; public static final String OAUTH_2 = "oauth2"; public static final String ENABLE_EMAIL_NOTIFICATION = "EnableEmailNotification"; public static final String IMPERSONATION_RESOURCE_TYPE_NAME = "IMPERSONATION_CONFIGURATION"; diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/ImpersonatorPermissionValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/ImpersonatorPermissionValidator.java new file mode 100644 index 00000000000..cacdbfa1448 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/ImpersonatorPermissionValidator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.wso2.carbon.identity.oauth2.impersonation.validators; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationContext; +import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationRequestDTO; +import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; + +import java.util.List; + +import static org.wso2.carbon.identity.oauth2.impersonation.utils.Constants.IMPERSONATION_SCOPE_NAME; + +/** + * The ImpersonatorPermissionValidator class is responsible for validating whether an authenticated user + * has the necessary impersonation permissions within a given tenant and for a specified client. + * The validation process involves checking if the authenticated user has the "internal_user_impersonate" + * in their authorized scopes. If the scope is present, the impersonation context is marked as validated. + */ +public class ImpersonatorPermissionValidator implements ImpersonationValidator { + + private static final String NAME = "ImpersonatorPermissionValidator"; + private static final Log LOG = LogFactory.getLog(ImpersonatorPermissionValidator.class); + private DefaultOAuth2ScopeValidator scopeValidator; + + public ImpersonatorPermissionValidator() { + + this.scopeValidator = new DefaultOAuth2ScopeValidator(); + } + + @Override + public int getPriority() { + + return 100; + } + + @Override + public String getImpersonationValidatorName() { + + return NAME; + } + + @Override + public ImpersonationContext validateImpersonation(ImpersonationContext impersonationContext) + throws IdentityOAuth2Exception { + + ImpersonationRequestDTO impersonationRequestDTO = impersonationContext.getImpersonationRequestDTO(); + OAuthAuthzReqMessageContext authzReqMessageContext = impersonationRequestDTO.getoAuthAuthzReqMessageContext(); + + String tenantDomain = authzReqMessageContext.getAuthorizationReqDTO().getTenantDomain(); + String clientId = authzReqMessageContext.getAuthorizationReqDTO().getConsumerKey(); + authzReqMessageContext.getAuthorizationReqDTO().setScopes(authzReqMessageContext.getRequestedScopes()); + List authorizedScopes = scopeValidator.validateScope(authzReqMessageContext); + if (authorizedScopes.contains(IMPERSONATION_SCOPE_NAME)) { + impersonationContext.setValidated(true); + } else { + impersonationContext.setValidated(false); + impersonationContext.setValidationFailureErrorMessage("Authenticated user : " + authzReqMessageContext + .getAuthorizationReqDTO().getUser().getLoggableMaskedUserId() + " doesn't have impersonation " + + "permission for client : " + clientId + " in the tenant : " + tenantDomain); + LOG.error("Authenticated user : " + authzReqMessageContext + .getAuthorizationReqDTO().getUser().getLoggableMaskedUserId() + "doesn't have impersonation " + + "permission for client : " + clientId + " in the tenant : " + tenantDomain); + } + return impersonationContext; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/SubjectScopeValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/SubjectScopeValidator.java index ceeb81a1157..670e755e620 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/SubjectScopeValidator.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/impersonation/validators/SubjectScopeValidator.java @@ -19,21 +19,25 @@ package org.wso2.carbon.identity.oauth2.impersonation.validators; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.application.common.model.User; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationContext; import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationRequestDTO; -import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.core.service.RealmService; import java.util.List; +import static org.wso2.carbon.identity.oauth.OAuthUtil.getUserFromTenant; + /** * The {@code SubjectScopeValidator} class implements the {@link ImpersonationValidator} interface * to validate impersonation requests based on subject scopes. @@ -91,18 +95,26 @@ public ImpersonationContext validateImpersonation(ImpersonationContext impersona private AuthenticatedUser getAuthenticatedSubjectUser(String subjectUserId, String tenantDomain) throws IdentityOAuth2Exception { - AuthenticatedUser authenticatedUser; - - String username; try { - username = OAuth2Util.resolveUsernameFromUserId(tenantDomain, subjectUserId); - } catch (UserStoreException e) { + RealmService realmService = OAuthComponentServiceHolder.getInstance().getRealmService(); + + int tenantId = realmService.getTenantManager().getTenantId(tenantDomain); + User user = getUserFromTenant(subjectUserId, tenantId); + if (user == null) { + throw new IdentityOAuth2ClientException(OAuth2ErrorCodes.INVALID_REQUEST, + "Invalid User Id provided for Impersonation request. Unable to find the user for given " + + "user id : " + subjectUserId + " tenant Domain : " + tenantDomain); + } + AuthenticatedUser authenticatedUser = new AuthenticatedUser(); + authenticatedUser.setUserId(subjectUserId); + authenticatedUser.setAuthenticatedSubjectIdentifier(subjectUserId); + authenticatedUser.setUserName(user.getUserName()); + authenticatedUser.setUserStoreDomain(user.getUserStoreDomain()); + authenticatedUser.setTenantDomain(tenantDomain); + return authenticatedUser; + } catch (UserStoreException | IdentityOAuth2Exception e) { throw new IdentityOAuth2Exception(OAuth2ErrorCodes.INVALID_REQUEST, "Use mapped local subject is mandatory but a local user couldn't be found"); } - authenticatedUser = OAuth2Util.getUserFromUserName(username); - authenticatedUser.setUserId(subjectUserId); - authenticatedUser.setAuthenticatedSubjectIdentifier(subjectUserId); - return authenticatedUser; } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java index c1f54cc5447..c562585c652 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.java @@ -78,6 +78,7 @@ import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationConfigMgtServiceImpl; import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationMgtServiceImpl; import org.wso2.carbon.identity.oauth2.impersonation.validators.ImpersonationValidator; +import org.wso2.carbon.identity.oauth2.impersonation.validators.ImpersonatorPermissionValidator; import org.wso2.carbon.identity.oauth2.impersonation.validators.SubjectScopeValidator; import org.wso2.carbon.identity.oauth2.keyidprovider.DefaultKeyIDProviderImpl; import org.wso2.carbon.identity.oauth2.keyidprovider.KeyIDProvider; @@ -402,6 +403,7 @@ protected void activate(ComponentContext context) { OAuth2ServiceComponentHolder.getInstance().setImpersonationMgtService(new ImpersonationMgtServiceImpl()); bundleContext.registerService(ImpersonationValidator.class, new SubjectScopeValidator(), null); + bundleContext.registerService(ImpersonationValidator.class, new ImpersonatorPermissionValidator(), null); bundleContext.registerService(ImpersonationConfigMgtService.class, new ImpersonationConfigMgtServiceImpl(), null); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuthApplicationMgtListener.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuthApplicationMgtListener.java index 9827bae3418..35e2ace7d59 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuthApplicationMgtListener.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuthApplicationMgtListener.java @@ -506,7 +506,7 @@ private void clearCacheEntriesAgainstTokenByConsumerKey(Set acces String tenantDomain) throws IdentityOAuth2Exception { AppInfoCache appInfoCache = AppInfoCache.getInstance(); - appInfoCache.clearCacheEntry(consumerKey); + appInfoCache.clearCacheEntry(consumerKey, tenantDomain); OAuthCache.getInstance().clearCacheEntry(new OAuthCacheKey(consumerKey)); if (isNotEmpty(accessTokenDOSet)) { for (AccessTokenDO accessTokenDo : accessTokenDOSet) { @@ -517,7 +517,10 @@ private void clearCacheEntriesAgainstTokenByConsumerKey(Set acces OAuthCacheKey oauthCacheKey = new OAuthCacheKey(accessTokenDo.getAccessToken()); CacheEntry oauthCacheEntry = OAuthCache.getInstance().getValueFromCache(oauthCacheKey); if (oauthCacheEntry != null) { + OAuthCache.getInstance().clearCacheEntry(oauthCacheKey); OAuthCache.getInstance().clearCacheEntry(oauthCacheKey, tenantDomain); + OAuthCache.getInstance().clearCacheEntry(oauthCacheKey, accessTokenDo.getAuthzUser() + .getTenantDomain()); } String tokenBindingReference = "NONE"; if (accessTokenDo.getTokenBinding() != null && StringUtils diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java index ee5054ee999..1372f45ce02 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/AccessTokenIssuer.java @@ -102,6 +102,8 @@ import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.IMPERSONATING_ACTOR; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.LogConstants.InputKeys.IMPERSONATOR; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OauthAppStates.APP_STATE_ACTIVE; import static org.wso2.carbon.identity.oauth2.OAuth2Constants.MAX_ALLOWED_LENGTH; import static org.wso2.carbon.identity.oauth2.Oauth2ScopeConstants.CONSOLE_SCOPE_PREFIX; @@ -198,7 +200,6 @@ public OAuth2AccessTokenRespDTO issue(OAuth2AccessTokenReqDTO tokenReqDTO) } } - triggerPreListeners(tokenReqDTO, tokReqMsgCtx, isRefreshRequest); OAuthClientAuthnContext oAuthClientAuthnContext = tokenReqDTO.getoAuthClientAuthnContext(); @@ -544,8 +545,16 @@ private OAuth2AccessTokenRespDTO validateGrantAndIssueToken(OAuth2AccessTokenReq .inputParam(OAuthConstants.LogConstants.InputKeys.GRANT_TYPE, grantType) .inputParam("token expiry time (s)", tokenRespDTO.getExpiresIn()) .resultStatus(DiagnosticLog.ResultStatus.SUCCESS) - .resultMessage("Access token issued for the application.") .logDetailLevel(DiagnosticLog.LogDetailLevel.APPLICATION); + if (tokReqMsgCtx.isImpersonationRequest()) { + if (tokReqMsgCtx.getProperty(IMPERSONATING_ACTOR) != null) { + String impersonatorId = tokReqMsgCtx.getProperty(IMPERSONATING_ACTOR).toString(); + diagnosticLogBuilder.inputParam(IMPERSONATOR, impersonatorId); + } + diagnosticLogBuilder.resultMessage("Impersonated Access token issued for the application."); + } else { + diagnosticLogBuilder.resultMessage("Access token issued for the application."); + } if (tokReqMsgCtx.getAuthorizedUser() != null) { diagnosticLogBuilder.inputParam(LogConstants.InputKeys.USER_ID, tokReqMsgCtx.getAuthorizedUser().getUserId()); @@ -627,6 +636,8 @@ private OAuth2AccessTokenRespDTO validateGrantAndIssueToken(OAuth2AccessTokenReq addUserAttributesAgainstAccessTokenForPasswordGrant(tokenRespDTO, tokReqMsgCtx); } + persistCustomizedAccessTokenAttributesForRefreshToken(tokenRespDTO, tokReqMsgCtx); + if (GrantType.AUTHORIZATION_CODE.toString().equals(grantType)) { // Cache entry against the authorization code has no value beyond the token request. clearCacheEntryAgainstAuthorizationCode(getAuthorizationCode(tokenReqDTO)); @@ -815,7 +826,6 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I removeAuthorizedScopes(tokReqMsgCtx, authorizedScopes); } - boolean isDropUnregisteredScopes = OAuthServerConfiguration.getInstance().isDropUnregisteredScopes(); if (isDropUnregisteredScopes) { if (log.isDebugEnabled()) { @@ -867,6 +877,7 @@ private List getScopeList(String[] scopes) { } private ServiceProvider getServiceProvider(OAuth2AccessTokenReqDTO tokenReq) throws IdentityOAuth2Exception { + ServiceProvider serviceProvider; try { serviceProvider = OAuth2ServiceComponentHolder.getApplicationMgtService().getServiceProviderByClientId( @@ -941,6 +952,7 @@ private String getSubjectClaim(ServiceProvider serviceProvider, private String getDefaultSubject(ServiceProvider serviceProvider, AuthenticatedUser authenticatedUser) throws UserIdNotFoundException { + String subject; boolean useUserIdForDefaultSubject = false; ServiceProviderProperty[] spProperties = serviceProvider.getSpProperties(); @@ -1165,8 +1177,8 @@ private void handleTokenBinding(OAuth2AccessTokenReqDTO tokenReqDTO, String gran if (OAuth2Constants.TokenBinderType.CLIENT_REQUEST.equals(tokenBinder.getBindingType()) && tokenBindingValueOptional.get().length() >= MAX_ALLOWED_LENGTH) { - throw new IdentityOAuth2ClientException(OAuth2ErrorCodes.INVALID_REQUEST, - "Token binding reference length exceeds limit"); + throw new IdentityOAuth2ClientException(OAuth2ErrorCodes.INVALID_REQUEST, + "Token binding reference length exceeds limit"); } String tokenBindingValue = tokenBindingValueOptional.get(); @@ -1260,7 +1272,7 @@ private void cacheUserAttributesAgainstAccessToken(AuthorizationGrantCacheEntry } private void addUserAttributesAgainstAccessTokenForPasswordGrant(OAuth2AccessTokenRespDTO tokenRespDTO, - OAuthTokenReqMessageContext tokReqMsgCtx) { + OAuthTokenReqMessageContext tokReqMsgCtx) { if (tokReqMsgCtx.getAuthorizedUser() != null) { AuthorizationGrantCacheKey newCacheKey = new AuthorizationGrantCacheKey(tokenRespDTO.getAccessToken()); @@ -1274,6 +1286,35 @@ private void addUserAttributesAgainstAccessTokenForPasswordGrant(OAuth2AccessTok } } + private void persistCustomizedAccessTokenAttributesForRefreshToken(OAuth2AccessTokenRespDTO tokenRespDTO, + OAuthTokenReqMessageContext tokReqMsgCtx) { + + /* + If pre issue access token actions are executed it may have done modifications to the audience list, claims, + incorporated to the access token which are not persisted in the access token table. + If so, persist those custom modifications against the token id in the transaction session store + to populate the authorized access token context back at refresh token flow. + */ + if (tokReqMsgCtx.isPreIssueAccessTokenActionsExecuted()) { + AuthorizationGrantCacheKey newCacheKey = new AuthorizationGrantCacheKey(tokenRespDTO.getTokenId()); + AuthorizationGrantCacheEntry authorizationGrantCacheEntry = + new AuthorizationGrantCacheEntry(); + authorizationGrantCacheEntry.setTokenId(tokenRespDTO.getTokenId()); + authorizationGrantCacheEntry.setPreIssueAccessTokenActionsExecuted( + tokReqMsgCtx.isPreIssueAccessTokenActionsExecuted()); + authorizationGrantCacheEntry.setAudiences(tokReqMsgCtx.getAudiences()); + authorizationGrantCacheEntry.setCustomClaims(tokReqMsgCtx.getAdditionalAccessTokenClaims()); + + authorizationGrantCacheEntry.setValidityPeriod( + TimeUnit.MILLISECONDS.toNanos(tokReqMsgCtx.getRefreshTokenvalidityPeriod())); + AuthorizationGrantCache.getInstance().addToCacheByToken(newCacheKey, authorizationGrantCacheEntry); + + log.debug("Customized audience list and access token attributes from pre issue access token actions " + + "are persisted in the AuthorizationGrantCache against the token id: " + + tokenRespDTO.getTokenId()); + } + } + private void clearCacheEntryAgainstAuthorizationCode(String authorizationCode) { AuthorizationGrantCacheKey oldCacheKey = new AuthorizationGrantCacheKey(authorizationCode); @@ -1380,7 +1421,7 @@ private static boolean isNotActiveState(String appState) { * provide the correct user store manager from the user realm. * * @param tenantDomain The tenant domain of the authenticated user. - * @param userId The ID of the authenticated user. + * @param userId The ID of the authenticated user. * @return User store manager of the user reside organization. */ private Optional getUserStoreManagerFromRealmOfUserResideOrganization(String tenantDomain, 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 b0ff6a1ba1a..45fba7b340b 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 @@ -167,7 +167,7 @@ private JWTClaimsSet createSubjectTokenJWTClaimSet(OAuthAuthzReqMessageContext o long curTimeInMillis = Calendar.getInstance().getTimeInMillis(); AuthenticatedUser authenticatedUser = getAuthenticatedUser(oauthAuthzMsgCtx, null); - String sub = getSubjectClaim(consumerKey, spTenantDomain, authenticatedUser); + String sub = authenticatedUser.getAuthenticatedSubjectIdentifier(); String subject = oauthAuthzMsgCtx.getAuthorizationReqDTO().getRequestedSubjectId(); @@ -244,8 +244,12 @@ public String getAccessTokenHash(String accessToken) throws OAuthSystemException try { JWT parsedJwtToken = JWTParser.parse(accessToken); + // JWT ClaimsSet can be null if the ID token is encrypted. + if (parsedJwtToken.getJWTClaimsSet() == null) { + throw new OAuthSystemException("JWT claims set is null in the JWT token."); + } String jwtId = parsedJwtToken.getJWTClaimsSet().getJWTID(); - if (jwtId == null) { + if (StringUtils.isBlank(jwtId)) { throw new OAuthSystemException("JTI could not be retrieved from the JWT token."); } return jwtId; @@ -289,6 +293,7 @@ protected String buildJWTToken(OAuthTokenReqMessageContext request) throws Ident } jwtClaimsSet = jwtClaimsSetBuilder.build(); + if (JWSAlgorithm.NONE.getName().equals(signatureAlgorithm.getName())) { return new PlainJWT(jwtClaimsSet).serialize(); } @@ -587,8 +592,8 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe long curTimeInMillis = Calendar.getInstance().getTimeInMillis(); AuthenticatedUser authenticatedUser = getAuthenticatedUser(authAuthzReqMessageContext, tokenReqMessageContext); - String sub = getSubjectClaim(consumerKey, spTenantDomain, authenticatedUser); - if (checkPairwiseSubEnabledForAccessTokens()) { + String sub = authenticatedUser.getAuthenticatedSubjectIdentifier(); + if (OAuth2Util.isPairwiseSubEnabledForAccessTokens()) { // pairwise sub claim is returned only if pairwise subject identifier for access tokens is enabled. sub = OIDCClaimUtil.getSubjectClaim(sub, oAuthAppDO); } @@ -611,14 +616,14 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe jwtClaimsSetBuilder.claim(OAuthConstants.AUTHORIZED_USER_TYPE, getAuthorizedUserType(authAuthzReqMessageContext, tokenReqMessageContext)); - jwtClaimsSetBuilder.expirationTime(calculateAccessTokenExpiryTime(accessTokenLifeTimeInMillis, curTimeInMillis)); - // This is a spec (openid-connect-core-1_0:2.0) requirement for ID tokens. But we are keeping this in JWT - // as well. - List audience = OAuth2Util.getOIDCAudience(consumerKey, oAuthAppDO); - jwtClaimsSetBuilder.audience(audience); + // This is a spec (openid-connect-core-1_0:2.0) requirement for ID tokens. + // But we are keeping this in JWT as well. + jwtClaimsSetBuilder.audience(tokenReqMessageContext != null ? tokenReqMessageContext.getAudiences() : + OAuth2Util.getOIDCAudience(consumerKey, oAuthAppDO)); + JWTClaimsSet jwtClaimsSet; // Handle custom claims @@ -628,6 +633,7 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe jwtClaimsSet = handleCustomClaims(jwtClaimsSetBuilder, tokenReqMessageContext); } + // todo: deprecate when pre issue access token action is ready if (tokenReqMessageContext != null && tokenReqMessageContext.getOauth2AccessTokenReqDTO() != null && tokenReqMessageContext.getOauth2AccessTokenReqDTO().getAccessTokenExtendedAttributes() != null) { Map customClaims = @@ -639,6 +645,7 @@ protected JWTClaimsSet createJWTClaimSet(OAuthAuthzReqMessageContext authAuthzRe } } } + // Include token binding. jwtClaimsSet = handleTokenBinding(jwtClaimsSetBuilder, tokenReqMessageContext); @@ -703,12 +710,6 @@ private JWTClaimsSet handleCnf(JWTClaimsSet.Builder jwtClaimsSetBuilder, return jwtClaimsSetBuilder.build(); } - private String getSubjectClaim(String clientId, String spTenantDomain, AuthenticatedUser authorizedUser) - throws IdentityOAuth2Exception { - - return authorizedUser.getAuthenticatedSubjectIdentifier(); - } - /** * Get authentication request object from message context. * @@ -800,7 +801,17 @@ protected long getAccessTokenLifeTimeInMillis(OAuthAuthzReqMessageContext authzR protected long getAccessTokenLifeTimeInMillis(OAuthTokenReqMessageContext tokenReqMessageContext, OAuthAppDO oAuthAppDO, String consumerKey) throws IdentityOAuth2Exception { + long lifetimeInMillis; + + if (tokenReqMessageContext.isPreIssueAccessTokenActionsExecuted()) { + lifetimeInMillis = tokenReqMessageContext.getValidityPeriod(); + log.debug("Access token life time is set from OAuthTokenReqMessageContext. Token Lifetime : " + + lifetimeInMillis + "ms."); + + return lifetimeInMillis; + } + boolean isUserAccessTokenType = isUserAccessTokenType(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), tokenReqMessageContext); @@ -853,6 +864,22 @@ protected JWTClaimsSet handleCustomClaims(JWTClaimsSet.Builder jwtClaimsSetBuild OAuthTokenReqMessageContext tokenReqMessageContext) throws IdentityOAuth2Exception { + if (tokenReqMessageContext != null && tokenReqMessageContext.isPreIssueAccessTokenActionsExecuted()) { + Map customClaims = tokenReqMessageContext.getAdditionalAccessTokenClaims(); + + if (customClaims != null) { + if (log.isDebugEnabled()) { + log.debug("Pre issue access token actions are executed. " + + "Returning the customized claim set from actions. Claims: " + + customClaims.keySet()); + } + + customClaims.forEach(jwtClaimsSetBuilder::claim); + } + + return jwtClaimsSetBuilder.build(); + } + if (tokenReqMessageContext != null && tokenReqMessageContext.getOauth2AccessTokenReqDTO() != null && StringUtils.equals(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), @@ -970,16 +997,6 @@ private JWTClaimsSet setSignerRealm(String tenantDomain, JWTClaimsSet jwtClaimsS return jwtClaimsSet; } - /** - * Check whether pairwise subject identifier is enabled for access token response. - * - * @return true if pairwise subject identifier is enabled for access token response. - */ - private boolean checkPairwiseSubEnabledForAccessTokens() { - - return Boolean.parseBoolean(IdentityUtil.getProperty(ENABLE_PPID_FOR_ACCESS_TOKENS)); - } - /** * Set entity_id claim to the JWT if token persistence is disabled. This is to identify the principal subject of the * issuing token. diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java index 693b7fe8abf..5552c809f08 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/OAuthTokenReqMessageContext.java @@ -24,6 +24,8 @@ import org.wso2.carbon.identity.oauth2.rar.model.AuthorizationDetails; import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; +import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -57,6 +59,12 @@ public class OAuthTokenReqMessageContext { private boolean isImpersonationRequest; + private boolean preIssueAccessTokenActionsExecuted; + + private List audiences; + + private Map additionalAccessTokenClaims; + private AuthorizationDetails authorizationDetails; public OAuthTokenReqMessageContext(OAuth2AccessTokenReqDTO oauth2AccessTokenReqDTO) { @@ -99,11 +107,19 @@ public void setTenantID(int tenantID) { this.tenantID = tenantID; } + /** + * Get the validity period of the token. + * @return validity period of the token in milliseconds + */ public long getValidityPeriod() { return validityPeriod; } + /** + * Set the validity period of the token. + * @param validityPeriod validity period of the token in milliseconds + */ public void setValidityPeriod(long validityPeriod) { this.validityPeriod = validityPeriod; @@ -160,10 +176,12 @@ public void setTokenBinding(TokenBinding tokenBinding) { } public String[] getAuthorizedInternalScopes() { + return authorizedInternalScopes; } public void setAuthorizedInternalScopes(String[] authorizedInternalScopes) { + this.authorizedInternalScopes = authorizedInternalScopes; } @@ -187,6 +205,36 @@ public void setImpersonationRequest(boolean impersonationRequest) { isImpersonationRequest = impersonationRequest; } + public boolean isPreIssueAccessTokenActionsExecuted() { + + return preIssueAccessTokenActionsExecuted; + } + + public void setPreIssueAccessTokenActionsExecuted(boolean preIssueAccessTokenActionsExecuted) { + + this.preIssueAccessTokenActionsExecuted = preIssueAccessTokenActionsExecuted; + } + + public List getAudiences() { + + return audiences; + } + + public void setAudiences(List audiences) { + + this.audiences = audiences; + } + + public Map getAdditionalAccessTokenClaims() { + + return additionalAccessTokenClaims; + } + + public void setAdditionalAccessTokenClaims(Map additionalAccessTokenClaims) { + + this.additionalAccessTokenClaims = additionalAccessTokenClaims; + } + /** * Retrieves the user consented or authorized authorization details. * diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuer.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuer.java index 808837960ab..f725a63e3a8 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuer.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuer.java @@ -20,8 +20,13 @@ package org.wso2.carbon.identity.oauth2.token; import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +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.oauth.common.OAuthConstants; 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.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationContext; @@ -29,7 +34,9 @@ import org.wso2.carbon.identity.oauth2.impersonation.services.ImpersonationMgtService; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.model.SubjectTokenDO; +import org.wso2.carbon.utils.DiagnosticLog; +import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.SUBJECT_TOKEN_EXPIRY_TIME_VALUE; import static org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration.JWT_TOKEN_TYPE; /** @@ -38,6 +45,8 @@ */ public class SubjectTokenIssuer { + private static final String OAUTH_APP_DO = "OAuthAppDO"; + /** * Issues a subject token for the given OAuth authorization request message context. * @@ -75,6 +84,39 @@ public SubjectTokenDO issue(OAuthAuthzReqMessageContext oauthAuthzMsgCtx) throws .get(JWT_TOKEN_TYPE); SubjectTokenDO subjectTokenDO = new SubjectTokenDO(); subjectTokenDO.setSubjectToken(oauthTokenIssuer.issueSubjectToken(oauthAuthzMsgCtx)); + OAuthAppDO oAuthAppDO = (OAuthAppDO) oauthAuthzMsgCtx.getProperty(OAUTH_APP_DO); + int subjectTokenLifeTime = oAuthAppDO.getSubjectTokenExpiryTime() <= 0 ? SUBJECT_TOKEN_EXPIRY_TIME_VALUE : + oAuthAppDO.getSubjectTokenExpiryTime(); + + if (LoggerUtils.isDiagnosticLogsEnabled()) { + DiagnosticLog.DiagnosticLogBuilder diagnosticLogBuilder = new DiagnosticLog.DiagnosticLogBuilder( + OAuthConstants.LogConstants.OAUTH_INBOUND_SERVICE, + OAuthConstants.LogConstants.ActionIDs.ISSUE_SUBJECT_TOKEN); + diagnosticLogBuilder.inputParam(LogConstants.InputKeys.CLIENT_ID, + oauthAuthzMsgCtx.getAuthorizationReqDTO().getConsumerKey()) + .inputParam(OAuthConstants.LogConstants.InputKeys.AUTHORIZED_SCOPES, + oauthAuthzMsgCtx.getApprovedScope()) + .inputParam(OAuthConstants.LogConstants.InputKeys.RESPONSE_TYPE, + oauthAuthzMsgCtx.getAuthorizationReqDTO().getResponseType()) + .inputParam("token expiry time (s)", subjectTokenLifeTime) + .resultStatus(DiagnosticLog.ResultStatus.SUCCESS) + .resultMessage("Subject token issued for the application.") + .logDetailLevel(DiagnosticLog.LogDetailLevel.APPLICATION); + + AuthenticatedUser impersonator = oauthAuthzMsgCtx.getAuthorizationReqDTO().getUser(); + if (impersonator != null) { + try { + diagnosticLogBuilder.inputParam(LogConstants.InputKeys.USER_ID, impersonator.getUserId()); + } catch (UserIdNotFoundException e) { + if (StringUtils.isNotBlank(impersonator.getAuthenticatedSubjectIdentifier())) { + diagnosticLogBuilder.inputParam(LogConstants.InputKeys.USER, LoggerUtils.isLogMaskingEnable ? + LoggerUtils.getMaskedContent(impersonator.getAuthenticatedSubjectIdentifier()) : + impersonator.getAuthenticatedSubjectIdentifier()); + } + } + } + LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder); + } return subjectTokenDO; } 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 1ab7b78a5cf..383e4c3fb4a 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 @@ -26,6 +26,9 @@ import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionType; import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.base.IdentityConstants; @@ -71,12 +74,17 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; 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.util.OAuth2Util.EXTENDED_REFRESH_TOKEN_DEFAULT_TIME; /** @@ -277,7 +285,7 @@ public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws Id Set scopeHandlers = OAuthServerConfiguration.getInstance().getOAuth2ScopeHandlers(); boolean isValid = true; - for (OAuth2ScopeHandler scopeHandler: scopeHandlers) { + for (OAuth2ScopeHandler scopeHandler : scopeHandlers) { if (scopeHandler != null && scopeHandler.canHandle(tokReqMsgCtx)) { isValid = scopeHandler.validateScope(tokReqMsgCtx); if (log.isDebugEnabled()) { @@ -325,6 +333,7 @@ public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws Id @Override public boolean authorizeAccessDelegation(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { + OAuthCallback authzCallback = new OAuthCallback(tokReqMsgCtx.getAuthorizedUser(), tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(), OAuthCallback.OAuthCallbackType.ACCESS_DELEGATION_TOKEN); @@ -362,7 +371,7 @@ protected void storeAccessToken(OAuth2AccessTokenReqDTO oAuth2AccessTokenReqDTO, try { OAuthTokenPersistenceFactory.getInstance().getAccessTokenDAO() .insertAccessToken(newAccessToken, oAuth2AccessTokenReqDTO.getClientId(), - newTokenBean, existingTokenBean, userStoreDomain); + newTokenBean, existingTokenBean, userStoreDomain); } catch (IdentityException e) { String maskedToken = LoggerUtils.isLogMaskingEnable ? LoggerUtils.getMaskedContent(newAccessToken) : newAccessToken; @@ -439,12 +448,10 @@ private OAuth2AccessTokenRespDTO generateNewAccessToken(OAuthTokenReqMessageCont OauthTokenIssuer oauthTokenIssuer) throws IdentityOAuth2Exception { - OAuthAppDO oAuthAppBean = getoAuthApp(consumerKey); Timestamp timestamp = new Timestamp(new Date().getTime()); - long validityPeriodInMillis = getConfiguredExpiryTimeForApplication(tokReqMsgCtx, consumerKey, oAuthAppBean); - AccessTokenDO newTokenBean = createNewTokenBean(tokReqMsgCtx, oAuthAppBean, existingTokenBean, timestamp, - validityPeriodInMillis, oauthTokenIssuer); - setDetailsToMessageContext(tokReqMsgCtx, validityPeriodInMillis, newTokenBean, timestamp); + updateMessageContextToCreateNewToken(tokReqMsgCtx, consumerKey, existingTokenBean, timestamp); + executePreIssueAccessTokenActions(tokReqMsgCtx); + AccessTokenDO newTokenBean = createNewTokenBean(tokReqMsgCtx, existingTokenBean, oauthTokenIssuer); /* Check whether the existing token needs to be expired and send the corresponding parameters to the persistAccessTokenInDB method. */ @@ -461,7 +468,50 @@ private OAuth2AccessTokenRespDTO generateNewAccessToken(OAuthTokenReqMessageCont // Update cache with newly added token. updateCacheIfEnabled(newTokenBean, OAuth2Util.buildScopeString(tokReqMsgCtx.getScope()), oauthTokenIssuer); - return createResponseWithTokenBean(newTokenBean, validityPeriodInMillis, scope); + return createResponseWithTokenBean(newTokenBean, newTokenBean.getValidityPeriodInMillis(), scope); + } + + private void executePreIssueAccessTokenActions(OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (checkExecutePreIssueAccessTokensActions(tokenReqMessageContext)) { + + Map additionalProperties = new HashMap<>(); + Consumer> mapInitializer = + map -> map.put("tokenMessageContext", tokenReqMessageContext); + mapInitializer.accept(additionalProperties); + + try { + ActionExecutionStatus executionStatus = + OAuthComponentServiceHolder.getInstance().getActionExecutorService() + .execute(ActionType.PRE_ISSUE_ACCESS_TOKEN, additionalProperties, + IdentityTenantUtil.getTenantDomain(IdentityTenantUtil.getLoginTenantId())); + if (log.isDebugEnabled()) { + log.debug(String.format( + "Invoked pre issue access token action for clientID: %s grant types: %s. Status: %s", + tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(), + tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), + Optional.ofNullable(executionStatus).isPresent() ? executionStatus.getStatus() : "NA")); + } + } catch (ActionExecutionException e) { + // If error ignore and proceed + log.error("Error while executing pre issue access token action", e); + } + } + } + + private boolean checkExecutePreIssueAccessTokensActions(OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + OAuthAppDO oAuthAppBean = getoAuthApp(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId()); + String grantType = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(); + + // Allow for following grant types and for JWT access tokens only. + return (OAuthConstants.GrantTypes.AUTHORIZATION_CODE.equals(grantType) || + OAuthConstants.GrantTypes.CLIENT_CREDENTIALS.equals(grantType) || + OAuthConstants.GrantTypes.PASSWORD.equals(grantType) || + OAuthConstants.GrantTypes.REFRESH_TOKEN.equals(grantType)) && + JWT_TOKEN_TYPE.equals(oAuthAppBean.getTokenType()); } private boolean isExistingTokenValid(AccessTokenDO existingTokenBean, long expireTime) { @@ -482,10 +532,11 @@ private boolean isExistingTokenValid(AccessTokenDO existingTokenBean, long expir return false; } - private AccessTokenDO createNewTokenBean(OAuthTokenReqMessageContext tokReqMsgCtx, OAuthAppDO oAuthAppBean, - AccessTokenDO existingTokenBean, Timestamp timestamp, long validityPeriodInMillis, - OauthTokenIssuer oauthTokenIssuer) throws IdentityOAuth2Exception { + private AccessTokenDO createNewTokenBean(OAuthTokenReqMessageContext tokReqMsgCtx, AccessTokenDO existingTokenBean, + OauthTokenIssuer oauthTokenIssuer) throws IdentityOAuth2Exception { + String tenantDomain = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getTenantDomain(); + OAuth2AccessTokenReqDTO tokenReq = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); validateGrantTypeParam(tokenReq); @@ -498,49 +549,63 @@ private AccessTokenDO createNewTokenBean(OAuthTokenReqMessageContext tokReqMsgCt newTokenBean.setTokenId(UUID.randomUUID().toString()); newTokenBean.setGrantType(tokenReq.getGrantType()); newTokenBean.setAppResidentTenantId(IdentityTenantUtil.getLoginTenantId()); + newTokenBean.setIsConsentedToken(tokReqMsgCtx.isConsentedToken()); + newTokenBean.setTokenType(getTokenType(tokReqMsgCtx)); + newTokenBean.setIssuedTime(new Timestamp(tokReqMsgCtx.getAccessTokenIssuedTime())); + newTokenBean.setAccessToken(getNewAccessToken(tokReqMsgCtx, oauthTokenIssuer)); + newTokenBean.setValidityPeriodInMillis(tokReqMsgCtx.getValidityPeriod()); + newTokenBean.setValidityPeriod(tokReqMsgCtx.getValidityPeriod() / SECONDS_TO_MILISECONDS_FACTOR); + newTokenBean.setTokenBinding(tokReqMsgCtx.getTokenBinding()); + newTokenBean.setAccessTokenExtendedAttributes(tokenReq.getAccessTokenExtendedAttributes()); + setRefreshTokenDetails(tokReqMsgCtx, existingTokenBean, newTokenBean, oauthTokenIssuer); + + return newTokenBean; + } + + private void updateMessageContextToCreateNewToken(OAuthTokenReqMessageContext tokReqMsgCtx, String consumerKey, + AccessTokenDO existingTokenBean, Timestamp timestamp) + throws IdentityOAuth2Exception { + /* If the existing token is available, the consented token flag will be extracted from that. Otherwise, from the current grant. */ if (OAuth2ServiceComponentHolder.isConsentedTokenColumnEnabled()) { if (existingTokenBean != null) { - newTokenBean.setIsConsentedToken(existingTokenBean.isConsentedToken()); + tokReqMsgCtx.setConsentedToken(existingTokenBean.isConsentedToken()); } else { - if (OIDCClaimUtil.isConsentBasedClaimFilteringApplicable(tokenReq.getGrantType())) { - newTokenBean.setIsConsentedToken(true); + if (OIDCClaimUtil.isConsentBasedClaimFilteringApplicable( + tokReqMsgCtx.getOauth2AccessTokenReqDTO().getGrantType())) { + tokReqMsgCtx.setConsentedToken(true); } } - tokReqMsgCtx.setConsentedToken(newTokenBean.isConsentedToken()); } - newTokenBean.setTokenType(getTokenType(tokReqMsgCtx)); - newTokenBean.setIssuedTime(timestamp); - newTokenBean.setAccessToken(getNewAccessToken(tokReqMsgCtx, oauthTokenIssuer)); - newTokenBean.setValidityPeriodInMillis(validityPeriodInMillis); - newTokenBean.setValidityPeriod(validityPeriodInMillis / SECONDS_TO_MILISECONDS_FACTOR); - newTokenBean.setTokenBinding(tokReqMsgCtx.getTokenBinding()); - newTokenBean.setAccessTokenExtendedAttributes(tokenReq.getAccessTokenExtendedAttributes()); - setRefreshTokenDetails(tokReqMsgCtx, oAuthAppBean, existingTokenBean, timestamp, validityPeriodInMillis, - tokenReq, newTokenBean, oauthTokenIssuer); - return newTokenBean; + OAuthAppDO oAuthAppBean = getoAuthApp(consumerKey); + long validityPeriodInMillis = getConfiguredExpiryTimeForApplication(tokReqMsgCtx, consumerKey, oAuthAppBean); + tokReqMsgCtx.setValidityPeriod(validityPeriodInMillis); + tokReqMsgCtx.setAccessTokenIssuedTime(timestamp.getTime()); + tokReqMsgCtx.setAudiences(OAuth2Util.getOIDCAudience(consumerKey, oAuthAppBean)); + + updateRefreshTokenValidityPeriodInMessageContext(oAuthAppBean, tokReqMsgCtx); } - private void setRefreshTokenDetails(OAuthTokenReqMessageContext tokReqMsgCtx, OAuthAppDO oAuthAppBean, - AccessTokenDO existingTokenBean, Timestamp timestamp, long validityPeriodInMillis, - OAuth2AccessTokenReqDTO tokenReq, AccessTokenDO newTokenBean, OauthTokenIssuer oauthTokenIssuer) + private void setRefreshTokenDetails(OAuthTokenReqMessageContext tokReqMsgCtx, AccessTokenDO existingTokenBean, + AccessTokenDO newTokenBean, OauthTokenIssuer oauthTokenIssuer) throws IdentityOAuth2Exception { - boolean isExtendedToken = newTokenBean.getAccessTokenExtendedAttributes() != null && - newTokenBean.getAccessTokenExtendedAttributes().getRefreshTokenValidityPeriod() > + OAuth2AccessTokenReqDTO tokenReq = tokReqMsgCtx.getOauth2AccessTokenReqDTO(); + boolean isExtendedToken = tokenReq.getAccessTokenExtendedAttributes() != null && + tokenReq.getAccessTokenExtendedAttributes().getRefreshTokenValidityPeriod() > EXTENDED_REFRESH_TOKEN_DEFAULT_TIME; /* Check whether the token renewal per request configuration is configured and the validation of the refresh token. If the token renewal per request configuration is enabled, renew the refresh token as well. */ - if (!isTokenRenewalPerRequestConfigured() && isRefreshTokenValid(existingTokenBean, validityPeriodInMillis, - tokenReq.getClientId()) && !isExtendedToken) { + if (!isTokenRenewalPerRequestConfigured() && + isRefreshTokenValid(existingTokenBean, tokReqMsgCtx.getValidityPeriod(), + tokenReq.getClientId()) && !isExtendedToken) { setRefreshTokenDetailsFromExistingToken(existingTokenBean, newTokenBean); } else { // no valid refresh token found in existing Token - newTokenBean.setRefreshTokenIssuedTime(timestamp); + newTokenBean.setRefreshTokenIssuedTime(new Timestamp(tokReqMsgCtx.getAccessTokenIssuedTime())); // Set refresh token validity period. - newTokenBean.setRefreshTokenValidityPeriodInMillis( - getRefreshTokenValidityPeriod(tokenReq.getClientId(), oAuthAppBean, tokReqMsgCtx)); + newTokenBean.setRefreshTokenValidityPeriodInMillis(tokReqMsgCtx.getRefreshTokenvalidityPeriod()); newTokenBean.setRefreshToken(getRefreshToken(tokReqMsgCtx, oauthTokenIssuer)); } } @@ -628,23 +693,6 @@ private void updateCacheIfEnabled(AccessTokenDO newTokenBean, String scope, Oaut } } - private void setDetailsToMessageContext(OAuthTokenReqMessageContext tokReqMsgCtx, long validityPeriodInMillis, - AccessTokenDO newTokenBean, Timestamp timestamp) { - // set the validity period. this is needed by downstream handlers. - // if this is set before - then this will override it by the calculated new value. - tokReqMsgCtx.setValidityPeriod(validityPeriodInMillis); - - // set the refresh token validity period. this is needed by downstream handlers. - // if this is set before - then this will override it by the calculated new value. - tokReqMsgCtx.setRefreshTokenvalidityPeriod(newTokenBean.getRefreshTokenValidityPeriodInMillis()); - - // set access token issued time.this is needed by downstream handlers. - tokReqMsgCtx.setAccessTokenIssuedTime(timestamp.getTime()); - - // set refresh token issued time.this is needed by downstream handlers. - tokReqMsgCtx.setRefreshTokenIssuedTime(newTokenBean.getRefreshTokenIssuedTime().getTime()); - } - private String getNewAccessToken(OAuthTokenReqMessageContext tokReqMsgCtx, OauthTokenIssuer oauthTokenIssuer) throws IdentityOAuth2Exception { try { @@ -690,15 +738,14 @@ private void validateGrantTypeParam(OAuth2AccessTokenReqDTO tokenReq) throws Ide } } - private long getRefreshTokenValidityPeriod(String consumerKey, OAuthAppDO oAuthAppBean, - OAuthTokenReqMessageContext tokReqMsgCtx) { + private void updateRefreshTokenValidityPeriodInMessageContext(OAuthAppDO oAuthAppBean, + OAuthTokenReqMessageContext tokReqMsgCtx) { + long refreshTokenValidityPeriodInMillis; long validityPeriodFromMsgContext = tokReqMsgCtx.getRefreshTokenvalidityPeriod(); - if (validityPeriodFromMsgContext != OAuthConstants.UNASSIGNED_VALIDITY_PERIOD - && validityPeriodFromMsgContext > 0) { - refreshTokenValidityPeriodInMillis = validityPeriodFromMsgContext * - SECONDS_TO_MILISECONDS_FACTOR; + if (validityPeriodFromMsgContext > 0) { + refreshTokenValidityPeriodInMillis = validityPeriodFromMsgContext * SECONDS_TO_MILISECONDS_FACTOR; if (log.isDebugEnabled()) { log.debug("OAuth application id : " + oAuthAppBean.getOauthConsumerKey() + ", using refresh token " + "validity period configured from OAuthTokenReqMessageContext: " + @@ -708,14 +755,15 @@ private long getRefreshTokenValidityPeriod(String consumerKey, OAuthAppDO oAuthA refreshTokenValidityPeriodInMillis = oAuthAppBean.getRefreshTokenExpiryTime() * SECONDS_TO_MILISECONDS_FACTOR; if (log.isDebugEnabled()) { - log.debug("OAuth application id : " + consumerKey + ", refresh token validity time " + - refreshTokenValidityPeriodInMillis + "ms"); + log.debug("OAuth application id : " + oAuthAppBean.getOauthConsumerKey() + + ", refresh token validity time " + refreshTokenValidityPeriodInMillis + "ms"); } } else { refreshTokenValidityPeriodInMillis = OAuthServerConfiguration.getInstance() .getRefreshTokenValidityPeriodInSeconds() * SECONDS_TO_MILISECONDS_FACTOR; } - return refreshTokenValidityPeriodInMillis; + + tokReqMsgCtx.setRefreshTokenvalidityPeriod(refreshTokenValidityPeriodInMillis); } private void addTokenToCache(OAuthCacheKey cacheKey, AccessTokenDO existingAccessTokenDO) { @@ -752,7 +800,7 @@ private OAuth2AccessTokenRespDTO createResponseWithTokenBean(AccessTokenDO exist if (issueRefreshToken(existingAccessTokenDO.getTokenType()) && OAuthServerConfiguration.getInstance().getSupportedGrantTypes().containsKey( - GrantType.REFRESH_TOKEN.toString())) { + GrantType.REFRESH_TOKEN.toString())) { String grantTypes = oAuthAppDO.getGrantTypes(); List supportedGrantTypes = new ArrayList<>(); if (StringUtils.isNotEmpty(grantTypes)) { 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 ccebaa26503..a2e2edd2a57 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 @@ -24,8 +24,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionType; import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException; import org.wso2.carbon.identity.base.IdentityConstants; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache; import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheEntry; @@ -37,6 +41,7 @@ import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; import org.wso2.carbon.identity.oauth.tokenprocessor.RefreshTokenGrantProcessor; import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; @@ -55,14 +60,18 @@ import java.sql.Timestamp; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN; import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenBindings.NONE; +import static org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration.JWT_TOKEN_TYPE; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.buildCacheKeyStringForTokenWithUserId; import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.buildCacheKeyStringForTokenWithUserIdOrgId; @@ -109,11 +118,13 @@ public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) // context property. RefreshTokenValidationDataDO validationBean = (RefreshTokenValidationDataDO) tokReqMsgCtx .getProperty(PREV_ACCESS_TOKEN); - if (isRefreshTokenExpired(validationBean)) { return handleError(OAuth2ErrorCodes.INVALID_GRANT, "Refresh token is expired.", tokenReq); } + tokReqMsgCtx.setValidityPeriod(validationBean.getAccessTokenValidityInMillis()); + + executePreIssueAccessTokenActions(validationBean, tokReqMsgCtx); AccessTokenDO accessTokenBean = getRefreshTokenGrantProcessor() .createAccessTokenBean(tokReqMsgCtx, tokenReq, validationBean, getTokenType(tokReqMsgCtx)); @@ -670,6 +681,70 @@ private static void addUserAttributesToCache(AccessTokenDO accessTokenBean, } } + private void executePreIssueAccessTokenActions(RefreshTokenValidationDataDO refreshTokenValidationDataDO, + OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (checkExecutePreIssueAccessTokensActions(refreshTokenValidationDataDO, tokenReqMessageContext)) { + + setCustomizedAccessTokenAttributesToMessageContext(refreshTokenValidationDataDO, tokenReqMessageContext); + + Map additionalProperties = new HashMap<>(); + Consumer> mapInitializer = + map -> map.put("tokenMessageContext", tokenReqMessageContext); + mapInitializer.accept(additionalProperties); + + try { + ActionExecutionStatus executionStatus = + OAuthComponentServiceHolder.getInstance().getActionExecutorService() + .execute(ActionType.PRE_ISSUE_ACCESS_TOKEN, additionalProperties, + IdentityTenantUtil.getTenantDomain(IdentityTenantUtil.getLoginTenantId())); + if (log.isDebugEnabled()) { + log.debug(String.format( + "Invoked pre issue access token action for clientID: %s grant types: %s. Status: %s", + tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(), + tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(), + Optional.ofNullable(executionStatus).isPresent() ? executionStatus.getStatus() : "NA")); + } + } catch (ActionExecutionException e) { + // If error ignore and proceed + log.error("Error while executing pre issue access token action", e); + } + } + } + + private boolean checkExecutePreIssueAccessTokensActions(RefreshTokenValidationDataDO refreshTokenValidationDataDO, + OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + OAuthAppDO oAuthAppBean = getOAuthApp(tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId()); + String grantType = refreshTokenValidationDataDO.getGrantType(); + + // Allow if refresh token is issued for token requests from following grant types and, + // for JWT access tokens only. + return (OAuthConstants.GrantTypes.AUTHORIZATION_CODE.equals(grantType) || + OAuthConstants.GrantTypes.PASSWORD.equals(grantType)) && + JWT_TOKEN_TYPE.equals(oAuthAppBean.getTokenType()); + } + + private void setCustomizedAccessTokenAttributesToMessageContext(RefreshTokenValidationDataDO refreshTokenData, + OAuthTokenReqMessageContext tokenRequestContext) { + + AuthorizationGrantCacheKey grantCacheKey = new AuthorizationGrantCacheKey(refreshTokenData.getTokenId()); + AuthorizationGrantCacheEntry grantCacheEntry = AuthorizationGrantCache.getInstance() + .getValueFromCacheByTokenId(grantCacheKey, refreshTokenData.getTokenId()); + + if (grantCacheEntry != null && grantCacheEntry.isPreIssueAccessTokenActionsExecuted()) { + tokenRequestContext.setPreIssueAccessTokenActionsExecuted(true); + tokenRequestContext.setAdditionalAccessTokenClaims(grantCacheEntry.getCustomClaims()); + tokenRequestContext.setAudiences(grantCacheEntry.getAudiences()); + log.debug("Updated OAuthTokenReqMessageContext with customized audience list and access token" + + " attributes in the AuthorizationGrantCache for token id: " + refreshTokenData.getTokenId()); + + AuthorizationGrantCache.getInstance().clearCacheEntryByToken(grantCacheKey); + } + } + private boolean isRenewRefreshToken(String renewRefreshToken) { if (StringUtils.isNotBlank(renewRefreshToken)) { 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 60214170577..873e4949893 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 @@ -334,6 +334,8 @@ public class OAuth2Util { */ public static final String FIDP_ROLE_BASED_AUTHZ_APP_CONFIG = "FIdPRoleBasedAuthzApplications.AppName"; + private static final String ENABLE_PPID_FOR_ACCESS_TOKENS = "OAuth.OpenIDConnect.EnablePairwiseSubForAccessToken"; + private static final String INBOUND_AUTH2_TYPE = "oauth2"; private static final Log log = LogFactory.getLog(OAuth2Util.class); private static final Log diagnosticLog = LogFactory.getLog("diagnostics"); @@ -4742,6 +4744,12 @@ public static boolean isValidTokenBinding(TokenBinding tokenBinding, HttpServlet return true; } + /* The request token binding type can't be validated, as it is an auto generated UUID to issue unique JWT tokens + by avoiding revocation of already issued JWT tokens. */ + if (OAuthConstants.REQUEST_BINDING_TYPE.equals(tokenBinding.getBindingType())) { + return true; + } + Optional tokenBinderOptional = OAuth2ServiceComponentHolder.getInstance() .getTokenBinder(tokenBinding.getBindingType()); if (!tokenBinderOptional.isPresent()) { @@ -5521,4 +5529,9 @@ private static void addOrganizationUserDetails(AuthenticatedUser authenticatedUs // Set authorized user tenant domain to the tenant domain of the application. authenticatedUser.setTenantDomain(appTenantDomain); } + + public static boolean isPairwiseSubEnabledForAccessTokens() { + + return Boolean.parseBoolean(IdentityUtil.getProperty(ENABLE_PPID_FOR_ACCESS_TOKENS)); + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java index 4e1a014b801..1025c723f34 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java @@ -36,6 +36,7 @@ import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.dao.SharedAppResolveDAO; @@ -44,6 +45,7 @@ import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationContext; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandler; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandlerClientException; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandlerException; import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; @@ -205,6 +207,8 @@ private List getAuthorizedScopes(List requestedScopes, Authentic try { validatedScopes = scopeValidationHandler.validateScopes(requestedScopes, authorizedScopes.getScopes(), scopeValidationContext); + } catch (ScopeValidationHandlerClientException e) { + throw new IdentityOAuth2ClientException(e.getMessage(), e); } catch (ScopeValidationHandlerException e) { throw new IdentityOAuth2Exception("Error while validating policies roles from " + "authorization service.", e); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/JDBCScopeValidator.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/JDBCScopeValidator.java index f099945bf60..53fe7945776 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/JDBCScopeValidator.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/JDBCScopeValidator.java @@ -93,6 +93,11 @@ public class JDBCScopeValidator extends OAuth2ScopeValidator { private static final String SCOPE_VALIDATOR_NAME = "Role based scope validator"; private static final String OPENID = "openid"; private static final String PRESERVE_CASE_SENSITIVITY = "preservedCaseSensitive"; + private static final String SCOPE_VALIDATOR_PRESERVE_CASE_SENSITIVITY_CONFIG = + "OAuth.ScopeValidationPreserveCaseSensitivity"; + + private static final boolean SCOPE_VALIDATOR_PRESERVE_CASE_SENSITIVITY = + Boolean.parseBoolean(IdentityUtil.getProperty(SCOPE_VALIDATOR_PRESERVE_CASE_SENSITIVITY_CONFIG)); private static final Log log = LogFactory.getLog(JDBCScopeValidator.class); @@ -411,7 +416,7 @@ private boolean isUserAuthorizedForScope(String scopeName, String[] userRoles, i //Check if the user still has a valid role for this scope. Set scopeRoles = new HashSet<>(rolesOfScope); - if (preservedCaseSensitive) { + if (preservedCaseSensitive || SCOPE_VALIDATOR_PRESERVE_CASE_SENSITIVITY) { rolesOfScope.retainAll(Arrays.asList(userRoles)); } else { Set rolesOfScopeLowerCase = new HashSet<>(); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerClientException.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerClientException.java new file mode 100644 index 00000000000..518ac004f78 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerClientException.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.oauth2.validators.validationhandler; + +/** + * ScopeValidatorPolicyHandlerClientException + */ +public class ScopeValidationHandlerClientException extends ScopeValidationHandlerException { + + /** + * Constructs a new exception with an error message. + * + * @param message The detail message. + */ + public ScopeValidationHandlerClientException(String message) { + + super(message); + } + + /** + * Constructs a new exception with the message and cause. + * + * @param message The detail message. + * @param cause The cause. + */ + public ScopeValidationHandlerClientException(String message, Throwable cause) { + + super(message, cause); + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/RoleBasedScopeValidationHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/RoleBasedScopeValidationHandler.java index 37d771c6c97..499d441f1f8 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/RoleBasedScopeValidationHandler.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/RoleBasedScopeValidationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-2024, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -28,12 +28,14 @@ import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2ClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.Oauth2ScopeConstants; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationContext; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandler; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandlerClientException; import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandlerException; import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; import org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants; @@ -99,6 +101,8 @@ public List validateScopes(List requestedScopes, List ap List filteredScopes = appAuthorizedScopes.stream().filter(associatedScopes::contains) .collect(Collectors.toList()); return requestedScopes.stream().filter(filteredScopes::contains).collect(Collectors.toList()); + } catch (IdentityOAuth2ClientException e) { + throw new ScopeValidationHandlerClientException(e.getMessage(), e); } catch (IdentityOAuth2Exception | IdentityRoleManagementException e) { throw new ScopeValidationHandlerException("Error while validation scope with RBAC Scope Validation " + "handler", e); diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/OIDCClaimUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/OIDCClaimUtil.java index 46eb0641dcb..c3a76639049 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/OIDCClaimUtil.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/openidconnect/OIDCClaimUtil.java @@ -288,7 +288,7 @@ private static boolean isUserConsentRequiredForClaims(String grantType) { * @return subject type */ - private static SubjectType getSubjectType(OAuthAppDO authAppDO) { + public static SubjectType getSubjectType(OAuthAppDO authAppDO) { if (StringUtils.isNotEmpty(authAppDO.getSubjectType())) { return SubjectType.fromValue(authAppDO.getSubjectType()); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImplTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImplTest.java old mode 100644 new mode 100755 index d295a002679..643bad39f0c --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImplTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthAdminServiceImplTest.java @@ -850,6 +850,34 @@ private void testValidateTokenAuthenticationWithInvalidAuthentication() throws E } } + @DataProvider(name = "getTokenAuthMethodAndTokenReuseConfigData") + public Object[][] getTokenAuthMethodAndTokenReuseConfigData() { + + return new Object[][]{ + // Client auth method, Expected result. + {"private_key_jwt", null, true}, + {null, true, true}, + {"", true, true}, + {" ", true, true}, + {"dummy_method", true, true}, + {"private_key_jwt", true, false}, + {null, null, false}, + {"dummy_method", null, false}}; + } + + @Test(description = "Test invalid reuse token config & client auth method combination.", + dataProvider = "getTokenAuthMethodAndTokenReuseConfigData") + private void testInvalidReuseTokenRequestAndClientAuthMethod(String tokenEndpointAuthMethod, + Boolean tokenEndpointAllowReusePvtKeyJwt, + boolean expectedResult) throws Exception { + + OAuthAdminServiceImpl oAuthAdminService = new OAuthAdminServiceImpl(); + + Assert.assertEquals(invokePrivateMethod(oAuthAdminService, + "isInvalidTokenEPReusePvtKeyJwtRequest", new Class[]{String.class, Boolean.class}, + tokenEndpointAuthMethod, tokenEndpointAllowReusePvtKeyJwt), expectedResult); + } + @Test(description = "Test validating signature algorithm") private void testValidateSignatureAlgorithm() throws Exception { @@ -1059,4 +1087,12 @@ private Object invokePrivateMethod(Object object, String methodName, Object... p method.setAccessible(true); return method.invoke(object, params); } + + private Object invokePrivateMethod(Object object, String methodName, Class[] paramTypes, Object... params) + throws Exception { + + Method method = object.getClass().getDeclaredMethod(methodName, paramTypes); + method.setAccessible(true); + return method.invoke(object, params); + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java index e40207daf4f..b569091c65f 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/OAuth2ServiceTest.java @@ -1,7 +1,7 @@ /* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). * - * WSO2 Inc. licenses this file to you under the Apache License, + * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at @@ -11,10 +11,11 @@ * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ + package org.wso2.carbon.identity.oauth2; import org.apache.oltu.oauth2.common.message.types.GrantType; @@ -519,6 +520,7 @@ public Object[][] createExceptions() { return new Object[][]{ {new IdentityOAuth2Exception(""), "server_error"}, {new InvalidOAuthClientException(""), "invalid_client"}, + {new IdentityOAuth2ClientException("access_denied", ""), "access_denied"} }; } diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/ImpersonatorPermissionValidatorTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/ImpersonatorPermissionValidatorTest.java new file mode 100644 index 00000000000..c2139bad9fd --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/ImpersonatorPermissionValidatorTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.wso2.carbon.identity.oauth2.impersonation; + +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; +import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; +import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationContext; +import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationRequestDTO; +import org.wso2.carbon.identity.oauth2.impersonation.validators.ImpersonatorPermissionValidator; +import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.wso2.carbon.identity.oauth2.impersonation.utils.Constants.IMPERSONATION_SCOPE_NAME; + +/** + * Unit test cases for {@link ImpersonatorPermissionValidatorTest} + */ +@Listeners(MockitoTestNGListener.class) +public class ImpersonatorPermissionValidatorTest { + + @Mock + private AuthenticatedUser impersonator; + @Mock + private OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO; + @Mock + private OAuthAuthzReqMessageContext oAuthAuthzReqMessageContext; + @Mock + private DefaultOAuth2ScopeValidator defaultOAuth2ScopeValidator; + private ImpersonationRequestDTO impersonationRequestDTO; + private static final String[] SCOPES_WITHOUT_OPENID = new String[]{"scope1", "scope2"}; + + @BeforeMethod + public void setUp() throws Exception { + + lenient().when(impersonator.getLoggableMaskedUserId()).thenReturn("123456789"); + lenient().when(oAuth2AuthorizeReqDTO.getRequestedSubjectId()).thenReturn("dummySubjectId"); + lenient().when(oAuth2AuthorizeReqDTO.getUser()).thenReturn(impersonator); + lenient().when(oAuth2AuthorizeReqDTO.getConsumerKey()).thenReturn("dummyConsumerKey"); + lenient().when(oAuth2AuthorizeReqDTO.getScopes()).thenReturn(SCOPES_WITHOUT_OPENID); + lenient().when(oAuth2AuthorizeReqDTO.getTenantDomain()).thenReturn("carbon.super"); + lenient().when(oAuthAuthzReqMessageContext.getAuthorizationReqDTO()).thenReturn(oAuth2AuthorizeReqDTO); + impersonationRequestDTO = new ImpersonationRequestDTO(); + impersonationRequestDTO.setoAuthAuthzReqMessageContext(oAuthAuthzReqMessageContext); + } + + @Test + public void testValidateImpersonation() throws IdentityException, NoSuchFieldException, IllegalAccessException { + + when(defaultOAuth2ScopeValidator.validateScope(oAuthAuthzReqMessageContext)).thenReturn(Arrays.asList("scope1", + "scope2", IMPERSONATION_SCOPE_NAME)); + + ImpersonationContext impersonationContext = new ImpersonationContext(); + impersonationContext.setImpersonationRequestDTO(impersonationRequestDTO); + ImpersonatorPermissionValidator impersonatorPermissionValidator = new ImpersonatorPermissionValidator(); + Field field = ImpersonatorPermissionValidator.class.getDeclaredField("scopeValidator"); + field.setAccessible(true); + field.set(impersonatorPermissionValidator, defaultOAuth2ScopeValidator); + + impersonationContext = + impersonatorPermissionValidator.validateImpersonation(impersonationContext); + + assertTrue(impersonationContext.isValidated(), + "Impersonation context's validated attribute should be true"); + assertNull(impersonationContext.getValidationFailureErrorMessage(), + "Validation error message should be null"); + } + + @Test + public void testValidateImpersonationNegativeCase() throws IdentityException, NoSuchFieldException, + IllegalAccessException { + + when(defaultOAuth2ScopeValidator.validateScope(oAuthAuthzReqMessageContext)).thenReturn(Arrays.asList("scope1", + "scope2")); + + ImpersonationContext impersonationContext = new ImpersonationContext(); + impersonationContext.setImpersonationRequestDTO(impersonationRequestDTO); + ImpersonatorPermissionValidator impersonatorPermissionValidator = new ImpersonatorPermissionValidator(); + Field field = ImpersonatorPermissionValidator.class.getDeclaredField("scopeValidator"); + field.setAccessible(true); + field.set(impersonatorPermissionValidator, defaultOAuth2ScopeValidator); + + impersonationContext = + impersonatorPermissionValidator.validateImpersonation(impersonationContext); + + assertFalse(impersonationContext.isValidated(), + "Impersonation context's validated attribute should be false"); + assertNotNull(impersonationContext.getValidationFailureErrorMessage(), + "Validation error message shouldn't be null"); + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/SubjectScopeValidatorTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/SubjectScopeValidatorTest.java index 182d54e3d3f..cd2a5e94813 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/SubjectScopeValidatorTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/impersonation/SubjectScopeValidatorTest.java @@ -26,9 +26,11 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.application.common.model.User; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.base.IdentityException; -import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; +import org.wso2.carbon.identity.oauth.OAuthUtil; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; import org.wso2.carbon.identity.oauth2.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; @@ -36,8 +38,9 @@ import org.wso2.carbon.identity.oauth2.impersonation.models.ImpersonationRequestDTO; import org.wso2.carbon.identity.oauth2.impersonation.validators.SubjectScopeValidator; import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; -import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.user.core.tenant.TenantManager; import java.lang.reflect.Field; import java.util.Arrays; @@ -67,23 +70,27 @@ public class SubjectScopeValidatorTest { @Mock private ApplicationManagementService applicationManagementService; @Mock - private AuthenticatedUser endUser; + private OAuthComponentServiceHolder mockOAuthComponentServiceHolder; @Mock - private OAuthServerConfiguration mockOAuthServerConfiguration; - + private RealmService mockRealmService; + @Mock + private TenantManager mockTenantManager; private ImpersonationRequestDTO impersonationRequestDTO; private static final String[] SCOPES_WITHOUT_OPENID = new String[]{"scope1", "scope2"}; - - private MockedStatic oAuthServerConfiguration; - private MockedStatic oAuth2Util; + private MockedStatic oAuthComponentServiceHolder; + private MockedStatic oAuthUtil; private MockedStatic oAuth2ServiceComponentHolder; @BeforeMethod public void setUp() throws Exception { - oAuthServerConfiguration = mockStatic(OAuthServerConfiguration.class); - oAuthServerConfiguration.when(OAuthServerConfiguration::getInstance).thenReturn(mockOAuthServerConfiguration); - oAuth2Util = mockStatic(OAuth2Util.class); + oAuthComponentServiceHolder = mockStatic(OAuthComponentServiceHolder.class); + oAuthComponentServiceHolder.when(OAuthComponentServiceHolder::getInstance) + .thenReturn(mockOAuthComponentServiceHolder); + when(mockOAuthComponentServiceHolder.getRealmService()).thenReturn(mockRealmService); + when(mockRealmService.getTenantManager()).thenReturn(mockTenantManager); + when(mockTenantManager.getTenantId("carbon.super")).thenReturn(-1234); + oAuthUtil = mockStatic(OAuthUtil.class); oAuth2ServiceComponentHolder = mockStatic(OAuth2ServiceComponentHolder.class); oAuth2ServiceComponentHolder.when( OAuth2ServiceComponentHolder::getApplicationMgtService).thenReturn(applicationManagementService); @@ -100,16 +107,24 @@ public void setUp() throws Exception { impersonationRequestDTO = new ImpersonationRequestDTO(); impersonationRequestDTO.setoAuthAuthzReqMessageContext(oAuthAuthzReqMessageContext); - oAuth2Util.when(() -> OAuth2Util.resolveUsernameFromUserId("carbon.super", "dummySubjectId")) - .thenReturn("dummyUserName"); - oAuth2Util.when(() -> OAuth2Util.getUserFromUserName("dummyUserName")).thenReturn(endUser); + oAuthUtil.when(() -> OAuthUtil.getUserFromTenant("dummySubjectId", -1234)) + .thenReturn(getDummyUser()); + } + + private User getDummyUser() { + + User user = new User(); + user.setUserName("dummmyUserName"); + user.setUserStoreDomain("dummyUserStore"); + user.setTenantDomain("carbon.super"); + return user; } @AfterMethod public void tearDown() { - oAuthServerConfiguration.close(); - oAuth2Util.close(); + oAuthUtil.close(); oAuth2ServiceComponentHolder.close(); + oAuthComponentServiceHolder.close();; } @Test 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 8e708b49bff..552aef9b116 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 @@ -329,6 +329,7 @@ public Object[][] provideClaimSetData() { cal.add(Calendar.HOUR_OF_DAY, 1); // adds one hour tokenReqMessageContext.addProperty(EXPIRY_TIME_JWT, cal.getTime()); tokenReqMessageContext.addProperty(OAuthConstants.UserType.USER_TYPE, OAuthConstants.UserType.APPLICATION); + tokenReqMessageContext.setAudiences(Collections.singletonList(DUMMY_CLIENT_ID)); authenticatedUserForTokenReq.setFederatedUser(false); return new Object[][]{ @@ -383,6 +384,7 @@ public void testCreateJWTClaimSet(Object authzReqMessageContext, oAuth2Util.when(() -> OAuth2Util.getOIDCAudience(anyString(), any())).thenReturn(Collections.singletonList (DUMMY_CLIENT_ID)); oAuth2Util.when(OAuth2Util::isTokenPersistenceEnabled).thenReturn(true); + oAuth2Util.when(OAuth2Util::isPairwiseSubEnabledForAccessTokens).thenReturn(ppidEnabled); when(mockOAuthServerConfiguration.getSignatureAlgorithm()).thenReturn(SHA256_WITH_HMAC); when(mockOAuthServerConfiguration.getUserAccessTokenValidityPeriodInSeconds()) @@ -392,8 +394,6 @@ public void testCreateJWTClaimSet(Object authzReqMessageContext, JWTTokenIssuer jwtTokenIssuer = spy(new JWTTokenIssuer()); - identityUtil.when(() -> IdentityUtil.getProperty("OAuth.OpenIDConnect.EnablePairwiseSubForAccessToken")) - .thenReturn(String.valueOf(ppidEnabled)); JWTClaimsSet jwtClaimSet = jwtTokenIssuer.createJWTClaimSet( (OAuthAuthzReqMessageContext) authzReqMessageContext, diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuerTest.java index e833ec2e4a6..eebc8f1d76f 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/SubjectTokenIssuerTest.java @@ -27,7 +27,9 @@ import org.testng.annotations.Test; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; 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.authz.OAuthAuthzReqMessageContext; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; @@ -63,9 +65,11 @@ public class SubjectTokenIssuerTest { @Mock private JWTTokenIssuer jwtTokenIssuer; private static final String[] SCOPES_WITHOUT_OPENID = new String[]{"scope1", "scope2"}; + private static final String OAUTH_APP_DO = "OAuthAppDO"; private ImpersonationMgtServiceImpl impersonationMgtService = new ImpersonationMgtServiceImpl(); private MockedStatic oAuthServerConfiguration; + private MockedStatic loggerUtils; @BeforeMethod public void setUp() throws Exception { @@ -86,6 +90,8 @@ public void setUp() throws Exception { Map oauthTokenIssuerMap = new HashMap<>(); oauthTokenIssuerMap.put(JWT_TOKEN_TYPE, jwtTokenIssuer); lenient().when(mockOAuthServerConfiguration.getOauthTokenIssuerMap()).thenReturn(oauthTokenIssuerMap); + loggerUtils = mockStatic(LoggerUtils.class); + loggerUtils.when(LoggerUtils::isDiagnosticLogsEnabled).thenReturn(true); } @AfterMethod @@ -94,12 +100,14 @@ public void tearDown() { OAuth2ServiceComponentHolder.getInstance() .removeImpersonationValidator(new DummyErrornusImpersonationValidator()); oAuthServerConfiguration.close(); + loggerUtils.close(); } @Test public void testIssue() throws IdentityException { when(oAuthAuthzReqMessageContext.getAuthorizationReqDTO()).thenReturn(oAuth2AuthorizeReqDTO); + when(oAuthAuthzReqMessageContext.getProperty(OAUTH_APP_DO)).thenReturn(new OAuthAppDO()); OAuth2ServiceComponentHolder.getInstance().addImpersonationValidator( new DummyImpersonationValidator()); diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java index 4ed73470f98..fbf0662ff9a 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java +++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/grant/AbstractAuthorizationGrantHandlerTest.java @@ -18,10 +18,16 @@ package org.wso2.carbon.identity.oauth2.token.handlers.grant; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.wso2.carbon.identity.action.execution.ActionExecutorService; +import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException; +import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; +import org.wso2.carbon.identity.action.execution.model.ActionType; import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.common.testng.WithCarbonHome; @@ -52,7 +58,10 @@ import java.util.Set; import java.util.UUID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -63,11 +72,12 @@ @WithH2Database(jndiName = "jdbc/WSO2IdentityDB", files = {"dbScripts/identity.sql", "dbScripts/insert_application_and_token.sql", "dbScripts/insert_consumer_app.sql", "dbScripts/insert_local_idp.sql"}) -@WithRealmService(injectToSingletons = { OAuthComponentServiceHolder.class }) - +@WithRealmService(injectToSingletons = {OAuthComponentServiceHolder.class}) public class AbstractAuthorizationGrantHandlerTest { private AbstractAuthorizationGrantHandler handler; + @Mock + private ActionExecutorService mockActionExecutionService; private RefreshGrantHandler refreshGrantHandler; private AuthenticatedUser authenticatedUser; @@ -82,11 +92,17 @@ public class AbstractAuthorizationGrantHandlerTest { private OAuthAppDO oAuthAppDO; @BeforeMethod - public void setUp() throws IdentityOAuth2Exception, IdentityOAuthAdminException { + public void setUp() throws IdentityOAuth2Exception, IdentityOAuthAdminException, ActionExecutionException { authenticatedUser = new AuthenticatedUser() { }; OAuthComponentServiceHolder.getInstance().setRealmService(IdentityTenantUtil.getRealmService()); + + OAuthComponentServiceHolder.getInstance().setActionExecutorService(mockActionExecutionService); + MockitoAnnotations.initMocks(this); + when(mockActionExecutionService.execute(any(ActionType.class), anyMap(), any())).thenReturn( + new ActionExecutionStatus(ActionExecutionStatus.Status.SUCCESS, null)); + authenticatedUser.setUserName("randomUser"); authenticatedUser.setTenantDomain("Homeless"); authenticatedUser.setUserStoreDomain("Street"); @@ -130,26 +146,27 @@ public Object[][] issueDataProvider() { { true, false, 0L, 0L, 0L, 0L, true, TOKEN_STATE_ACTIVE, false, true }, { true, false, 0L, 0L, 0L, 3600L, true, TOKEN_STATE_ACTIVE, false, false }, - { true, true, 3600L, 3600L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, true }, - { true, true, 0L, 3600L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, false }, - { true, true, 0L, 0L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, true }, - { true, false, 0L, 0L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, false }, - { false, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_ACTIVE, true, true }, - { false, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_REVOKED, true, false }, - { false, false, 0L, 0L, 0L, 0L, true, TOKEN_STATE_ACTIVE, true, true }, - { false, false, 0L, 0L, 0L, 3600L, true, TOKEN_STATE_ACTIVE, true, false }, - { true, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_ACTIVE, true, true }, - { true, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_REVOKED, true, false }, - { true, false, 0L, 0L, 0L, 0L, true, TOKEN_STATE_ACTIVE, true, true }, - { true, false, 0L, 0L, 0L, 3600L, true, TOKEN_STATE_ACTIVE, true, false }, - { true, true, 0L, 0L, -1L, 3600L, true, TOKEN_STATE_ACTIVE, true, true }, - { false, true, 0L, 0L, -1L, 3600L, true, TOKEN_STATE_ACTIVE, true, false } }; + {true, true, 3600L, 3600L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, true}, + {true, true, 0L, 3600L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, false}, + {true, true, 0L, 0L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, true}, + {true, false, 0L, 0L, 0L, 0L, false, TOKEN_STATE_ACTIVE, true, false}, + {false, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_ACTIVE, true, true}, + {false, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_REVOKED, true, false}, + {false, false, 0L, 0L, 0L, 0L, true, TOKEN_STATE_ACTIVE, true, true}, + {false, false, 0L, 0L, 0L, 3600L, true, TOKEN_STATE_ACTIVE, true, false}, + {true, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_ACTIVE, true, true}, + {true, false, 0L, 0L, 3600L, 0L, true, TOKEN_STATE_REVOKED, true, false}, + {true, false, 0L, 0L, 0L, 0L, true, TOKEN_STATE_ACTIVE, true, true}, + {true, false, 0L, 0L, 0L, 3600L, true, TOKEN_STATE_ACTIVE, true, false}, + {true, true, 0L, 0L, -1L, 3600L, true, TOKEN_STATE_ACTIVE, true, true}, + {false, true, 0L, 0L, -1L, 3600L, true, TOKEN_STATE_ACTIVE, true, false}}; } @Test(dataProvider = "IssueDataProvider") public void testIssue(boolean cacheEnabled, boolean cacheEntryAvailable, long cachedTokenValidity, - long cachedRefreshTokenValidity, long dbTokenValidity, long dbRefreshTokenValidity, - boolean dbEntryAvailable, String dbTokenState, boolean tokenLoggable, boolean isIDPIdColumnEnabled) + long cachedRefreshTokenValidity, long dbTokenValidity, long dbRefreshTokenValidity, + boolean dbEntryAvailable, String dbTokenState, boolean tokenLoggable, + boolean isIDPIdColumnEnabled) throws Exception { OAuth2ServiceComponentHolder.setIDPIdColumnEnabled(isIDPIdColumnEnabled); @@ -163,7 +180,7 @@ public void testIssue(boolean cacheEnabled, boolean cacheEntryAvailable, long ca OAuthTokenReqMessageContext tokReqMsgCtx = new OAuthTokenReqMessageContext(oAuth2AccessTokenReqDTO); tokReqMsgCtx.setAuthorizedUser(authenticatedUser); - tokReqMsgCtx.setScope(new String[] { "scope1", "scope2" }); + tokReqMsgCtx.setScope(new String[]{"scope1", "scope2"}); OAuth2AccessTokenRespDTO tokenRespDTO = handler.issue(tokReqMsgCtx); assertNotNull(tokenRespDTO.getAccessToken()); @@ -171,12 +188,14 @@ public void testIssue(boolean cacheEnabled, boolean cacheEntryAvailable, long ca @DataProvider(name = "AuthorizeAccessDelegationDataProvider") public Object[][] buildAuthorizeAccessDelegationDataProvider() { - return new Object[][] { { GrantType.SAML20_BEARER.toString() }, { GrantType.IWA_NTLM.toString() }, - { PASSWORD_GRANT } }; + + return new Object[][]{{GrantType.SAML20_BEARER.toString()}, {GrantType.IWA_NTLM.toString()}, + {PASSWORD_GRANT}}; } @Test(dataProvider = "AuthorizeAccessDelegationDataProvider") public void testAuthorizeAccessDelegation(String grantType) throws Exception { + Set callbackHandlerMetaData = new HashSet<>(); callbackHandlerMetaData.add(new OAuthCallbackHandlerMetaData(DEFAULT_CALLBACK_HANDLER_CLASS_NAME, null, 1)); @@ -191,7 +210,7 @@ public void testAuthorizeAccessDelegation(String grantType) throws Exception { oAuth2AccessTokenReqDTO.setGrantType(grantType); OAuthTokenReqMessageContext tokReqMsgCtx = new OAuthTokenReqMessageContext(oAuth2AccessTokenReqDTO); - tokReqMsgCtx.setScope(new String[] { "scope1", "scope2" }); + tokReqMsgCtx.setScope(new String[]{"scope1", "scope2"}); tokReqMsgCtx.setAuthorizedUser(authenticatedUser); boolean result = handler.authorizeAccessDelegation(tokReqMsgCtx); @@ -200,11 +219,12 @@ public void testAuthorizeAccessDelegation(String grantType) throws Exception { @DataProvider(name = "IsAuthorizedClientDataProvider") public Object[][] buildIsAuthorizedClient() { - return new Object[][] { - { true, GrantType.SAML20_BEARER.toString() + " " + GrantType.IWA_NTLM.toString() + " " + PASSWORD_GRANT, - PASSWORD_GRANT, true }, - { true, GrantType.SAML20_BEARER.toString() + " " + GrantType.IWA_NTLM.toString(), PASSWORD_GRANT, - false }, { true, null, PASSWORD_GRANT, false }, { false, null, PASSWORD_GRANT, false }, }; + + return new Object[][]{ + {true, GrantType.SAML20_BEARER.toString() + " " + GrantType.IWA_NTLM.toString() + " " + PASSWORD_GRANT, + PASSWORD_GRANT, true}, + {true, GrantType.SAML20_BEARER.toString() + " " + GrantType.IWA_NTLM.toString(), PASSWORD_GRANT, + false}, {true, null, PASSWORD_GRANT, false}, {false, null, PASSWORD_GRANT, false}}; } @Test(dataProvider = "IsAuthorizedClientDataProvider") @@ -216,7 +236,7 @@ public void testIsAuthorizedClient(boolean oAuthAppDOAvailable, String grantType oAuth2AccessTokenReqDTO.setGrantType(grantType); OAuthTokenReqMessageContext tokReqMsgCtx = new OAuthTokenReqMessageContext(oAuth2AccessTokenReqDTO); - tokReqMsgCtx.setScope(new String[] { "scope1", "scope2" }); + tokReqMsgCtx.setScope(new String[]{"scope1", "scope2"}); tokReqMsgCtx.setAuthorizedUser(authenticatedUser); if (oAuthAppDOAvailable) { @@ -235,7 +255,7 @@ public void testStoreAccessToken() throws IdentityException { AccessTokenDO newAccessTokenDO = new AccessTokenDO(); AccessTokenDO existingAccessTokenDO = new AccessTokenDO(); newAccessTokenDO.setAuthzUser(authenticatedUser); - newAccessTokenDO.setScope(new String[] { "scope1", "scope2" }); + newAccessTokenDO.setScope(new String[]{"scope1", "scope2"}); handler.storeAccessToken(oAuth2AccessTokenReqDTO, TestConstants.USERSTORE_DOMAIN, newAccessTokenDO, TestConstants.NEW_ACCESS_TOKEN, existingAccessTokenDO); @@ -303,15 +323,15 @@ public boolean canHandle(OAuthTokenReqMessageContext tokReqMsgCtx) { scopeHandlers.add(oAuth2ScopeHandler2); scopeHandlers.add(oAuth2ScopeHandler3); - return new Object[][] { { oAuthTokenReqMessageContext1, Collections.EMPTY_SET, true }, - { oAuthTokenReqMessageContext2, Collections.EMPTY_SET, true }, - { oAuthTokenReqMessageContext3, Collections.EMPTY_SET, true }, - { oAuthTokenReqMessageContext3, scopeHandlers, false } }; + return new Object[][]{{oAuthTokenReqMessageContext1, Collections.EMPTY_SET, true}, + {oAuthTokenReqMessageContext2, Collections.EMPTY_SET, true}, + {oAuthTokenReqMessageContext3, Collections.EMPTY_SET, true}, + {oAuthTokenReqMessageContext3, scopeHandlers, false}}; } @Test(dataProvider = "BuildTokenRequestMessageContext") public void testValidateScope(Object tokenRequestMessageContext, Set scopeHandlers, - boolean expectedValue) throws IdentityOAuth2Exception { + boolean expectedValue) throws IdentityOAuth2Exception { OAuthTokenReqMessageContext tokReqMsgCtx = (OAuthTokenReqMessageContext) tokenRequestMessageContext; OAuthServerConfiguration serverConfiguration = OAuthServerConfiguration.getInstance(); @@ -340,10 +360,10 @@ public Object[][] buildTokenRequestMsgContextForAuthorizedClient() { oAuthTokenReqMessageContext2.addProperty("OAuthAppDO", oAuthAppDO2); oAuthTokenReqMessageContext3.addProperty("OAuthAppDO", oAuthAppDO3); - return new Object[][] { { oAuthTokenReqMessageContext1, false, false }, - { oAuthTokenReqMessageContext1, true, false }, { oAuthTokenReqMessageContext2, false, true }, - { oAuthTokenReqMessageContext2, true, true }, { oAuthTokenReqMessageContext3, false, false }, - { oAuthTokenReqMessageContext3, true, false } }; + return new Object[][]{{oAuthTokenReqMessageContext1, false, false}, + {oAuthTokenReqMessageContext1, true, false}, {oAuthTokenReqMessageContext2, false, true}, + {oAuthTokenReqMessageContext2, true, true}, {oAuthTokenReqMessageContext3, false, false}, + {oAuthTokenReqMessageContext3, true, false}}; } @Test(dataProvider = "BuildTokenRequestMsgContextForAuthorizedClient") diff --git a/components/org.wso2.carbon.identity.oauth/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.oauth/src/test/resources/testng.xml index 33feab55628..990c264e742 100644 --- a/components/org.wso2.carbon.identity.oauth/src/test/resources/testng.xml +++ b/components/org.wso2.carbon.identity.oauth/src/test/resources/testng.xml @@ -41,6 +41,7 @@ + @@ -185,6 +186,7 @@ + diff --git a/components/org.wso2.carbon.identity.oidc.dcr/pom.xml b/components/org.wso2.carbon.identity.oidc.dcr/pom.xml index b71936cda1b..e4bd76233df 100644 --- a/components/org.wso2.carbon.identity.oidc.dcr/pom.xml +++ b/components/org.wso2.carbon.identity.oidc.dcr/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.oidc.session/pom.xml b/components/org.wso2.carbon.identity.oidc.session/pom.xml index b43c0e96672..901fb43b310 100644 --- a/components/org.wso2.carbon.identity.oidc.session/pom.xml +++ b/components/org.wso2.carbon.identity.oidc.session/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/components/org.wso2.carbon.identity.webfinger/pom.xml b/components/org.wso2.carbon.identity.webfinger/pom.xml index f70925f3393..c57c127cd35 100644 --- a/components/org.wso2.carbon.identity.webfinger/pom.xml +++ b/components/org.wso2.carbon.identity.webfinger/pom.xml @@ -21,7 +21,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/features/org.wso2.carbon.identity.oauth.common.feature/pom.xml b/features/org.wso2.carbon.identity.oauth.common.feature/pom.xml index 60e8a9e73aa..eb8151c0c9b 100644 --- a/features/org.wso2.carbon.identity.oauth.common.feature/pom.xml +++ b/features/org.wso2.carbon.identity.oauth.common.feature/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/features/org.wso2.carbon.identity.oauth.dcr.server.feature/pom.xml b/features/org.wso2.carbon.identity.oauth.dcr.server.feature/pom.xml index 5d568469699..bd58f491556 100644 --- a/features/org.wso2.carbon.identity.oauth.dcr.server.feature/pom.xml +++ b/features/org.wso2.carbon.identity.oauth.dcr.server.feature/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/features/org.wso2.carbon.identity.oauth.feature/pom.xml b/features/org.wso2.carbon.identity.oauth.feature/pom.xml index ea0d568eaad..7ed3e814fe7 100644 --- a/features/org.wso2.carbon.identity.oauth.feature/pom.xml +++ b/features/org.wso2.carbon.identity.oauth.feature/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/features/org.wso2.carbon.identity.oauth.server.feature/pom.xml b/features/org.wso2.carbon.identity.oauth.server.feature/pom.xml index 9430b32e832..df4ca6d6ba9 100644 --- a/features/org.wso2.carbon.identity.oauth.server.feature/pom.xml +++ b/features/org.wso2.carbon.identity.oauth.server.feature/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/features/org.wso2.carbon.identity.oauth.ui.feature/pom.xml b/features/org.wso2.carbon.identity.oauth.ui.feature/pom.xml index c142836a986..4c3c9320c81 100644 --- a/features/org.wso2.carbon.identity.oauth.ui.feature/pom.xml +++ b/features/org.wso2.carbon.identity.oauth.ui.feature/pom.xml @@ -22,7 +22,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index e9dec24b8d3..32f2179cc21 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 4.0.0 org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT pom WSO2 Carbon OAuth module http://wso2.org @@ -277,6 +277,11 @@ org.wso2.carbon.identity.configuration.mgt.core ${carbon.identity.framework.version} + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.action.execution + ${carbon.identity.framework.version} + @@ -915,7 +920,7 @@ [1.0.1, 2.0.0) - 7.2.37 + 7.3.50 [5.25.234, 8.0.0) diff --git a/service-stubs/org.wso2.carbon.claim.metadata.mgt.stub/pom.xml b/service-stubs/org.wso2.carbon.claim.metadata.mgt.stub/pom.xml index a26791f0601..4c1fde7e7bf 100644 --- a/service-stubs/org.wso2.carbon.claim.metadata.mgt.stub/pom.xml +++ b/service-stubs/org.wso2.carbon.claim.metadata.mgt.stub/pom.xml @@ -21,7 +21,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT ../../pom.xml diff --git a/test-utils/org.wso2.carbon.identity.oauth.common.testng/pom.xml b/test-utils/org.wso2.carbon.identity.oauth.common.testng/pom.xml index 391555ea62d..7fba9719750 100644 --- a/test-utils/org.wso2.carbon.identity.oauth.common.testng/pom.xml +++ b/test-utils/org.wso2.carbon.identity.oauth.common.testng/pom.xml @@ -23,7 +23,7 @@ org.wso2.carbon.identity.inbound.auth.oauth2 identity-inbound-auth-oauth ../../pom.xml - 7.0.107-SNAPSHOT + 7.0.128-SNAPSHOT 4.0.0