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