/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved * * 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 * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/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 Copyrighted [year] [name of copyright owner]" * * $Id: LogWriter.java,v 1.3 2008/06/25 05:42:09 qcheng Exp $ * * Portions Copyrighted 2015-2016 ForgeRock AS. */ package com.sun.identity.cli; import static org.forgerock.audit.events.AuthenticationAuditEventBuilder.Status.*; import static org.forgerock.http.routing.Version.*; import static org.forgerock.json.JsonValue.*; import static org.forgerock.openam.audit.AuditConstants.*; import static org.forgerock.openam.utils.Time.*; import java.io.IOException; import java.net.URISyntaxException; import java.security.AccessController; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.forgerock.audit.events.AccessAuditEventBuilder; import org.forgerock.guava.common.collect.ImmutableMap; import org.forgerock.guice.core.InjectorHolder; import org.forgerock.http.Client; import org.forgerock.http.HttpApplicationException; import org.forgerock.http.header.AcceptApiVersionHeader; import org.forgerock.http.protocol.Request; import org.forgerock.http.protocol.Response; import org.forgerock.json.JsonValue; import org.forgerock.openam.audit.AMAccessAuditEventBuilder; import org.forgerock.openam.audit.AMAuditEventBuilder; import org.forgerock.openam.audit.AMAuthenticationAuditEventBuilder; import org.forgerock.openam.audit.AuditConstants; import org.forgerock.util.Function; import org.forgerock.util.promise.NeverThrowsException; import org.slf4j.LoggerFactory; import com.iplanet.am.util.SystemProperties; import com.iplanet.dpro.session.SessionID; import com.iplanet.services.naming.WebtopNaming; import com.iplanet.sso.SSOToken; import com.sun.identity.log.LogRecord; import com.sun.identity.log.Logger; import com.sun.identity.log.messageid.LogMessageID; import com.sun.identity.log.messageid.LogMessageProvider; import com.sun.identity.log.messageid.MessageProviderFactory; import com.sun.identity.security.AdminTokenAction; import com.sun.identity.shared.debug.Debug; /** * Writes audit log entries. */ public class LogWriter { private static final Debug DEBUG = Debug.getInstance("amCLI"); private static final String LOG_MSG_XML = "CLI"; /** * Access Log Type. */ public final static int LOG_ACCESS = 0; /** * Error Log Type. */ public final static int LOG_ERROR = 1; private static final List IGNORED_LOG_FIELDS = Arrays.asList("error message", "realm", "user id"); private static final Map NORMALIZED_FIELD_NAMES = new ImmutableMap.Builder() .put("name of realm", "realm") .put("realm where entity resides", "realm") .put("realm where circle of trust resides", "realm") .build(); private static final Client client = InjectorHolder.getInstance(Client.class); private LogWriter() { } /** * Writes to log. * * @param mgr Command Manager Object. * @param type Type of log message. * @param level Logging level of the message. * @param msgid ID for message. * @param msgdata array of log message "data". * @param ssoToken Single Sign On Token of the user who committed the * operation. * @throws CLIException if log cannot be written. */ public static void log( CommandManager mgr, int type, Level level, String msgid, String[] msgdata, SSOToken ssoToken ) throws CLIException { if (!mgr.isLogOff()) { Logger logger; String logName = mgr.getLogName(); switch (type) { case LOG_ERROR: logger = (com.sun.identity.log.Logger) Logger.getLogger(logName + ".error"); break; default: logger = (com.sun.identity.log.Logger) Logger.getLogger(logName + ".access"); } try { LogMessageProvider msgProvider = MessageProviderFactory.getProvider(LOG_MSG_XML); SSOToken adminSSOToken = AccessController.doPrivileged(AdminTokenAction.getInstance()); if (ssoToken == null) { ssoToken = adminSSOToken; } if (logger.isLoggable(level)) { LogRecord logRec = msgProvider.createLogRecord(msgid, msgdata, ssoToken); if (logRec != null) { logger.log(logRec, adminSSOToken); } } logToAuditService(type, msgid, msgdata, ssoToken, msgProvider, adminSSOToken); } catch (Exception e) { throw new CLIException(e, ExitCodes.CANNOT_WRITE_LOG); } } } private static void logToAuditService(int type, String msgid, String[] msgdata, SSOToken ssoToken, LogMessageProvider msgProvider, SSOToken adminSSOToken) throws Exception { String operation = msgid.substring(msgid.indexOf('_') + 1); LogMessageID logMessageID = msgProvider.getAllHashMessageIDs().get(msgid); if (logMessageID == null) { DEBUG.error("Attempted audit logging for unknown message ID {}", msgid); return; } List fields = logMessageID.getDataColumns(); AMAuditEventBuilder builder; String topic; if ("LOGIN".equals(operation) && !msgid.startsWith("ATTEMPT")) { builder = authenticationEventBuilder(type, msgid, fields, msgdata); topic = AuditConstants.AUTHENTICATION_TOPIC; } else if (!"LOGIN".equals(operation)) { builder = accessEventBuilder(type, msgid, msgdata, operation, fields); topic = AuditConstants.ACCESS_TOPIC; } else { return; } JsonValue eventJson = builder.transactionId(CommandManager.TRANSACTION_ID.getValue()) .timestamp(currentTimeMillis()) .userId(ssoToken.getPrincipal().getName()) .trackingIdFromSSOToken(ssoToken) .component(AuditConstants.Component.SSOADM) .toEvent().getValue(); String sessionId = adminSSOToken.getTokenID().toString(); sendEvent(topic, eventJson, sessionId, WebtopNaming.mapSiteToServer(new SessionID(sessionId))); } private static void sendEvent(String topic, JsonValue eventJson, String sessionId, String baseUrl) throws HttpApplicationException, URISyntaxException { Request request = new Request(); request.setMethod("POST"); if (eventJson.isDefined(EVENT_REALM)) { String realm = eventJson.get(EVENT_REALM).asString(); baseUrl = baseUrl + "/json/realm-audit" + (realm.endsWith("/") ? realm : realm + "/"); } else { baseUrl = baseUrl + "/json/global-audit/"; } request.setUri(baseUrl + topic + "?_action=create"); request.getHeaders().add(SystemProperties.get("com.iplanet.am.cookie.name"), sessionId); request.getHeaders().add(new AcceptApiVersionHeader(version(1), version(1))); request.getEntity().setJson(eventJson.getObject()); client.send(request).then(WARN_OF_FAILURES_FUNCTION); } private static AMAccessAuditEventBuilder accessEventBuilder(int type, String msgid, String[] msgdata, String operation, List fields) { AMAccessAuditEventBuilder accessEventBuilder = new AMAccessAuditEventBuilder(); String realm = null; JsonValue requestData = json(object()); if (msgdata != null) { for (int i = 0; i < msgdata.length; i++) { String fieldName = fields.get(i).toLowerCase(); if (NORMALIZED_FIELD_NAMES.containsKey(fieldName)) { fieldName = NORMALIZED_FIELD_NAMES.get(fieldName); } if (!IGNORED_LOG_FIELDS.contains(fieldName)) { requestData.put(fieldName, msgdata[i]); } else if (fieldName.equals("realm")) { realm = msgdata[i]; } } } accessEventBuilder.request("ssoadm", operation, requestData); if (type == LOG_ERROR) { int errorMessageIndex = fields.indexOf("error message"); if (errorMessageIndex > -1) { accessEventBuilder.responseWithDetail(AccessAuditEventBuilder.ResponseStatus.FAILED, null, json(object(field("message", msgdata[errorMessageIndex])))); } else { accessEventBuilder.response(AccessAuditEventBuilder.ResponseStatus.FAILED, null); } accessEventBuilder.eventName(AuditConstants.EventName.AM_ACCESS_OUTCOME); } else if (msgid.startsWith("SUCCEED")) { accessEventBuilder.response(AccessAuditEventBuilder.ResponseStatus.SUCCESSFUL, null); accessEventBuilder.eventName(AuditConstants.EventName.AM_ACCESS_OUTCOME); } else { accessEventBuilder.eventName(AuditConstants.EventName.AM_ACCESS_ATTEMPT); } if (realm != null) { accessEventBuilder.realm(realm); } return accessEventBuilder; } private static AMAuthenticationAuditEventBuilder authenticationEventBuilder(int type, String msgid, List fields, String[] principal) { AMAuthenticationAuditEventBuilder authEventBuilder = new AMAuthenticationAuditEventBuilder() .principal(principal[fields.indexOf("user ID")]); if (!msgid.startsWith("ATTEMPT")) { authEventBuilder.result(type == LOG_ERROR ? FAILED : SUCCESSFUL); } return authEventBuilder.eventName(AuditConstants.EventName.AM_LOGIN_COMPLETED); } private static Function WARN_OF_FAILURES_FUNCTION = new Function() { private final org.slf4j.Logger logger = LoggerFactory.getLogger("amAudit"); @Override public Void apply(Response response) throws NeverThrowsException { if (!response.getStatus().isSuccessful()) { String responseText; try { responseText = response.getEntity().getString(); } catch (IOException e) { responseText = "--unknown--"; } logger.warn("Could not log audit via REST API: Status: {}, Response: {}", response.getStatus(), responseText); } response.close(); return null; } }; }