diff --git a/components/org.wso2.carbon.identity.auth.service/pom.xml b/components/org.wso2.carbon.identity.auth.service/pom.xml
index 46eeca47..0d30fde6 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.orbit.com.nimbusds
+ nimbus-jose-jwt
+
org.testng
testng
@@ -204,6 +212,7 @@
org.wso2.carbon.identity.organization.management.service.exception; version="${org.wso2.carbon.identity.organization.management.core.version.range}",
org.wso2.carbon.identity.handler.event.account.lock.exception;
version="${identity.event.handler.account.lock.version.range}",
+ com.nimbusds.jwt; version="${nimbusds.osgi.version.range}",
!org.wso2.carbon.identity.auth.service.internal,
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..5c59c94b 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,89 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) {
return authenticationResult;
}
+ /**
+ * Handles the logging of impersonated access tokens. This method extracts the claims from the given
+ * access token and checks if it represents an impersonation request. If impersonation is detected,
+ * it logs the impersonation event with relevant details such as subject, impersonator, resource path,
+ * HTTP method, client ID, and scope. The method ensures that only non-GET requests are logged for audit purposes.
+ *
+ * @param authenticationContext The authentication context containing the authentication request details.
+ * @param accessToken The access token to be inspected for impersonation.
+ * @param introspectionResponseDTO The introspection response containing token details.
+ */
+ private void handleImpersonatedAccessToken(AuthenticationContext authenticationContext,
+ String accessToken,
+ OAuth2IntrospectionResponseDTO introspectionResponseDTO) {
+
+ try {
+ // Extract claims from the access token
+ SignedJWT signedJWT = getSignedJWT(accessToken);
+ JWTClaimsSet claimsSet = getClaimSet(signedJWT);
+ if (claimsSet != null) {
+ String subject = resolveSubject(claimsSet);
+ String impersonator = resolveImpersonator(claimsSet);
+ // Check if the token represents an impersonation request
+ if (impersonator != null) {
+ MDC.put(Constants.IMPERSONATOR, impersonator);
+ 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 +473,67 @@ 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);
+ return signedJWT;
+ } catch (ParseException e) {
+ throw new IdentityOAuth2Exception("Error while parsing the JWT", e);
+ }
+ }
+
+ /**
+ * 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 {
+
+ try {
+ if (signedJWT != null){
+ return signedJWT.getJWTClaimsSet();
+ }
+ return null;
+ } catch (ParseException e) {
+ throw new IdentityOAuth2Exception("Error when retrieving claimsSet from the JWT", e);
+ }
+ }
+
+ /**
+ * 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/components/org.wso2.carbon.identity.auth.valve/src/main/java/org/wso2/carbon/identity/auth/valve/AuthenticationValve.java b/components/org.wso2.carbon.identity.auth.valve/src/main/java/org/wso2/carbon/identity/auth/valve/AuthenticationValve.java
index db5ef1f3..85b308aa 100644
--- a/components/org.wso2.carbon.identity.auth.valve/src/main/java/org/wso2/carbon/identity/auth/valve/AuthenticationValve.java
+++ b/components/org.wso2.carbon.identity.auth.valve/src/main/java/org/wso2/carbon/identity/auth/valve/AuthenticationValve.java
@@ -78,6 +78,7 @@ public class AuthenticationValve extends ValveBase {
private static final String USER_AGENT = "User-Agent";
private static final String REMOTE_ADDRESS = "remoteAddress";
private static final String SERVICE_PROVIDER = "serviceProvider";
+ private static final String IMPERSONATOR = "impersonator";
private final String CLIENT_COMPONENT = "clientComponent";
private final String REST_API_CLIENT_COMPONENT = "REST API";
private static final String AUTH_USER_TENANT_DOMAIN = "authUserTenantDomain";
@@ -275,6 +276,7 @@ private void unsetMDCThreadLocals() {
MDC.remove(USER_AGENT);
MDC.remove(REMOTE_ADDRESS);
MDC.remove(SERVICE_PROVIDER);
+ MDC.remove(IMPERSONATOR);
}
private boolean isLoggableParam(String param) {
diff --git a/pom.xml b/pom.xml
index 2b07a1c6..715f7e79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -162,6 +162,11 @@
json
${json.wso2.version}
+
+ org.wso2.orbit.com.nimbusds
+ nimbus-jose-jwt
+ ${nimbusds.version}
+
org.wso2.carbon.identity.organization.management
org.wso2.carbon.identity.organization.management.authz.service
@@ -357,6 +362,9 @@
[6.2.18, 8.0.0)
+ 7.9.0.wso2v1
+ [7.3.0,8.0.0)
+
1.1.14
[1.0.0, 2.0.0)