/* * 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 org.forgerock.openidm.config.manage; import static org.forgerock.json.JsonValue.json; import static org.forgerock.json.JsonValue.object; import java.util.ArrayList; import java.util.List; import org.forgerock.audit.events.AuditEvent; import org.forgerock.audit.events.ConfigAuditEventBuilder; import org.forgerock.json.JsonValue; import org.forgerock.json.patch.JsonPatch; import org.forgerock.json.resource.ConnectionFactory; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.Request; import org.forgerock.json.resource.RequestType; import org.forgerock.json.resource.Requests; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.services.context.Context; import org.forgerock.services.context.SecurityContext; import org.forgerock.util.promise.Promise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utilizing the ConfigAuditEventBuilder this class will send log events of config changes to the commons * audit module. * * @see ConfigAuditEventBuilder */ public class ConfigAuditEventLogger { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigAuditEventLogger.class); public static final String CONFIG_AUDIT_EVENT_NAME = "CONFIG"; public static final String AUDIT_CONFIG_REST_PATH = "audit/config"; public static final String ROOT_ID_PATH = "/" + ResourceResponse.FIELD_CONTENT_ID; /** * Calls buildAuditEvent() and invokes the request to the audit path. * * @param connectionFactory factory used to make crest call to audit service. */ public final Promise<ResourceResponse, ResourceException> log(ConfigAuditState configAuditState, Request request, Context context, ConnectionFactory connectionFactory) { try { // Build the event utilizing the config builder. final AuditEvent auditEvent = ConfigAuditEventBuilder.configEvent() .operationFromCrestRequest(request) .userId(getUserId(context)) .runAs(getUserId(context)) .transactionIdFromContext(context) .revision(configAuditState.getRevision()) .timestamp(System.currentTimeMillis()) .objectId(configAuditState.getId()) .eventName(CONFIG_AUDIT_EVENT_NAME) .before(configAuditState.getBefore()) .after(configAuditState.getAfter()) .changedFields(getChangedFields( configAuditState.getBefore(), configAuditState.getAfter(), request.getRequestType())) .toEvent(); return connectionFactory.getConnection().create(context, Requests.newCreateRequest(AUDIT_CONFIG_REST_PATH, auditEvent.getValue())).asPromise(); } catch (ResourceException e) { LOGGER.error("had trouble logging audit event for config changes.", e); return e.asPromise(); } catch (Exception e) { LOGGER.error("had trouble logging audit event for config changes.", e); return new InternalServerErrorException(e.getMessage(), e).asPromise(); } } private String[] getChangedFields(JsonValue before, JsonValue after, RequestType requestType) { if (RequestType.READ.equals(requestType) || RequestType.QUERY.equals(requestType)) { // if reading or query there are no changed fields return new String[0]; } if (null == before && null == after) { return new String[0]; } // Default to empty json when they are null. if (after == null) { after = json(object()); } if (before == null) { before = json(object()); } // Now find the changed fields. List<String> changedFields = new ArrayList<>(); for (JsonValue change : JsonPatch.diff(before, after)) { String diff = change.get(JsonPatch.PATH_PTR).asString(); // Config objects require id only for repo storage and not on the filesystem. // As far as reporting changes to a config, it is not relevant to be reported as changes. if (!ROOT_ID_PATH.equals(diff)) { changedFields.add(diff); } } return changedFields.toArray(new String[changedFields.size()]); } private String getUserId(final Context context) { return context.containsContext(SecurityContext.class) ? context.asContext(SecurityContext.class).getAuthenticationId() : null; } }