diff --git a/components/org.wso2.carbon.identity.auth.service/pom.xml b/components/org.wso2.carbon.identity.auth.service/pom.xml index 46eeca47..712737ba 100644 --- a/components/org.wso2.carbon.identity.auth.service/pom.xml +++ b/components/org.wso2.carbon.identity.auth.service/pom.xml @@ -70,6 +70,14 @@ org.wso2.carbon.identity.event.handler.accountlock org.wso2.carbon.identity.handler.event.account.lock + + org.json.wso2 + json + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.central.log.mgt + org.testng testng diff --git a/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/handler/impl/OAuth2AccessTokenHandler.java b/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/handler/impl/OAuth2AccessTokenHandler.java index 060d21be..24782d49 100644 --- a/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/handler/impl/OAuth2AccessTokenHandler.java +++ b/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/handler/impl/OAuth2AccessTokenHandler.java @@ -18,12 +18,16 @@ package org.wso2.carbon.identity.auth.service.handler.impl; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import org.apache.catalina.connector.Request; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHeaders; +import org.json.JSONObject; import org.slf4j.MDC; +import org.wso2.carbon.CarbonConstants; 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.common.model.ProvisioningServiceProviderType; @@ -51,14 +55,11 @@ import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.identity.oauth2.validators.RefreshTokenValidator; +import java.text.ParseException; +import java.util.Map; import java.util.Optional; import static org.wso2.carbon.identity.auth.service.util.AuthConfigurationUtil.isAuthHeaderMatch; -import static org.wso2.carbon.identity.auth.service.util.Constants.AUTHENTICATION_TYPE; -import static org.wso2.carbon.identity.auth.service.util.Constants.IDP_NAME; -import static org.wso2.carbon.identity.auth.service.util.Constants.IS_FEDERATED_USER; -import static org.wso2.carbon.identity.auth.service.util.Constants.OAUTH2_ALLOWED_SCOPES; -import static org.wso2.carbon.identity.auth.service.util.Constants.OAUTH2_VALIDATE_SCOPE; import static org.wso2.carbon.identity.oauth2.OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER; /** @@ -69,6 +70,7 @@ public class OAuth2AccessTokenHandler extends AuthenticationHandler { private static final Log log = LogFactory.getLog(OAuth2AccessTokenHandler.class); + private static final Log AUDIT = CarbonConstants.AUDIT_LOG; private final String OAUTH_HEADER = "Bearer"; private final String CONSUMER_KEY = "consumer-key"; private final String SERVICE_PROVIDER = "serviceProvider"; @@ -117,7 +119,7 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) { oAuth2TokenValidationService.buildIntrospectionResponse(requestDTO); IdentityUtil.threadLocalProperties.get() - .put(AUTHENTICATION_TYPE, oAuth2IntrospectionResponseDTO.getAut()); + .put(Constants.AUTHENTICATION_TYPE, oAuth2IntrospectionResponseDTO.getAut()); if (!oAuth2IntrospectionResponseDTO.isActive() || RefreshTokenValidator.TOKEN_TYPE_NAME.equals(oAuth2IntrospectionResponseDTO.getTokenType())) { @@ -138,6 +140,8 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) { return authenticationResult; } + handleImpersonatedAccessToken(authenticationContext, accessToken, oAuth2IntrospectionResponseDTO); + authenticationResult.setAuthenticationStatus(AuthenticationStatus.SUCCESS); User authorizedUser = oAuth2IntrospectionResponseDTO.getAuthorizedUser(); @@ -145,22 +149,23 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) { authenticationContext.setUser(authorizedUser); if (authorizedUser instanceof AuthenticatedUser) { IdentityUtil.threadLocalProperties.get() - .put(IS_FEDERATED_USER, ((AuthenticatedUser) authorizedUser).isFederatedUser()); + .put(Constants.IS_FEDERATED_USER, + ((AuthenticatedUser) authorizedUser).isFederatedUser()); IdentityUtil.threadLocalProperties.get() - .put(IDP_NAME, ((AuthenticatedUser) authorizedUser).getFederatedIdPName()); + .put(Constants.IDP_NAME, ((AuthenticatedUser) authorizedUser).getFederatedIdPName()); } else { AuthenticatedUser authenticatedUser = new AuthenticatedUser(authorizedUser); IdentityUtil.threadLocalProperties.get() - .put(IS_FEDERATED_USER, authenticatedUser.isFederatedUser()); + .put(Constants.IS_FEDERATED_USER, authenticatedUser.isFederatedUser()); IdentityUtil.threadLocalProperties.get() - .put(IDP_NAME, authenticatedUser.getFederatedIdPName()); + .put(Constants.IDP_NAME, authenticatedUser.getFederatedIdPName()); } } authenticationContext.addParameter(CONSUMER_KEY, oAuth2IntrospectionResponseDTO.getClientId()); - authenticationContext.addParameter(OAUTH2_ALLOWED_SCOPES, + authenticationContext.addParameter(Constants.OAUTH2_ALLOWED_SCOPES, OAuth2Util.buildScopeArray(oAuth2IntrospectionResponseDTO.getScope())); - authenticationContext.addParameter(OAUTH2_VALIDATE_SCOPE, + authenticationContext.addParameter(Constants.OAUTH2_VALIDATE_SCOPE, AuthConfigurationUtil.getInstance().isScopeValidationEnabled()); String serviceProvider = null; try { @@ -197,6 +202,77 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) { return authenticationResult; } + private void handleImpersonatedAccessToken(AuthenticationContext authenticationContext, + String accessToken, + OAuth2IntrospectionResponseDTO introspectionResponseDTO) { + + try { + // Extract claims from the access token + SignedJWT signedJWT = getSignedJWT(accessToken); + JWTClaimsSet claimsSet = getClaimSet(signedJWT); + String subject = resolveSubject(claimsSet); + String impersonator = resolveImpersonator(claimsSet); + + // Check if the token represents an impersonation request + if (impersonator != null) { + String scope = introspectionResponseDTO.getScope(); + String clientId = introspectionResponseDTO.getClientId(); + String requestUri = authenticationContext.getAuthenticationRequest().getRequestUri(); + String httpMethod = authenticationContext.getAuthenticationRequest().getMethod(); + + // Ensure it's not a GET request before logging + if (!Constants.GET.equals(httpMethod)) { + // Prepare data for audit log + JSONObject data = new JSONObject(); + data.put(Constants.SUBJECT, subject); + data.put(Constants.IMPERSONATOR, impersonator); + data.put(Constants.RESOURCE_PATH, requestUri); + data.put(Constants.HTTP_METHOD, httpMethod); + data.put(Constants.CLIENT_ID, clientId); + data.put(Constants.SCOPE, scope); + + String action; + + switch (httpMethod) { + case Constants.PATCH: + action = Constants.IMPERSONATION_RESOURCE_MODIFICATION; + break; + case Constants.POST: + action = Constants.IMPERSONATION_RESOURCE_CREATION; + break; + case Constants.DELETE: + action = Constants.IMPERSONATION_RESOURCE_DELETION; + break; + default: + action = Constants.IMPERSONATION_RESOURCE_ACCESS; + break; + } + // Log the audit event + AUDIT.info(createAuditMessage(impersonator, action, subject, data, Constants.AUTHORIZED)); + } + } + } catch (IdentityOAuth2Exception e) { + // Ignore IdentityOAuth2Exception since this is an audit log section + } + } + + /** + * To create an audit message based on provided parameters. + * + * @param action Activity + * @param target Target affected by this activity. + * @param data Information passed along with the request. + * @param resultField Result value. + * @return Relevant audit log in Json format. + */ + private String createAuditMessage(String subject, String action, String target, JSONObject data, String resultField) { + + String auditMessage = + Constants.INITIATOR + "=%s " + Constants.ACTION + "=%s " + Constants.TARGET + "=%s " + + Constants.DATA + "=%s " + Constants.OUTCOME + "=%s"; + return String.format(auditMessage, subject, action, target, data, resultField); + } + @Override public void init(InitConfig initConfig) { @@ -385,4 +461,66 @@ private void setProvisioningServiceProviderThreadLocal(String oauthAppConsumerKe IdentityApplicationManagementUtil.setThreadLocalProvisioningServiceProvider(provisioningServiceProvider); } } + + /** + * Get the SignedJWT by parsing the subjectToken. + * + * @param token Token sent in the request + * @return SignedJWT + * @throws IdentityOAuth2Exception Error when parsing the subjectToken + */ + private SignedJWT getSignedJWT(String token) throws IdentityOAuth2Exception { + + SignedJWT signedJWT; + if (StringUtils.isEmpty(token)) { + return null; + } + try { + signedJWT = SignedJWT.parse(token); + } catch (ParseException e) { + throw new IdentityOAuth2Exception("Error while parsing the JWT", e); + } + return signedJWT; + } + + /** + * Retrieve the JWTClaimsSet from the SignedJWT. + * + * @param signedJWT SignedJWT object + * @return JWTClaimsSet + * @throws IdentityOAuth2Exception Error when retrieving the JWTClaimsSet + */ + public static JWTClaimsSet getClaimSet(SignedJWT signedJWT) throws IdentityOAuth2Exception { + + JWTClaimsSet claimsSet = null; + try { + claimsSet = signedJWT.getJWTClaimsSet(); + } catch (ParseException e) { + throw new IdentityOAuth2Exception("Error when retrieving claimsSet from the JWT", e); + } + return claimsSet; + } + + /** + * The default implementation creates the subject from the Sub attribute. + * To translate between the federated and local user store, this may need some mapping. + * Override if needed + * + * @param claimsSet all the JWT claims + * @return The subject, to be used + */ + private String resolveSubject(JWTClaimsSet claimsSet) { + + return claimsSet.getSubject(); + } + + private String resolveImpersonator(JWTClaimsSet claimsSet) { + + if (claimsSet.getClaim(Constants.ACT) != null) { + + Map mayActClaimSet = (Map) claimsSet.getClaim(Constants.ACT); + return mayActClaimSet.get(Constants.SUB); + } + return null; + } } diff --git a/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/util/Constants.java b/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/util/Constants.java index fa6f6d03..7c925f73 100644 --- a/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/util/Constants.java +++ b/components/org.wso2.carbon.identity.auth.service/src/main/java/org/wso2/carbon/identity/auth/service/util/Constants.java @@ -57,4 +57,30 @@ public class Constants { public static final String RESOURCE_ACCESS_CONTROL_V2_FILE = "resource-access-control-v2.xml"; public static final String AUTHENTICATION_TYPE = "authenticationType"; public final static String VALIDATE_LEGACY_PERMISSIONS = "validateLegacyPermissions"; + + // Audit Log Constants. + public static final String INITIATOR = "Initiator"; + public static final String ACTION = "Action"; + public static final String TARGET = "Target"; + public static final String DATA = "Data"; + public static final String OUTCOME = "Outcome"; + + // Impersonation Constants. + public static final String ACT = "act"; + public static final String SUB = "sub"; + public static final String GET = "GET"; + public static final String POST = "POST"; + public static final String PATCH = "PATCH"; + public static final String DELETE = "DELETE"; + public static final String AUTHORIZED = "AUTHORIZED"; + public static final String IMPERSONATION_RESOURCE_MODIFICATION = "resource-modification-via-impersonation"; + public static final String IMPERSONATION_RESOURCE_ACCESS = "resource-access-via-impersonation"; + public static final String IMPERSONATION_RESOURCE_DELETION = "resource-deletion-via-impersonation"; + public static final String IMPERSONATION_RESOURCE_CREATION = "resource-creation-via-impersonation"; + public static final String SUBJECT = "subject"; + public static final String IMPERSONATOR = "impersonator"; + public static final String RESOURCE_PATH = "ResourcePath"; + public static final String HTTP_METHOD = "httpMethod"; + public static final String CLIENT_ID = "clientId"; + public static final String SCOPE = "scope"; } diff --git a/pom.xml b/pom.xml index 2b07a1c6..7f394523 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,12 @@ ${identity.framework.version} + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.central.log.mgt + ${identity.framework.version} + + org.wso2.carbon.identity.auth.rest org.wso2.carbon.identity.auth.service