Skip to content

Commit

Permalink
impersonation audit logs
Browse files Browse the repository at this point in the history
  • Loading branch information
Thumimku committed Jun 6, 2024
1 parent cb40459 commit 9d5a6e7
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 12 deletions.
9 changes: 9 additions & 0 deletions components/org.wso2.carbon.identity.auth.service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
<groupId>org.wso2.carbon.identity.event.handler.accountlock</groupId>
<artifactId>org.wso2.carbon.identity.handler.event.account.lock</artifactId>
</dependency>
<dependency>
<groupId>org.json.wso2</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
Expand Down Expand Up @@ -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}",
</Import-Package>
<Export-Package>
!org.wso2.carbon.identity.auth.service.internal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

/**
Expand All @@ -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";
Expand Down Expand Up @@ -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())) {
Expand All @@ -138,29 +140,32 @@ protected AuthenticationResult doAuthenticate(MessageContext messageContext) {
return authenticationResult;
}

handleImpersonatedAccessToken(authenticationContext, accessToken, oAuth2IntrospectionResponseDTO);

authenticationResult.setAuthenticationStatus(AuthenticationStatus.SUCCESS);

User authorizedUser = oAuth2IntrospectionResponseDTO.getAuthorizedUser();
if (authorizedUser != null) {
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 {
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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<String, String> mayActClaimSet = (Map) claimsSet.getClaim(Constants.ACT);
return mayActClaimSet.get(Constants.SUB);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@
<artifactId>json</artifactId>
<version>${json.wso2.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbusds.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.organization.management</groupId>
<artifactId>org.wso2.carbon.identity.organization.management.authz.service</artifactId>
Expand Down Expand Up @@ -357,6 +362,9 @@
<org.wso2.carbon.identity.oauth.import.version.range>[6.2.18, 8.0.0)
</org.wso2.carbon.identity.oauth.import.version.range>

<nimbusds.version>7.9.0.wso2v1</nimbusds.version>
<nimbusds.osgi.version.range>[7.3.0,8.0.0)</nimbusds.osgi.version.range>

<org.wso2.carbon.identity.organization.management.version>1.1.14
</org.wso2.carbon.identity.organization.management.version>
<org.wso2.carbon.identity.organization.management.version.range>[1.0.0, 2.0.0)
Expand Down

0 comments on commit 9d5a6e7

Please sign in to comment.