ConfigAuditEventLogger.java revision a97a4ced3330c02d8e2413956a450889bf77d6f0
/*
* 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.Requests;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.openidm.util.ContextUtil;
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;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 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);
private static final ObjectMapper mapper = new ObjectMapper();
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 {
JsonValue before = configAuditState.getBefore();
JsonValue after = configAuditState.getAfter();
// Get authenticationId from security context, if it exists.
String authenticationId = (context.containsContext(SecurityContext.class))
? context.asContext(SecurityContext.class).getAuthenticationId() : null;
// Build the event utilizing the config builder.
AuditEvent auditEvent = ConfigAuditEventBuilder.configEvent()
.operationFromCrestRequest(request)
.userId(authenticationId)
.runAs(authenticationId)
.transactionId(ContextUtil.getTransactionId(context))
.revision(configAuditState.getRevision())
.timestamp(System.currentTimeMillis())
.objectId(configAuditState.getId())
.eventName(CONFIG_AUDIT_EVENT_NAME)
.before(null != before ? mapper.writeValueAsString(before.getObject()) : "")
.after(null != after ? mapper.writeValueAsString(after.getObject()) : "")
.changedFields(getChangedFields(before, after))
.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) {
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()]);
}
}