diff --git a/components/org.wso2.carbon.identity.oauth/pom.xml b/components/org.wso2.carbon.identity.oauth/pom.xml index e7d21d8352e..b6e53a8ba50 100644 --- a/components/org.wso2.carbon.identity.oauth/pom.xml +++ b/components/org.wso2.carbon.identity.oauth/pom.xml @@ -329,6 +329,14 @@ jaxp-ri test + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.api.resource.mgt + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.role.v2.mgt.core + @@ -423,6 +431,8 @@ org.wso2.carbon.utils.multitenancy;version="${carbon.kernel.imp.pkg.version.range}", org.wso2.carbon.identity.multi.attribute.login.mgt.*; version="${carbon.identity.framework.imp.pkg.version.range}", + org.wso2.carbon.identity.api.resource.mgt; version="${carbon.identity.framework.imp.pkg.version.range}", + org.wso2.carbon.identity.role.v2.mgt.core; version="${carbon.identity.framework.imp.pkg.version.range}" !org.wso2.carbon.identity.oauth.internal, 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 209f72f6336..52480685d0b 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 @@ -31,6 +31,7 @@ import org.wso2.carbon.identity.oauth2.dao.TokenManagementDAO; import org.wso2.carbon.identity.oauth2.token.handlers.response.AccessTokenResponseHandler; import org.wso2.carbon.identity.oauth2.validators.scope.ScopeValidator; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandler; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.management.service.OrganizationUserResidentResolverService; import org.wso2.carbon.identity.role.mgt.core.RoleManagementService; @@ -57,6 +58,7 @@ public class OAuthComponentServiceHolder { private List tokenBindingMetaDataDTOs = new ArrayList<>(); private OAuthAdminServiceImpl oAuthAdminService; private List scopeValidators = new ArrayList<>(); + private List scopeValidationHandlers = new ArrayList<>(); private Map oAuthApplicationMgtListeners = new TreeMap<>(); private RoleManagementService roleManagementService; private OrganizationUserResidentResolverService organizationUserResidentResolverService; @@ -105,6 +107,46 @@ public void setScopeValidators(List scopeValidators) { this.scopeValidators = scopeValidators; } + /** + * Get the list of scope validation handler implementations available. + * + * @return ScopeValidationHandler returns a list ot scope validation policy handler. + */ + public List getScopeValidationHandlers() { + + return scopeValidationHandlers; + } + + /** + * Add scope validation handler implementation. + * + * @param scopeValidationHandler Scope validation handler implementation. + */ + public void addScopeValidationHandler(ScopeValidationHandler scopeValidationHandler) { + + scopeValidationHandlers.add(scopeValidationHandler); + } + + /** + * Remove scope validation policy implementation. + * + * @param scopeValidationHandler Scope validation policy implementation. + */ + public void removeScopeValidationHandler(ScopeValidationHandler scopeValidationHandler) { + + scopeValidationHandlers.remove(scopeValidationHandler); + } + + /** + * Set a list of scope validation handler implementations. + * + * @param scopeValidationHandlers List of Scope validation handler implementation. + */ + public void setScopeValidatorPolicyHandlers(List scopeValidationHandlers) { + + this.scopeValidationHandlers = scopeValidationHandlers; + } + private OAuthComponentServiceHolder() { } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java index a02d0cd4aaa..ec24550a8f6 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/AuthorizationHandlerManager.java @@ -41,7 +41,9 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeRespDTO; import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters; +import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; import org.wso2.carbon.identity.oauth2.validators.JDBCPermissionBasedInternalScopeValidator; import org.wso2.carbon.identity.oauth2.validators.RoleBasedInternalScopeValidator; import org.wso2.carbon.utils.CarbonUtils; @@ -276,15 +278,23 @@ private void validateRequestedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx, List requestedAllowedScopes = getAllowedScopesFromRequestedScopes(authzReqMsgCtx); // Remove the system level allowed scopes from requested scopes for further validation. removeAllowedScopesFromRequestedScopes(authzReqMsgCtx, requestedAllowedScopes); - // If it is management app, we validate internal scopes in the requested scopes. - String[] authorizedInternalScopes = new String[0]; - log.debug("Handling the internal scope validation."); - authorizedInternalScopes = getAuthorizedInternalScopes(authzReqMsgCtx); - - // Remove the internal scopes from requested scopes for further validation. - removeInternalScopesFromRequestedScopes(authzReqMsgCtx); - // Adding the authorized internal scopes to tokReqMsgCtx for any special validators to use. - authzReqMsgCtx.setAuthorizedInternalScopes(authorizedInternalScopes); + List authorizedScopes = null; + // Switch the scope validators dynamically based on the authorization runtime. + if (AuthzUtil.isLegacyAuthzRuntime()) { + // If it is management app, we validate internal scopes in the requested scopes. + String[] authorizedInternalScopes = new String[0]; + log.debug("Handling the internal scope validation."); + authorizedInternalScopes = getAuthorizedInternalScopes(authzReqMsgCtx); + + // Remove the internal scopes from requested scopes for further validation. + removeInternalScopesFromRequestedScopes(authzReqMsgCtx); + // Adding the authorized internal scopes to tokReqMsgCtx for any special validators to use. + authzReqMsgCtx.setAuthorizedInternalScopes(authorizedInternalScopes); + } else { + // Engage new scope validator + authorizedScopes = getAuthorizedScopes(authzReqMsgCtx); + removeAuthorizedScopesFromRequestedScopes(authzReqMsgCtx, authorizedScopes); + } boolean isDropUnregisteredScopes = OAuthServerConfiguration.getInstance().isDropUnregisteredScopes(); if (isDropUnregisteredScopes) { if (log.isDebugEnabled()) { @@ -296,8 +306,12 @@ private void validateRequestedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx, boolean isValid = validateScopes(authzReqMsgCtx, authzHandler); boolean isValidatedScopesContainsInRequestedScopes = isValidatedScopesContainsInRequestedScopes(authzReqMsgCtx); if (isValid && isValidatedScopesContainsInRequestedScopes) { - // Add authorized internal scopes to the request for sending in the response. - addAuthorizedInternalScopes(authzReqMsgCtx, authzReqMsgCtx.getAuthorizedInternalScopes()); + if (AuthzUtil.isLegacyAuthzRuntime()) { + // Add authorized internal scopes to the request for sending in the response. + addAuthorizedInternalScopes(authzReqMsgCtx, authzReqMsgCtx.getAuthorizedInternalScopes()); + } else { + addAuthorizedScopes(authzReqMsgCtx, authorizedScopes); + } // Add scopes that filtered from the allowed scopes list. addAllowedScopes(authzReqMsgCtx, requestedAllowedScopes.toArray(new String[0])); } else { @@ -359,6 +373,19 @@ private String[] getAuthorizedInternalScopes(OAuthAuthzReqMessageContext authzRe return authorizedInternalScopes; } + /** + * get authorized scopes. + * + * @param authzReqMsgCtx authzReqMsgCtx + * @return - authorizedInternalScopes scopes list + */ + private List getAuthorizedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx) + throws IdentityOAuth2Exception { + + DefaultOAuth2ScopeValidator scopeValidator = new DefaultOAuth2ScopeValidator(); + return scopeValidator.validateScope(authzReqMsgCtx); + } + /** * Eemove internal scopes from requested scopes. * @@ -379,6 +406,27 @@ private void removeInternalScopesFromRequestedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx.getAuthorizationReqDTO().setScopes(scopes.toArray(new String[0])); } + /** + * Remove authorized scopes from requested scopes. + * + * @param authzReqMsgCtx authzReqMsgCtx + * @param authorizedScopes Authorized Scopes + */ + private void removeAuthorizedScopesFromRequestedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx, + List authorizedScopes) { + + if (authzReqMsgCtx.getAuthorizationReqDTO().getScopes() == null) { + return; + } + List scopes = new ArrayList<>(); + for (String scope : authzReqMsgCtx.getAuthorizationReqDTO().getScopes()) { + if (!authorizedScopes.contains(scope) && !scope.equalsIgnoreCase(SYSTEM_SCOPE)) { + scopes.add(scope); + } + } + authzReqMsgCtx.getAuthorizationReqDTO().setScopes(scopes.toArray(new String[0])); + } + /** * Remove the system level allowed scopes from requested scopes. * @@ -419,6 +467,14 @@ private void addAuthorizedInternalScopes(OAuthAuthzReqMessageContext authzReqMsg authzReqMsgCtx.setApprovedScope(scopesToReturn); } + private void addAuthorizedScopes(OAuthAuthzReqMessageContext authzReqMsgCtx, List authorizedScopes) { + + String[] scopes = authzReqMsgCtx.getApprovedScope(); + String[] scopesToReturn = (String[]) ArrayUtils.addAll(scopes, authorizedScopes.toArray()); + authzReqMsgCtx.setApprovedScope(scopesToReturn); + } + + private void addRequestedOIDCScopes(OAuthAuthzReqMessageContext authzReqMsgCtx, String[] requestedOIDCScopes) { Set scopesToReturn = new HashSet<>(Arrays.asList(authzReqMsgCtx.getApprovedScope())); 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 320cc873477..cab7d829939 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 @@ -31,11 +31,13 @@ import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.identity.api.resource.mgt.APIResourceManager; import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticationService; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationDataPublisher; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationMethodNameTranslator; import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.application.mgt.AuthorizedAPIManagementService; import org.wso2.carbon.identity.application.mgt.listener.ApplicationMgtListener; import org.wso2.carbon.identity.consent.server.configs.mgt.services.ConsentServerConfigsManagementService; import org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager; @@ -80,6 +82,10 @@ import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.oauth2.validators.scope.RoleBasedScopeIssuer; import org.wso2.carbon.identity.oauth2.validators.scope.ScopeValidator; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.ScopeValidationHandler; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.impl.M2MScopeValidationHandler; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.impl.NoPolicyScopeValidationHandler; +import org.wso2.carbon.identity.oauth2.validators.validationhandler.impl.RoleBasedScopeValidationHandler; import org.wso2.carbon.identity.openidconnect.OpenIDConnectClaimFilter; import org.wso2.carbon.identity.openidconnect.OpenIDConnectClaimFilterImpl; import org.wso2.carbon.identity.openidconnect.dao.ScopeClaimMappingDAO; @@ -88,6 +94,7 @@ import org.wso2.carbon.identity.organization.management.service.OrganizationManagementInitialize; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.management.service.OrganizationUserResidentResolverService; +import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService; import org.wso2.carbon.identity.user.store.configuration.listener.UserStoreConfigListener; import org.wso2.carbon.idp.mgt.IdpManager; import org.wso2.carbon.registry.core.service.RegistryService; @@ -332,6 +339,9 @@ protected void activate(ComponentContext context) { bundleContext.registerService(OAuth2ScopeService.class.getName(), oAuth2ScopeService, null); // Registering OAuth2ScopeService under ScopeService interface as the default service. bundleContext.registerService(ScopeMetadataService.class, oAuth2ScopeService, null); + bundleContext.registerService(ScopeValidationHandler.class, new RoleBasedScopeValidationHandler(), null); + bundleContext.registerService(ScopeValidationHandler.class, new NoPolicyScopeValidationHandler(), null); + bundleContext.registerService(ScopeValidationHandler.class, new M2MScopeValidationHandler(), null); // 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 @@ -607,6 +617,29 @@ protected void removeScopeValidatorService(ScopeValidator scopeValidator) { OAuthComponentServiceHolder.getInstance().removeScopeValidator(scopeValidator); } + @Reference( + name = "scope.validator.handler", + service = ScopeValidationHandler.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC, + unbind = "removeScopeValidationHandler" + ) + protected void addScopeValidationHandler(ScopeValidationHandler scopeValidationHandler) { + + if (log.isDebugEnabled()) { + log.debug("Adding the Scope validation handler Service : " + scopeValidationHandler.getName()); + } + OAuthComponentServiceHolder.getInstance().addScopeValidationHandler(scopeValidationHandler); + } + + protected void removeScopeValidationHandler(ScopeValidationHandler scopeValidationHandler) { + + if (log.isDebugEnabled()) { + log.debug("Removing the Scope validator Service : " + scopeValidationHandler.getName()); + } + OAuthComponentServiceHolder.getInstance().removeScopeValidationHandler(scopeValidationHandler); + } + @Reference( name = "IdentityProviderManager", service = org.wso2.carbon.idp.mgt.IdpManager.class, @@ -1222,4 +1255,85 @@ protected void unsetRealmService(RealmService realmService) { OAuth2ServiceComponentHolder.getInstance().setRealmService(null); } + + @Reference( + name = "identity.authorized.api.management.component", + service = AuthorizedAPIManagementService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetAuthorizedAPIManagementService" + ) + protected void setAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) { + + if (log.isDebugEnabled()) { + log.debug("Adding Authorized API Management Service: " + authorizedAPIManagementService.getClass() + .getName()); + } + OAuth2ServiceComponentHolder.getInstance() + .setAuthorizedAPIManagementService(authorizedAPIManagementService); + } + + protected void unsetAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) { + + if (log.isDebugEnabled()) { + log.debug("Removing Authorized API Management Service: " + authorizedAPIManagementService.getClass() + .getName()); + } + OAuth2ServiceComponentHolder.getInstance().setAuthorizedAPIManagementService(null); + } + + @Reference( + name = "api.resource.mgt.service.component", + service = APIResourceManager.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetAPIResourceManagerService" + ) + protected void setAPIResourceManagerService(APIResourceManager apiResourceManager) { + + if (log.isDebugEnabled()) { + log.debug("Adding API Resource Manager: " + apiResourceManager.getClass().getName()); + } + OAuth2ServiceComponentHolder.getInstance().setApiResourceManager(apiResourceManager); + } + protected void unsetAPIResourceManagerService(APIResourceManager apiResourceManager) { + + if (log.isDebugEnabled()) { + log.debug("Removing API Resource Manager: " + apiResourceManager.getClass().getName()); + } + OAuth2ServiceComponentHolder.getInstance().setApiResourceManager(null); + } + + /** + * Set role management service V2 implementation. + * + * @param roleManagementService RoleManagementServiceV2. + */ + @Reference( + name = "org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService", + service = org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.DYNAMIC, + unbind = "unsetRoleManagementServiceV2") + protected void setRoleManagementServiceV2(RoleManagementService roleManagementService) { + + if (log.isDebugEnabled()) { + log.debug("Adding Role Management Service V2: " + roleManagementService.getClass().getName()); + } + OAuth2ServiceComponentHolder.getInstance().setRoleManagementServiceV2(roleManagementService); + } + + /** + * Unset role management service V2 implementation. + * + * @param roleManagementService RoleManagementServiceV2 + */ + protected void unsetRoleManagementServiceV2(RoleManagementService roleManagementService) { + + if (log.isDebugEnabled()) { + log.debug("Removing Role Management Service V2: " + roleManagementService.getClass().getName()); + } + OAuth2ServiceComponentHolder.getInstance().setRoleManagementServiceV2(null); + } + } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java index cdf408a151e..fde082095a8 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.java @@ -18,10 +18,12 @@ package org.wso2.carbon.identity.oauth2.internal; +import org.wso2.carbon.identity.api.resource.mgt.APIResourceManager; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationDataPublisher; import org.wso2.carbon.identity.application.authentication.framework.AuthenticationMethodNameTranslator; 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.consent.server.configs.mgt.services.ConsentServerConfigsManagementService; import org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager; import org.wso2.carbon.identity.core.handler.HandlerComparator; @@ -48,6 +50,7 @@ import org.wso2.carbon.identity.organization.management.service.OrganizationManagementInitialize; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; import org.wso2.carbon.identity.organization.management.service.OrganizationUserResidentResolverService; +import org.wso2.carbon.identity.role.v2.mgt.core.RoleManagementService; import org.wso2.carbon.idp.mgt.IdpManager; import org.wso2.carbon.registry.core.service.RegistryService; import org.wso2.carbon.user.core.service.RealmService; @@ -104,6 +107,9 @@ public class OAuth2ServiceComponentHolder { private RefreshTokenGrantProcessor refreshTokenGrantProcessor; private OAuth2RevocationProcessor revocationProcessor; private AccessTokenProvider accessTokenProvider; + private AuthorizedAPIManagementService authorizedAPIManagementService; + private APIResourceManager apiResourceManager; + private RoleManagementService roleManagementServiceV2; private OAuth2ServiceComponentHolder() { @@ -738,4 +744,52 @@ public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) { this.accessTokenProvider = accessTokenProvider; } + + + public AuthorizedAPIManagementService getAuthorizedAPIManagementService() { + + return authorizedAPIManagementService; + } + + public void setAuthorizedAPIManagementService(AuthorizedAPIManagementService authorizedAPIManagementService) { + + this.authorizedAPIManagementService = authorizedAPIManagementService; + } + + /** + * Get APIResourceManager osgi service. + * + * @return APIResourceManager. + */ + public APIResourceManager getApiResourceManager() { + return apiResourceManager; + } + /** + * Set APIResourceManager osgi service. + * + * @param apiResourceManager APIResourceManager. + */ + public void setApiResourceManager(APIResourceManager apiResourceManager) { + this.apiResourceManager = apiResourceManager; + } + + /** + * Get {@link RoleManagementService}. + * + * @return Instance of {@link RoleManagementService}. + */ + public RoleManagementService getRoleManagementServiceV2() { + + return roleManagementServiceV2; + } + + /** + * Set {@link RoleManagementService}. + * + * @param roleManagementServiceV2 Instance of {@link RoleManagementService}. + */ + public void setRoleManagementServiceV2(RoleManagementService roleManagementServiceV2) { + + this.roleManagementServiceV2 = roleManagementServiceV2; + } } diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/AccessTokenExtendedAttributes.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/AccessTokenExtendedAttributes.java index 0b13bdf76e5..7e6854e1733 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/AccessTokenExtendedAttributes.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/model/AccessTokenExtendedAttributes.java @@ -18,8 +18,8 @@ package org.wso2.carbon.identity.oauth2.model; -import com.hazelcast.com.fasterxml.jackson.annotation.JsonIgnore; -import com.hazelcast.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.util.Map; 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 c442520c1c3..2e1398b7782 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 @@ -66,7 +66,9 @@ import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding; import org.wso2.carbon.identity.oauth2.token.handlers.grant.AuthorizationGrantHandler; import org.wso2.carbon.identity.oauth2.token.handlers.response.AccessTokenResponseHandler; +import org.wso2.carbon.identity.oauth2.util.AuthzUtil; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; import org.wso2.carbon.identity.oauth2.validators.JDBCPermissionBasedInternalScopeValidator; import org.wso2.carbon.identity.oauth2.validators.RoleBasedInternalScopeValidator; import org.wso2.carbon.identity.openidconnect.IDTokenBuilder; @@ -645,6 +647,7 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I List requestedAllowedScopes = new ArrayList<>(); String[] authorizedInternalScopes = new String[0]; String[] requestedScopes = tokReqMsgCtx.getScope(); + List authorizedScopes = null; if (GrantType.CLIENT_CREDENTIALS.toString().equals(grantType) && !isManagementApp) { log.debug("Application is not configured as Management App and the grant type is client credentials. " + "Hence skipping internal scope validation to stop issuing internal scopes for the client : " + @@ -676,15 +679,24 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I if (log.isDebugEnabled()) { log.debug("Handling the internal scope validation."); } - // Execute Internal SCOPE Validation. - JDBCPermissionBasedInternalScopeValidator scopeValidator = new JDBCPermissionBasedInternalScopeValidator(); - authorizedInternalScopes = scopeValidator.validateScope(tokReqMsgCtx); - // Execute internal console scopes validation. - if (IdentityUtil.isSystemRolesEnabled()) { - RoleBasedInternalScopeValidator roleBasedInternalScopeValidator = new RoleBasedInternalScopeValidator(); - String[] roleBasedInternalConsoleScopes = roleBasedInternalScopeValidator.validateScope(tokReqMsgCtx); - authorizedInternalScopes = (String[]) ArrayUtils - .addAll(authorizedInternalScopes, roleBasedInternalConsoleScopes); + // Switch the scope validators dynamically based on the authorization runtime. + if (AuthzUtil.isLegacyAuthzRuntime()) { + // Execute Internal SCOPE Validation. + JDBCPermissionBasedInternalScopeValidator scopeValidator = + new JDBCPermissionBasedInternalScopeValidator(); + authorizedInternalScopes = scopeValidator.validateScope(tokReqMsgCtx); + // Execute internal console scopes validation. + if (IdentityUtil.isSystemRolesEnabled()) { + RoleBasedInternalScopeValidator roleBasedInternalScopeValidator = + new RoleBasedInternalScopeValidator(); + String[] roleBasedInternalConsoleScopes = roleBasedInternalScopeValidator + .validateScope(tokReqMsgCtx); + authorizedInternalScopes = (String[]) ArrayUtils + .addAll(authorizedInternalScopes, roleBasedInternalConsoleScopes); + } + } else { + // Engage new scope validator + authorizedScopes = getAuthorizedScopes(tokReqMsgCtx); } if (isManagementApp && GrantType.CLIENT_CREDENTIALS.toString().equals(grantType) && ArrayUtils.contains(requestedScopes, SYSTEM_SCOPE)) { @@ -705,10 +717,15 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I Those scopes should not send to the other scopes validators. Thus remove the scopes from the tokReqMsgCtx. Will be added to the response after executing the other scope validators. */ - removeInternalScopes(tokReqMsgCtx); + if (AuthzUtil.isLegacyAuthzRuntime()) { + removeInternalScopes(tokReqMsgCtx); + + // Adding the authorized internal scopes to tokReqMsgCtx for any special validators to use. + tokReqMsgCtx.setAuthorizedInternalScopes(authorizedInternalScopes); + } else { + removeAuthorizedScopes(tokReqMsgCtx, authorizedScopes); + } - // Adding the authorized internal scopes to tokReqMsgCtx for any special validators to use. - tokReqMsgCtx.setAuthorizedInternalScopes(authorizedInternalScopes); boolean isDropUnregisteredScopes = OAuthServerConfiguration.getInstance().isDropUnregisteredScopes(); if (isDropUnregisteredScopes) { @@ -725,7 +742,11 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I boolean isValidScope = authzGrantHandler.validateScope(tokReqMsgCtx); if (isValidScope) { // Add authorized internal scopes to the request for sending in the response. - addAuthorizedInternalScopes(tokReqMsgCtx, tokReqMsgCtx.getAuthorizedInternalScopes()); + if (AuthzUtil.isLegacyAuthzRuntime()) { + addAuthorizedInternalScopes(tokReqMsgCtx, tokReqMsgCtx.getAuthorizedInternalScopes()); + } else { + addAuthorizedScopes(tokReqMsgCtx, authorizedScopes); + } addAllowedScopes(tokReqMsgCtx, requestedAllowedScopes.toArray(new String[0])); if (LoggerUtils.isDiagnosticLogsEnabled()) { LoggerUtils.triggerDiagnosticLogEvent(new DiagnosticLog.DiagnosticLogBuilder( @@ -744,6 +765,13 @@ private boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) throws I return isValidScope; } + private List getAuthorizedScopes(OAuthTokenReqMessageContext tokReqMsgCtx) + throws IdentityOAuth2Exception { + + DefaultOAuth2ScopeValidator scopeValidator = new DefaultOAuth2ScopeValidator(); + return scopeValidator.validateScope(tokReqMsgCtx); + } + private List getScopeList(String[] scopes) { return ArrayUtils.isEmpty(scopes) ? Collections.emptyList() : Arrays.asList(scopes); @@ -925,6 +953,19 @@ private void addAuthorizedInternalScopes(OAuthTokenReqMessageContext tokReqMsgCt .distinct().toArray(String[]::new)); } + private void addAuthorizedScopes(OAuthTokenReqMessageContext tokReqMsgCtx, List authorizedScopes) { + + String[] scopes = tokReqMsgCtx.getScope(); + if (scopes == null) { + scopes = new String[0]; + } + if (authorizedScopes == null) { + authorizedScopes = new ArrayList<>(); + } + tokReqMsgCtx.setScope(Stream.concat(Arrays.stream(scopes), authorizedScopes.stream()) + .distinct().toArray(String[]::new)); + } + private void addRequestedOIDCScopes(OAuthTokenReqMessageContext tokReqMsgCtx, String[] requestedOIDCScopes) { @@ -960,6 +1001,20 @@ private void removeInternalScopes(OAuthTokenReqMessageContext tokReqMsgCtx) { tokReqMsgCtx.setScope(scopes.toArray(new String[0])); } + private void removeAuthorizedScopes(OAuthTokenReqMessageContext tokReqMsgCtx, List authorizedScopes) { + + if (tokReqMsgCtx.getScope() == null) { + return; + } + List scopes = new ArrayList<>(); + for (String scope : tokReqMsgCtx.getScope()) { + if (!authorizedScopes.contains(scope) && !scope.equalsIgnoreCase(SYSTEM_SCOPE)) { + scopes.add(scope); + } + } + tokReqMsgCtx.setScope(scopes.toArray(new String[0])); + } + /** * Handle token binding for the grant type. * diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java new file mode 100644 index 00000000000..9fdc6b7893d --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/AuthzUtil.java @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2023, 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.util; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.CarbonConstants; +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.application.authentication.framework.util.FrameworkConstants; +import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.common.model.ClaimMapping; +import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.role.v2.mgt.core.exception.IdentityRoleManagementException; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.user.core.NotImplementedException; +import org.wso2.carbon.user.core.common.AbstractUserStoreManager; +import org.wso2.carbon.user.core.common.Group; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.user.core.util.UserCoreUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.APPLICATION; +import static org.wso2.carbon.identity.role.v2.mgt.core.RoleConstants.ORGANIZATION; +import static org.wso2.carbon.user.core.UserCoreConstants.APPLICATION_DOMAIN; +import static org.wso2.carbon.user.core.UserCoreConstants.INTERNAL_DOMAIN; + +/** + * Utility methods for the authorization related functionality. + */ +public class AuthzUtil { + + private static final Log LOG = LogFactory.getLog(AuthzUtil.class); + + /** + * Get the user roles. + * + * @param authenticatedUser AuthenticatedUser. + * @return User roles. + * @throws IdentityOAuth2Exception if an error occurs while retrieving user roles. + */ + public static List getUserRoles(AuthenticatedUser authenticatedUser, String appId) + throws IdentityOAuth2Exception { + + if (authenticatedUser.isFederatedUser()) { + if (StringUtils.isNotBlank(authenticatedUser.getAccessingOrganization())) { + if (!authenticatedUser.getAccessingOrganization() + .equals(authenticatedUser.getUserResidentOrganization())) { + // Handle switching organization scenario. + return getSwitchUserRoles(authenticatedUser); + } + } + // Handler federated user scenario. + return getFederatedUserRoles(authenticatedUser, appId); + } + if (StringUtils.isNotBlank(authenticatedUser.getAccessingOrganization())) { + if (!authenticatedUser.getAccessingOrganization() + .equals(authenticatedUser.getUserResidentOrganization())) { + return getSwitchUserRoles(authenticatedUser); + } + } + return getRoles(getUserId(authenticatedUser), authenticatedUser.getTenantDomain()); + } + + /** + * Get switching user roles. + * + * @param authenticatedUser Authenticated User. + * @return Switching user roles. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of switching user. + */ + private static List getSwitchUserRoles(AuthenticatedUser authenticatedUser) throws IdentityOAuth2Exception { + + String accessingTenantDomain = getAccessingTenantDomain(authenticatedUser); + String accessingUserId = getAccessingUserId(authenticatedUser); + return getRoles(accessingUserId, accessingTenantDomain); + } + + /** + * Get the role ids. + * + * @param userId User ID. + * @param tenantDomain Tenant domain. + * @return Role ids of user. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of user. + */ + private static List getRoles(String userId, String tenantDomain) throws IdentityOAuth2Exception { + + List roleIds = new ArrayList<>(getRoleIdsOfUser(userId, tenantDomain)); + List groups = getUserGroups(userId, tenantDomain); + if (!groups.isEmpty()) { + roleIds.addAll(getRoleIdsOfGroups(groups, tenantDomain)); + } + return roleIds; + } + + /** + * Get the federated role ids. + * + * @param authenticatedUser Authenticated user. + * @return Federated role ids of user. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of user. + */ + private static List getFederatedUserRoles(AuthenticatedUser authenticatedUser, String appId) + throws IdentityOAuth2Exception { + + String tenantDomain = authenticatedUser.getTenantDomain(); + String roleNamesString = null; + Map claimMappingStringMap = authenticatedUser.getUserAttributes(); + if (claimMappingStringMap == null) { + return new ArrayList<>(); + } + for (Map.Entry entry : claimMappingStringMap.entrySet()) { + if (FrameworkConstants.LOCAL_ROLE_CLAIM_URI.equals(entry.getKey().getLocalClaim().getClaimUri())) { + roleNamesString = entry.getValue(); + break; + } + } + List roleNames = null; + if (StringUtils.isNotBlank(roleNamesString)) { + roleNames = Arrays.asList(roleNamesString.split(FrameworkUtils.getMultiAttributeSeparator())); + } + if (roleNames == null || roleNames.isEmpty()) { + return new ArrayList<>(); + } + + String allowedAppAudience = getApplicationAllowedAudience(appId, tenantDomain); + if (ORGANIZATION.equalsIgnoreCase(allowedAppAudience)) { + + return getRoleIdsFromNames(roleNames, ORGANIZATION, getOrganizationId(tenantDomain), tenantDomain); + } + return getRoleIdsFromNames(roleNames, APPLICATION, appId, tenantDomain); + } + + /** + * Get accessing tenant domain of authenticated user. + * + * @param authenticatedUser Authenticated user. + * @return Accessing tenant domain. + * @throws IdentityOAuth2Exception if an error occurs while retrieving accessing tenant domain. + */ + private static String getAccessingTenantDomain(AuthenticatedUser authenticatedUser) throws IdentityOAuth2Exception { + try { + return OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveTenantDomain(authenticatedUser.getAccessingOrganization()); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving accessing tenant domain", e); + } + } + + /** + * Get accessing user id of authenticated user. + * + * @param authenticatedUser Authenticated user. + * @return Accessing tenant domain. + * @throws IdentityOAuth2Exception if an error occurs while retrieving accessing user id. + */ + private static String getAccessingUserId(AuthenticatedUser authenticatedUser) throws IdentityOAuth2Exception { + + // TODO: resolve accessing user id. + return getUserId(authenticatedUser); + } + + /** + * Get the associated scopes for the roles. + * + * @param roles Roles. + * @param tenantDomain Tenant domain. + * @return List of associated scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving scope list of roles. + */ + public static List getAssociatedScopesForRoles(List roles, String tenantDomain) + throws IdentityOAuth2Exception { + + try { + return OAuth2ServiceComponentHolder.getInstance().getRoleManagementServiceV2() + .getPermissionListOfRoles(roles, tenantDomain); + } catch (IdentityRoleManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving scope list of roles : " + + StringUtils.join(roles, ",") + "tenant domain : " + tenantDomain, e); + } + } + + /** + * Get the role ids of user. + * + * @param userId User ID. + * @param tenantDomain Tenant domain. + * @return Role ids of user. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of user. + */ + private static List getRoleIdsOfUser(String userId, String tenantDomain) throws IdentityOAuth2Exception { + + try { + return OAuth2ServiceComponentHolder.getInstance().getRoleManagementServiceV2() + .getRoleIdListOfUser(userId, tenantDomain); + } catch (IdentityRoleManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving role id list of user : " + userId + + "tenant domain : " + tenantDomain, e); + } + } + + /** + * Get user id of the user + * + * @param authenticatedUser Authenticated user. + * @return User id. + * @throws IdentityOAuth2Exception if an error occurs while retrieving user id of user. + */ + private static String getUserId(AuthenticatedUser authenticatedUser) throws IdentityOAuth2Exception { + + try { + return authenticatedUser.getUserId(); + } catch (UserIdNotFoundException e) { + throw new IdentityOAuth2Exception("Error while resolving user id of user" , e); + } + } + + /** + * Get organization id + * + * @param tenantDomain Tenant domain. + * @return Organization Id. + * @throws IdentityOAuth2Exception if an error occurs while retrieving org id. + */ + private static String getOrganizationId(String tenantDomain) throws IdentityOAuth2Exception { + + try { + return OAuthComponentServiceHolder.getInstance().getOrganizationManager() + .resolveOrganizationId(tenantDomain); + } catch (OrganizationManagementException e) { + throw new IdentityOAuth2Exception("Error while resolving org id of tenant : " + tenantDomain , e); + } + } + + /** + * Get application allowed audience. + * + * @param appId App id. + * @param tenantDomain Tenant domain. + * @return Allowed audience of app. + * @throws IdentityOAuth2Exception if an error occurs while retrieving allowed audience of app. + */ + private static String getApplicationAllowedAudience(String appId, String tenantDomain) + throws IdentityOAuth2Exception { + + try { + return OAuth2ServiceComponentHolder.getApplicationMgtService() + .getAllowedAudienceForRoleAssociation(appId, tenantDomain); + } catch (IdentityApplicationManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving allowed audience of app : " + appId , e); + } + } + + /** + * Get the role ids from role names. + * + * @param roleNames Role names. + * @param tenantDomain Tenant domain. + * @param roleAudience Role audience. + * @param roleAudienceId Role audience id. + * @return Role ids of idp groups. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of idp groups. + */ + private static List getRoleIdsFromNames(List roleNames, String roleAudience, String roleAudienceId, + String tenantDomain) + throws IdentityOAuth2Exception { + + List roleIds = new ArrayList<>(); + try { + for (String roleName: roleNames) { + roleIds.add(OAuth2ServiceComponentHolder.getInstance().getRoleManagementServiceV2() + .getRoleIdByName(roleName, roleAudience, roleAudienceId, tenantDomain)); + } + } catch (IdentityRoleManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving role ids of list of role anme : " + + StringUtils.join(roleNames, ",") + "tenant domain : " + tenantDomain, e); + } + return roleIds; + } + + /** + * Get the groups of the authenticated user. + * + * @param userId User id. + * @param tenantDomain Tenant domain. + * @return - Groups of the user. + */ + private static List getUserGroups(String userId, String tenantDomain) throws IdentityOAuth2Exception { + + if (LOG.isDebugEnabled()) { + LOG.debug("Started group fetching for scope validation."); + } + List userGroups = new ArrayList<>(); + RealmService realmService = UserCoreUtil.getRealmService(); + try { + int tenantId = OAuth2Util.getTenantId(tenantDomain); + UserStoreManager userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager(); + List groups = + ((AbstractUserStoreManager) userStoreManager).getGroupListOfUser(userId, + null, null); + for (Group group : groups) { + String groupName = group.getGroupName(); + String groupDomainName = UserCoreUtil.extractDomainFromName(groupName); + if (!INTERNAL_DOMAIN.equalsIgnoreCase(groupDomainName) && + !APPLICATION_DOMAIN.equalsIgnoreCase(groupDomainName)) { + userGroups.add(group.getGroupID()); + } + } + } catch (IdentityOAuth2Exception e) { + throw new IdentityOAuth2Exception(e.getMessage(), e); + } catch (UserStoreException e) { + if (isDoGetGroupListOfUserNotImplemented(e)) { + return userGroups; + } + throw new IdentityOAuth2Exception(e.getMessage(), e); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Completed group fetching for scope validation."); + } + return userGroups; + } + + /** + * Get the role ids of groups. + * + * @param groups Groups. + * @param tenantDomain Tenant domain. + * @return Role ids of groups. + * @throws IdentityOAuth2Exception if an error occurs while retrieving role id list of groups. + */ + private static List getRoleIdsOfGroups(List groups, String tenantDomain) + throws IdentityOAuth2Exception { + + try { + return OAuth2ServiceComponentHolder.getInstance().getRoleManagementServiceV2() + .getRoleIdListOfGroups(groups, tenantDomain); + } catch (IdentityRoleManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving role id list of groups : " + + StringUtils.join(groups, ",") + "tenant domain : " + tenantDomain, e); + } + } + + + /** + * Check if the UserStoreException occurred due to the doGetGroupListOfUser method not being implemented. + * + * @param e UserStoreException. + * @return true if the UserStoreException was caused by the doGetGroupListOfUser method not being implemented, + * false otherwise. + */ + private static boolean isDoGetGroupListOfUserNotImplemented(UserStoreException e) { + + Throwable cause = e.getCause(); + while (cause != null) { + if (cause instanceof NotImplementedException) { + return true; + } + cause = cause.getCause(); + } + return false; + } + + /** + * Check whether legacy authorization runtime is enabled. + * + * @return True if legacy authorization runtime is enabled. + */ + public static boolean isLegacyAuthzRuntime() { + + return CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME; + } +} 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 eeda5802802..1637d7d6217 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 @@ -5011,7 +5011,7 @@ public static String[] extractCredentialsFromAuthzHeader(HttpServletRequest requ return OAuthUtils.decodeClientAuthenticationHeader(authorizationHeader); } - + /** * Retrieve the list of client authentication methods supported by the server. * diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/Oauth2ScopeUtils.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/Oauth2ScopeUtils.java index 6999cfbca8f..b255171003d 100644 --- a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/Oauth2ScopeUtils.java +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/util/Oauth2ScopeUtils.java @@ -55,6 +55,7 @@ public class Oauth2ScopeUtils { public static final String OAUTH_APP_DO_PROPERTY_NAME = "OAuthAppDO"; private static final String OAUTH_ENABLE_SYSTEM_LEVEL_INTERNAL_SYSTEM_SCOPE_MANAGEMENT = "OAuth.EnableSystemLevelInternalSystemScopeManagement"; + private static final String LEGACY_RBAC_SCOPE_VALIDATOR = "Role based scope validator"; public static IdentityOAuth2ScopeServerException generateServerException(Oauth2ScopeConstants.ErrorMessages error, String data) @@ -259,6 +260,12 @@ private static boolean iterateOAuth2ScopeValidators(OAuthAuthzReqMessageContext .getOAuth2ScopeValidators(); // Iterate through all available scope validators. for (OAuth2ScopeValidator validator : oAuth2ScopeValidators) { + + if (!AuthzUtil.isLegacyAuthzRuntime() && LEGACY_RBAC_SCOPE_VALIDATOR.equals(validator + .getValidatorName())) { + appScopeValidators.remove(validator.getValidatorName()); + continue; + } // Validate the scopes from the validator only if it's configured in the OAuth app. if (validator != null && appScopeValidators.contains(validator.getValidatorName())) { if (log.isDebugEnabled()) { 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 new file mode 100644 index 00000000000..5a8649debb1 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/DefaultOAuth2ScopeValidator.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2023, 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; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.api.resource.mgt.APIResourceMgtException; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.common.model.AuthorizedScopes; +import org.wso2.carbon.identity.application.common.model.Scope; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.oauth.IdentityOAuthAdminException; +import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +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.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +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.ScopeValidationHandlerException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.wso2.carbon.identity.oauth2.Oauth2ScopeConstants.SYSTEM_SCOPE; +import static org.wso2.carbon.identity.oauth2.util.OAuth2Util.INTERNAL_LOGIN_SCOPE; + +/** + * Default oauth2 scope validator which validate application authorized scopes. + */ +public class DefaultOAuth2ScopeValidator { + + public static final String CLIENT_TYPE = "oauth2"; + + private static final Log LOG = LogFactory.getLog(DefaultOAuth2ScopeValidator.class); + + private static final String NO_POLICY_HANDLER = "NoPolicyScopeValidationHandler"; + + /** + * Validate scope. + * + * @param authzReqMessageContext AuthzReqMessageContext. + * @return List of scopes. + * @throws IdentityOAuth2Exception Error when performing the scope validation. + */ + public List validateScope(OAuthAuthzReqMessageContext authzReqMessageContext) + throws IdentityOAuth2Exception { + + if (isScopesEmpty(authzReqMessageContext.getAuthorizationReqDTO().getScopes())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Requested scope list is empty. Therefore, default OAuth2 scope validation is skipped."); + } + return new ArrayList<>(); + } + List requestedScopes = Arrays.asList(authzReqMessageContext.getAuthorizationReqDTO().getScopes()); + String tenantDomain = authzReqMessageContext.getAuthorizationReqDTO().getTenantDomain(); + String clientId = authzReqMessageContext.getAuthorizationReqDTO().getConsumerKey(); + String appId = getApplicationId(clientId, tenantDomain); + List authorizedScopes = getAuthorizedScopes(requestedScopes, authzReqMessageContext + .getAuthorizationReqDTO().getUser(), appId, null, tenantDomain); + removeRegisteredScopes(authzReqMessageContext); + return authorizedScopes; + } + + /** + * Validate scope. + * + * @param tokenReqMessageContext tokenReqMessageContext. + * @return List of scopes. + * @throws IdentityOAuth2Exception Error when performing the scope validation. + */ + public List validateScope(OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (isScopesEmpty(tokenReqMessageContext.getScope())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Requested scope list is empty. Therefore, default OAuth2 scope validation is skipped."); + } + return new ArrayList<>(); + } + List requestedScopes = Arrays.asList(tokenReqMessageContext.getScope()); + String tenantDomain = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getTenantDomain(); + String clientId = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getClientId(); + String appId = getApplicationId(clientId, tenantDomain); + String grantType = tokenReqMessageContext.getOauth2AccessTokenReqDTO().getGrantType(); + List authorizedScopes = getAuthorizedScopes(requestedScopes, tokenReqMessageContext + .getAuthorizedUser(), appId, grantType, tenantDomain); + removeRegisteredScopes(tokenReqMessageContext); + if (OAuthConstants.GrantTypes.CLIENT_CREDENTIALS.equals(grantType) && authorizedScopes.contains(SYSTEM_SCOPE)) { + authorizedScopes.remove(INTERNAL_LOGIN_SCOPE); + } + return authorizedScopes; + } + + /** + * Get authorized scopes. + * + * @param requestedScopes Requested scopes. + * @param authenticatedUser Authenticated user. + * @param appId App ID. + * @param grantType Grant type. + * @param tenantDomain Tenant domain. + * @return Authorized scopes. + * @throws IdentityOAuth2Exception if any error occurs during getting authorized scopes. + */ + private List getAuthorizedScopes(List requestedScopes, AuthenticatedUser authenticatedUser, + String appId, String grantType, String tenantDomain) + throws IdentityOAuth2Exception { + + // Filter OIDC scopes and add to approved scopes list. + if (LOG.isDebugEnabled()) { + LOG.debug("Filtering OIDC scopes from requested scopes: " + StringUtils.join(requestedScopes, " ")); + } + Set requestedOIDCScopes = getRequestedOIDCScopes(tenantDomain, requestedScopes); + if (LOG.isDebugEnabled()) { + LOG.debug("Requested OIDC scopes : " + StringUtils.join(requestedOIDCScopes, " ")); + } + /* Here, we add the user-requested OIDC scopes to the approved scope list and remove from requested scope list + before we pass the scopes to the authorization service. Otherwise, the OIDC scopes will be dropped from + the approved scope list. */ + List approvedScopes = new ArrayList<>(requestedOIDCScopes); + requestedScopes = removeOIDCScopes(requestedScopes, requestedOIDCScopes); + if (requestedScopes.contains(SYSTEM_SCOPE)) { + requestedScopes.addAll(getInternalScopes(tenantDomain)); + requestedScopes.addAll(getConsoleScopes(tenantDomain)); + } + List authorizedScopesList = getAuthorizedScopes(appId, tenantDomain); + List scopeValidationHandlers = + OAuthComponentServiceHolder.getInstance().getScopeValidationHandlers(); + Map> validatedScopesByHandler = new HashMap<>(); + for (AuthorizedScopes authorizedScopes : authorizedScopesList) { + String policyId = authorizedScopes.getPolicyId(); + ScopeValidationContext scopeValidationContext = new ScopeValidationContext(); + scopeValidationContext.setAuthenticatedUser(authenticatedUser); + scopeValidationContext.setAppId(appId); + scopeValidationContext.setPolicyId(policyId); + scopeValidationContext.setGrantType(grantType); + for (ScopeValidationHandler scopeValidationHandler : scopeValidationHandlers) { + if (scopeValidationHandler.canHandle(scopeValidationContext)) { + scopeValidationContext.setValidatedScopesByHandler(validatedScopesByHandler); + List validatedScopes; + try { + validatedScopes = scopeValidationHandler.validateScopes(requestedScopes, + authorizedScopes.getScopes(), scopeValidationContext); + } catch (ScopeValidationHandlerException e) { + throw new IdentityOAuth2Exception("Error while validating policies roles from " + + "authorization service.", e); + } + validatedScopesByHandler.put(scopeValidationHandler.getName(), validatedScopes); + } + } + } + + // If "NoPolicyScopeValidationHandler" exists, add all its scopes to the result + Set scopes = new HashSet<>(validatedScopesByHandler.getOrDefault(NO_POLICY_HANDLER, + Collections.emptyList())); + + // Separate "NoPolicyScopeValidationHandler" and get the intersection of the rest of the scopes validated + // by other validators + List> otherHandlerScopes = new ArrayList<>(validatedScopesByHandler.values()); + otherHandlerScopes.remove(validatedScopesByHandler.get(NO_POLICY_HANDLER)); + + List intersection = new ArrayList<>(); + if (!otherHandlerScopes.isEmpty()) { + intersection = otherHandlerScopes.get(0); + for (int i = 1; i < otherHandlerScopes.size(); i++) { + intersection = intersection.stream().filter(otherHandlerScopes.get(i)::contains) + .collect(Collectors.toList()); + } + } + scopes.addAll(intersection); + approvedScopes.addAll(scopes); + return approvedScopes; + } + + /** + * Get the authorized scopes for the given appId and tenant domain. + * + * @param appId App id. + * @param tenantDomain Tenant domain. + * @return Authorized scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving authorized scopes for app. + */ + private List getAuthorizedScopes(String appId, String tenantDomain) + throws IdentityOAuth2Exception { + + try { + return OAuth2ServiceComponentHolder.getInstance() + .getAuthorizedAPIManagementService().getAuthorizedScopes(appId, tenantDomain); + } catch (IdentityApplicationManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving authorized scopes for app : " + appId + + "tenant domain : " + tenantDomain, e); + } + } + + /** + * Get the internal scopes. + * + * @param tenantDomain Tenant domain. + * @return Internal scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving internal scopes for tenant domain. + */ + private List getInternalScopes(String tenantDomain) throws IdentityOAuth2Exception { + + try { + List scopes = OAuth2ServiceComponentHolder.getInstance() + .getApiResourceManager().getScopesByTenantDomain(tenantDomain, "name sw internal_"); + return scopes.stream().map(Scope::getName).collect(Collectors.toCollection(ArrayList::new)); + } catch (APIResourceMgtException e) { + throw new IdentityOAuth2Exception("Error while retrieving internal scopes for tenant domain : " + + tenantDomain, e); + } + } + + /** + * Get the Console scopes. + * + * @param tenantDomain Tenant domain. + * @return Console scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving console scopes for tenant domain. + */ + private List getConsoleScopes(String tenantDomain) throws IdentityOAuth2Exception { + + try { + List scopes = OAuth2ServiceComponentHolder.getInstance() + .getApiResourceManager().getScopesByTenantDomain(tenantDomain, "name sw console:"); + return scopes.stream().map(Scope::getName).collect(Collectors.toCollection(ArrayList::new)); + } catch (APIResourceMgtException e) { + throw new IdentityOAuth2Exception("Error while retrieving console scopes for tenant domain : " + + tenantDomain, e); + } + } + + /** + * Get the registered scopes. + * + * @param tenantDomain Tenant domain. + * @return Registered scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving internal scopes for tenant domain. + */ + private List getRegisteredScopes(String tenantDomain) throws IdentityOAuth2Exception { + + try { + List scopes = OAuth2ServiceComponentHolder.getInstance() + .getApiResourceManager().getScopesByTenantDomain(tenantDomain, null); + return scopes.stream().map(Scope::getName).collect(Collectors.toCollection(ArrayList::new)); + } catch (APIResourceMgtException e) { + throw new IdentityOAuth2Exception("Error while retrieving internal scopes for tenant domain : " + + tenantDomain, e); + } + } + + /** + * Remove registered scopes. + * + * @param authzReqMessageContext OAuthAuthzReqMessageContext + * @throws IdentityOAuth2Exception Error while remove registered scopes. + */ + private void removeRegisteredScopes(OAuthAuthzReqMessageContext authzReqMessageContext) + throws IdentityOAuth2Exception { + + if (authzReqMessageContext.getAuthorizationReqDTO().getScopes() == null) { + return; + } + List registeredScopes = getRegisteredScopes(authzReqMessageContext.getAuthorizationReqDTO() + .getTenantDomain()); + List scopes = new ArrayList<>(); + for (String scope : authzReqMessageContext.getAuthorizationReqDTO().getScopes()) { + if (!registeredScopes.contains(scope)) { + scopes.add(scope); + } + } + authzReqMessageContext.getAuthorizationReqDTO().setScopes(scopes.toArray(new String[0])); + } + + /** + * Remove registered scopes. + * + * @param tokenReqMessageContext OAuthTokenReqMessageContext + * @throws IdentityOAuth2Exception Error while remove registered scopes. + */ + private void removeRegisteredScopes(OAuthTokenReqMessageContext tokenReqMessageContext) + throws IdentityOAuth2Exception { + + if (tokenReqMessageContext.getScope() == null) { + return; + } + List registeredScopes = getRegisteredScopes(tokenReqMessageContext.getOauth2AccessTokenReqDTO() + .getTenantDomain()); + List scopes = new ArrayList<>(); + for (String scope : tokenReqMessageContext.getScope()) { + if (!registeredScopes.contains(scope)) { + scopes.add(scope); + } + } + tokenReqMessageContext.setScope(scopes.toArray(new String[0])); + } + + /** + * Get the requested OIDC scopes + * + * @param tenantDomain Tenant domain. + * @param requestedScopes Requested scopes. + * @return Requested OIDC scopes. + * @throws IdentityOAuth2Exception if an error occurs while retrieving oidc scopes. + */ + private Set getRequestedOIDCScopes(String tenantDomain, List requestedScopes) + throws IdentityOAuth2Exception { + + OAuthAdminServiceImpl oAuthAdminServiceImpl = OAuth2ServiceComponentHolder.getInstance().getOAuthAdminService(); + try { + List oidcScopes = oAuthAdminServiceImpl.getRegisteredOIDCScope(tenantDomain); + return requestedScopes.stream().distinct().filter(oidcScopes::contains).collect(Collectors.toSet()); + } catch (IdentityOAuthAdminException e) { + throw new IdentityOAuth2Exception("Error while retrieving oidc scopes for tenant domain : " + + tenantDomain, e); + } + } + + /** + * Remove OIDC scopes from the list. + * + * @param requestedScopes Requested scopes. + * @param oidcScopes OIDC scopes. + * @return List of scopes. + */ + private List removeOIDCScopes(List requestedScopes, Set oidcScopes) { + + return requestedScopes.stream().distinct().filter(s -> !oidcScopes.contains(s)).collect(Collectors.toList()); + } + + /** + * Get the application resource id for the given client id + * + * @param clientId Client Id. + * @param tenantName Tenant name. + * @return Application resource id. + * @throws IdentityOAuth2Exception if an error occurs while retrieving application resource id. + */ + private String getApplicationId(String clientId, String tenantName) throws IdentityOAuth2Exception { + + ApplicationManagementService applicationMgtService = OAuth2ServiceComponentHolder.getApplicationMgtService(); + try { + return applicationMgtService.getApplicationResourceIDByInboundKey(clientId, CLIENT_TYPE, tenantName); + } catch (IdentityApplicationManagementException e) { + throw new IdentityOAuth2Exception("Error while retrieving application resource id for client : " + + clientId + " tenant : " + tenantName, e); + } + } + + /** + * Checks if the scopes list is empty + * + * @param scopes Scopes list + * @return true if scopes list is empty + */ + private boolean isScopesEmpty(String[] scopes) { + + return ArrayUtils.isEmpty(scopes); + } + +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationContext.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationContext.java new file mode 100644 index 00000000000..b7736493c1f --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationContext.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023, 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; + +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; + +import java.util.List; +import java.util.Map; + +/** + * Scope Validation Context is where we pass scope validation context to the scope validation handlers. + */ +public class ScopeValidationContext { + + private AuthenticatedUser authenticatedUser; + private String appId; + private String grantType; + + private String policyId; + private Map> validatedScopesByHandler; + + /** + * Get the authenticated user. + * + * @return AuthenticatedUser. + */ + + public AuthenticatedUser getAuthenticatedUser() { + + return authenticatedUser; + } + + /** + * Set the authenticated user. + * + * @param authenticatedUser AuthenticatedUser. + */ + public void setAuthenticatedUser(AuthenticatedUser authenticatedUser) { + + this.authenticatedUser = authenticatedUser; + } + + /** + * Get the application id. + * + * @return Application ID. + */ + public String getAppId() { + + return appId; + } + + /** + * Set the application id. + * + * @param appId Application ID. + */ + public void setAppId(String appId) { + + this.appId = appId; + } + + /** + * Get the validated scopes by handler + * + * @return Map of validated scopes. + */ + public Map> getValidatedScopesByHandler() { + + return validatedScopesByHandler; + } + + /** + * Set the validated scopes by handler. + * + * @param validatedScopesByHandler Map of validated scopes. + */ + public void setValidatedScopesByHandler(Map> validatedScopesByHandler) { + + this.validatedScopesByHandler = validatedScopesByHandler; + } + + /** + * Get the grant type. + * + * @return Grant type. + */ + public String getGrantType() { + + return grantType; + } + + /** + * Set the grant type. + * + * @param grantType Grant type. + */ + public void setGrantType(String grantType) { + + this.grantType = grantType; + } + + /** + * Get the policy id. + * + * @return Policy ID. + */ + public String getPolicyId() { + + return policyId; + } + + /** + * Set the policy id. + * + * @param policyId Policy ID. + */ + public void setPolicyId(String policyId) { + + this.policyId = policyId; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandler.java new file mode 100644 index 00000000000..7ab73cdfc0d --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 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; + +import java.util.List; + +/** + * Each scope validation handler for authorized policies should implement this. + */ +public interface ScopeValidationHandler { + + /** + * Check if the handler can handle the scope validation + * + * @param scopeValidationContext ScopeValidationContext. + * @return boolean + */ + boolean canHandle(ScopeValidationContext scopeValidationContext); + + /** + * Validate scopes. + * + * @param requestedScopes Requested scopes. + * @param appAuthorizedScopes Authorized scopes. + * @param scopeValidationContext ScopeValidationContext. + * @return List of scopes. + * @throws ScopeValidationHandlerException Error when performing the scope validation. + */ + List validateScopes(List requestedScopes, List appAuthorizedScopes, + ScopeValidationContext scopeValidationContext) throws ScopeValidationHandlerException; + + /** + * Get policy ID. + * + * @return Policy ID. + */ + String getPolicyID(); + + /** + * Get handler name. + * + * @return Handler name. + */ + + String getName(); + +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerException.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerException.java new file mode 100644 index 00000000000..ad5411cacb5 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/ScopeValidationHandlerException.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023, 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; + +/** + * ScopeValidatorPolicyHandlerException + */ +public class ScopeValidationHandlerException extends Exception { + + /** + * Constructs a new exception with an error message. + * + * @param message The detail message. + */ + public ScopeValidationHandlerException(String message) { + + super(message); + } + + /** + * Constructs a new exception with the message and cause. + * + * @param message The detail message. + * @param cause The cause. + */ + public ScopeValidationHandlerException(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/M2MScopeValidationHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/M2MScopeValidationHandler.java new file mode 100644 index 00000000000..f59291075c3 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/M2MScopeValidationHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 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.impl; + +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +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.ScopeValidationHandlerException; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * M2M scope validation handler engage for client credential grant to validate scopes. + */ +public class M2MScopeValidationHandler implements ScopeValidationHandler { + + @Override + public boolean canHandle(ScopeValidationContext scopeValidationContext) { + + return OAuthConstants.GrantTypes.CLIENT_CREDENTIALS.equals(scopeValidationContext.getGrantType()) && + scopeValidationContext.getPolicyId().equals("RBAC"); + } + + @Override + public List validateScopes(List requestedScopes, List appAuthorizedScopes, + ScopeValidationContext scopeValidationContext) + throws ScopeValidationHandlerException { + + return requestedScopes.stream().filter(appAuthorizedScopes::contains).collect(Collectors.toList()); + } + + @Override + public String getPolicyID() { + + return "M2M"; + } + + @Override + public String getName() { + + return "M2MScopeValidationHandler"; + } +} diff --git a/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/NoPolicyScopeValidationHandler.java b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/NoPolicyScopeValidationHandler.java new file mode 100644 index 00000000000..48a38407a8b --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/NoPolicyScopeValidationHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 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.impl; + +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.ScopeValidationHandlerException; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * No policy scope validation handler handle authorized no policy scopes. + */ +public class NoPolicyScopeValidationHandler implements ScopeValidationHandler { + + @Override + public boolean canHandle(ScopeValidationContext scopeValidationContext) { + + return getPolicyID().equals(scopeValidationContext.getPolicyId()); + } + + @Override + public List validateScopes(List requestedScopes, List appAuthorizedScopes, + ScopeValidationContext scopeValidationContext) + throws ScopeValidationHandlerException { + + return requestedScopes.stream().filter(appAuthorizedScopes::contains).collect(Collectors.toList()); + } + + @Override + public String getPolicyID() { + + return "NO POLICY"; + } + + @Override + public String getName() { + + return "NoPolicyScopeValidationHandler"; + } +} 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 new file mode 100644 index 00000000000..c1194c26389 --- /dev/null +++ b/components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/validators/validationhandler/impl/RoleBasedScopeValidationHandler.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2023, 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.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.common.model.RoleV2; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder; +import org.wso2.carbon.identity.oauth2.util.AuthzUtil; +import org.wso2.carbon.identity.oauth2.validators.DefaultOAuth2ScopeValidator; +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.ScopeValidationHandlerException; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Role based scope validation handler validate scopes based on users roles. + */ +public class RoleBasedScopeValidationHandler implements ScopeValidationHandler { + + private static final Log LOG = LogFactory.getLog(DefaultOAuth2ScopeValidator.class); + + @Override + public boolean canHandle(ScopeValidationContext scopeValidationContext) { + + return getPolicyID().equals(scopeValidationContext.getPolicyId()) + && !OAuthConstants.GrantTypes.CLIENT_CREDENTIALS.equals(scopeValidationContext.getGrantType()); + } + + @Override + public List validateScopes(List requestedScopes, List appAuthorizedScopes, + ScopeValidationContext scopeValidationContext) + throws ScopeValidationHandlerException { + + try { + List userRoles = AuthzUtil.getUserRoles(scopeValidationContext.getAuthenticatedUser(), + scopeValidationContext.getAppId()); + if (userRoles.isEmpty()) { + return new ArrayList<>(); + } + List filteredRoleIds = getFilteredRoleIds(userRoles, scopeValidationContext.getAppId(), + scopeValidationContext.getAuthenticatedUser().getTenantDomain()); + if (filteredRoleIds.isEmpty()) { + return new ArrayList<>(); + } + List associatedScopes = AuthzUtil.getAssociatedScopesForRoles(filteredRoleIds, + scopeValidationContext.getAuthenticatedUser().getTenantDomain()); + List filteredScopes = appAuthorizedScopes.stream().filter(associatedScopes::contains) + .collect(Collectors.toList()); + return requestedScopes.stream().filter(filteredScopes::contains).collect(Collectors.toList()); + } catch (IdentityOAuth2Exception e) { + throw new ScopeValidationHandlerException("Error while validation scope with RBAC Scope Validation " + + "handler", e); + } + } + + /** + * Get the filtered role ids. + * + * @param roleId Role id list. + * @param appId App id. + * @param tenantDomain Tenant domain. + * @return Filtered role ids. + * @throws ScopeValidationHandlerException if an error occurs while retrieving filtered role id list. + */ + private List getFilteredRoleIds(List roleId, String appId, String tenantDomain) + throws ScopeValidationHandlerException { + + List rolesAssociatedWithApp = getRoleIdsAssociatedWithApp(appId, tenantDomain); + return roleId.stream().distinct().filter(rolesAssociatedWithApp::contains).collect(Collectors.toList()); + } + + /** + * Get the role ids associated with app. + * + * @param appId App id. + * @param tenantDomain Tenant domain. + * @return Role ids associated with app. + * @throws ScopeValidationHandlerException if an error occurs while retrieving role id list of app. + */ + private List getRoleIdsAssociatedWithApp(String appId, String tenantDomain) + throws ScopeValidationHandlerException { + + try { + return OAuth2ServiceComponentHolder.getApplicationMgtService() + .getAssociatedRolesOfApplication(appId, tenantDomain).stream().map(RoleV2::getId) + .collect(Collectors.toCollection(ArrayList::new)); + } catch (IdentityApplicationManagementException e) { + throw new ScopeValidationHandlerException("Error while retrieving role id list of app : " + appId + + "tenant domain : " + tenantDomain, e); + } + } + + @Override + public String getPolicyID() { + + return "RBAC"; + } + + @Override + public String getName() { + + return "RoleBasedScopeValidationHandler"; + } +} diff --git a/pom.xml b/pom.xml index f8c07d8e295..486139b7f08 100644 --- a/pom.xml +++ b/pom.xml @@ -685,7 +685,16 @@ test - + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.api.resource.mgt + ${carbon.identity.framework.version} + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.role.v2.mgt.core + ${carbon.identity.framework.version} + @@ -857,7 +866,7 @@ 1.2.4 - 4.9.10 + 4.9.16 4.9.7 [4.5.0, 5.0.0) [1.0.1, 2.0.0)