/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2015 ForgeRock AS. */ package com.sun.identity.authentication.audit; import static com.sun.identity.authentication.util.ISAuthConstants.*; import static org.forgerock.audit.events.AuthenticationAuditEventBuilder.Status.*; import static org.forgerock.openam.audit.AMAuditEventBuilderUtils.getTrackingIdFromSSOToken; import static org.forgerock.openam.audit.AuditConstants.AUTHENTICATION_TOPIC; import static org.forgerock.openam.audit.AuditConstants.AuthenticationFailureReason.*; import static org.forgerock.openam.audit.AuditConstants.Component.AUTHENTICATION; import static org.forgerock.openam.audit.AuditConstants.EntriesInfoFieldKey.*; import static org.forgerock.openam.audit.AuditConstants.EntriesInfoFieldKey.AUTH_LEVEL; import static org.forgerock.openam.audit.AuditConstants.EventName.*; import static org.forgerock.openam.audit.context.AuditRequestContext.getTransactionIdValue; import static org.forgerock.openam.utils.StringUtils.isNotEmpty; import com.iplanet.sso.SSOException; import com.iplanet.sso.SSOToken; import com.sun.identity.authentication.AuthContext; import com.sun.identity.authentication.service.AMAuthErrorCode; import com.sun.identity.authentication.service.LoginState; import com.sun.identity.common.DNUtils; import org.forgerock.openam.audit.AMAuditEventBuilderUtils; import org.forgerock.openam.audit.AMAuthenticationAuditEventBuilder; import org.forgerock.openam.audit.AuditConstants; import org.forgerock.openam.audit.AuditConstants.AuthenticationFailureReason; import org.forgerock.openam.audit.AuditEventFactory; import org.forgerock.openam.audit.AuditEventPublisher; import org.forgerock.openam.audit.model.AuthenticationAuditEntry; import org.forgerock.openam.utils.CollectionUtils; import javax.inject.Inject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.NameCallback; import java.security.Principal; /** * This auditor is specifically aimed at constructing and logging authentication events for the login process. * * @since 13.0.0 */ public class AuthenticationProcessEventAuditor extends AbstractAuthenticationEventAuditor { /** * Constructor for {@link AuthenticationProcessEventAuditor}. * * @param eventPublisher The publisher responsible for logging the events. * @param eventFactory The factory that can be used to create the events. */ @Inject public AuthenticationProcessEventAuditor(AuditEventPublisher eventPublisher, AuditEventFactory eventFactory) { super(eventPublisher, eventFactory); } /** * Log an authentication process successful completion event. * * @param loginState The login state object. */ public void auditLoginSuccess(LoginState loginState) { String realm = getRealmFromState(loginState); if (eventPublisher.isAuditing(realm, AUTHENTICATION_TOPIC, AM_LOGIN_COMPLETED)) { String moduleName = null; String userDN = null; if (loginState != null) { moduleName = loginState.getAuthModuleNames(); userDN = loginState.getUserDN(); } AMAuthenticationAuditEventBuilder builder = eventFactory.authenticationEvent(realm) .transactionId(getTransactionIdValue()) .component(AUTHENTICATION) .eventName(AM_LOGIN_COMPLETED) .result(SUCCESSFUL) .entry(getAuditEntryDetail(moduleName, loginState)) .trackingIds(getTrackingIds(loginState)) .userId(userDN == null ? "" : userDN) .principal(DNUtils.DNtoName(userDN)); eventPublisher.tryPublish(AUTHENTICATION_TOPIC, builder.toEvent()); } } /** * Log an authentication process failure event. * * @param loginState The login state object. */ public void auditLoginFailure(LoginState loginState) { auditLoginFailure(loginState, findFailureReason(loginState)); } /** * Log an authentication process failure event. * * @param loginState The login state object. * @param failureReason The reason for the failure. If {@literal failureReason} is null then the value of * {@link LoginState#getErrorCode()} will be mapped to an {@link AuthenticationFailureReason} with * {@link AuthenticationFailureReason#LOGIN_FAILED} as default if the value could not be mapped. */ public void auditLoginFailure(LoginState loginState, AuthenticationFailureReason failureReason) { String realm = getRealmFromState(loginState); if (eventPublisher.isAuditing(realm, AUTHENTICATION_TOPIC, AM_LOGIN_COMPLETED)) { String principal = getFailedPrincipal(loginState); String moduleName = loginState == null ? null : loginState.getFailureModuleNames(); AuthenticationAuditEntry entryDetail = getAuditEntryDetail(moduleName, loginState); if (failureReason == null) { failureReason = findFailureReason(loginState); } entryDetail.addInfo(FAILURE_REASON, failureReason.name()); AMAuthenticationAuditEventBuilder builder = eventFactory.authenticationEvent(realm) .transactionId(getTransactionIdValue()) .component(AUTHENTICATION) .eventName(AM_LOGIN_COMPLETED) .result(FAILED) .entry(entryDetail) .trackingIds(getTrackingIds(loginState)) .userId(getUserId(principal, realm)) .principal(principal); eventPublisher.tryPublish(AUTHENTICATION_TOPIC, builder.toEvent()); } } /** * Log a logout event. * * @param token The {@Link SSOToken} of the event. */ public void auditLogout(SSOToken token) { String realm = getRealmFromToken(token); if (eventPublisher.isAuditing(realm, AUTHENTICATION_TOPIC, AM_LOGOUT)) { String principalName; try { Principal principal = token == null ? null : token.getPrincipal(); principalName = principal == null ? null : DNUtils.DNtoName(principal.getName()); } catch (SSOException e) { principalName = null; } AuthenticationAuditEntry entryDetail = new AuthenticationAuditEntry(); entryDetail.setModuleId(getSSOTokenProperty(token, AUTH_TYPE)); String host = getSSOTokenProperty(token, HOST); if (isNotEmpty(host)) { entryDetail.addInfo(IP_ADDRESS, host); } String trackingId = getTrackingIdFromSSOToken(token); String userId = AMAuditEventBuilderUtils.getUserId(token); AMAuthenticationAuditEventBuilder builder = eventFactory.authenticationEvent(realm) .transactionId(getTransactionIdValue()) .component(AUTHENTICATION) .eventName(AM_LOGOUT) .result(SUCCESSFUL) .entry(entryDetail) .trackingId(trackingId == null ? "" : trackingId) .userId(userId == null ? "" : userId) .principal(principalName); eventPublisher.tryPublish(AUTHENTICATION_TOPIC, builder.toEvent()); } } private AuthenticationAuditEntry getAuditEntryDetail(String moduleName, LoginState loginState) { AuthenticationAuditEntry entryDetail = new AuthenticationAuditEntry(); entryDetail.setModuleId(moduleName == null ? "" : moduleName); if (loginState != null) { String ip = loginState.getClient(); if (isNotEmpty(ip)) { entryDetail.addInfo(IP_ADDRESS, ip); } AuthContext.IndexType indexType = loginState.getIndexType(); if (indexType != null) { entryDetail.addInfo(AUTH_INDEX, indexType.toString()); } entryDetail.addInfo(AUTH_LEVEL, String.valueOf(loginState.getAuthLevel())); } return entryDetail; } private String getSSOTokenProperty(SSOToken ssoToken, String name) { try { return (ssoToken == null || name == null) ? null : ssoToken.getProperty(name); } catch (SSOException e) { return null; } } private String getFailedPrincipal(LoginState loginState) { if (loginState == null) { return null; } String principal = loginState.getUserDN(); if (principal != null) { return DNUtils.DNtoName(principal); } principal = loginState.getFailureTokenId(); if (principal != null) { return principal; } if (CollectionUtils.isNotEmpty(loginState.getAllReceivedCallbacks())) { for (Callback[] cb : loginState.getAllReceivedCallbacks().values()) { for (Callback aCb : cb) { if (aCb instanceof NameCallback) { return ((NameCallback) aCb).getName(); } } } } return null; } private AuditConstants.AuthenticationFailureReason findFailureReason(LoginState loginState) { String errorCode = loginState == null ? null : loginState.getErrorCode(); if (errorCode == null) { return LOGIN_FAILED; } switch (errorCode) { case AMAuthErrorCode.AUTH_PROFILE_ERROR: return NO_USER_PROFILE; case AMAuthErrorCode.AUTH_ACCOUNT_EXPIRED: return ACCOUNT_EXPIRED; case AMAuthErrorCode.AUTH_INVALID_PASSWORD: return INVALID_PASSWORD; case AMAuthErrorCode.AUTH_USER_INACTIVE: return USER_INACTIVE; case AMAuthErrorCode.AUTH_CONFIG_NOT_FOUND: return NO_CONFIG; case AMAuthErrorCode.AUTH_INVALID_DOMAIN: return INVALID_REALM; case AMAuthErrorCode.AUTH_ORG_INACTIVE: return REALM_INACTIVE; case AMAuthErrorCode.AUTH_TIMEOUT: return LOGIN_TIMEOUT; case AMAuthErrorCode.AUTH_MODULE_DENIED: return MODULE_DENIED; case AMAuthErrorCode.AUTH_USER_LOCKED: return LOCKED_OUT; case AMAuthErrorCode.AUTH_USER_NOT_FOUND: return USER_NOT_FOUND; case AMAuthErrorCode.AUTH_TYPE_DENIED: return AUTH_TYPE_DENIED; case AMAuthErrorCode.AUTH_MAX_SESSION_REACHED: return MAX_SESSION_REACHED; case AMAuthErrorCode.AUTH_SESSION_CREATE_ERROR: return SESSION_CREATE_ERROR; case AMAuthErrorCode.INVALID_AUTH_LEVEL: return INVALID_LEVEL; case AMAuthErrorCode.MODULE_BASED_AUTH_NOT_ALLOWED: return MODULE_DENIED; default: return LOGIN_FAILED; } } }