/*
* 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 2014-2015 ForgeRock AS.
*/
package org.forgerock.openidm.audit.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.forgerock.json.JsonValue.array;
import static org.forgerock.json.JsonValue.field;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.JsonValue.object;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.AS_SINGLE_FIELD_VALUES_FILTER;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.NEVER_FILTER;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.TYPE_ACTIVITY;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.newActionFilter;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.newAndCompositeFilter;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.newOrCompositeFilter;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.newReconActionFilter;
import static org.forgerock.openidm.audit.impl.AuditLogFilters.newScriptedFilter;
import static org.forgerock.services.context.ClientContext.newInternalClientContext;
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.script.ScriptException;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.Requests;
import org.forgerock.openidm.sync.TriggerContext;
import org.forgerock.script.engine.ScriptEngineFactory;
import org.forgerock.script.javascript.RhinoScriptEngineFactory;
import org.forgerock.script.registry.ScriptRegistryImpl;
import org.forgerock.script.source.DirectoryContainer;
import org.forgerock.services.context.Context;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* Test the audit log filter builder
*/
public class AuditLogFilterBuilderTest {
private ScriptRegistryImpl scriptRegistry;
@BeforeClass
public void initScriptRegistry() throws Exception {
Map<String, Object> configuration = new HashMap<String, Object>(1);
configuration.put(RhinoScriptEngineFactory.LANGUAGE_NAME, new HashMap<String, Object>(1));
scriptRegistry =
new ScriptRegistryImpl(configuration, ServiceLoader.load(ScriptEngineFactory.class), null, null);
URL container = AuditLogFilterBuilderTest.class.getResource("/container/");
Assert.assertNotNull(container);
scriptRegistry.addSourceUnit(new DirectoryContainer("container", container));
}
private AuditLogFilterBuilder auditLogFilterBuilder = new AuditLogFilterBuilder()
.add("eventTypes/activity/filter/actions",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue actions) {
return newActionFilter(TYPE_ACTIVITY, actions);
}
})
.add("eventTypes/activity/filter/triggers",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue triggers) {
List<AuditLogFilter> filters = new ArrayList<AuditLogFilter>();
for (String trigger : triggers.keys()) {
filters.add(newActionFilter(TYPE_ACTIVITY, triggers.get(trigger), trigger));
}
return newOrCompositeFilter(filters);
}
})
.add("eventTypes/recon/filter/actions",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue actions) {
return newReconActionFilter(actions);
}
})
.add("eventTypes/recon/filter/triggers",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue triggers) {
List<AuditLogFilter> filters = new ArrayList<AuditLogFilter>();
for (String trigger : triggers.keys()) {
filters.add(newReconActionFilter(triggers.get(trigger), trigger));
}
return newOrCompositeFilter(filters);
}
});
@Test
public void testActivityActionFilter() {
// Given
/*
"eventTypes" : {
"activity" : {
"filter" : {
"actions" : [ "create" ]
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("actions", array("create"))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context context = mock(Context.class);
// When
CreateRequest create = Requests.newCreateRequest("activity", null, json(object(field("operation", "create"))));
CreateRequest update = Requests.newCreateRequest("activity", null, json(object(field("operation", "update"))));
CreateRequest skittle = Requests.newCreateRequest("activity", null, json(object(field("operation", "skittle"))));
// Then
assertFalse(filter.isFiltered(context, create));
assertTrue(filter.isFiltered(context, update));
assertFalse(filter.isFiltered(context, skittle)); // non-RequestTypes are always unfiltered
}
@Test
public void testActivityActionFilterWithNonRequestTypeAction() {
// Given
/*
"eventTypes" : {
"activity" : {
"filter" : {
"actions" : [ "skittle" ]
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("actions", array("skittle"))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context context = mock(Context.class);
// When
CreateRequest create = Requests.newCreateRequest("activity", null, json(object(field("operation", "create"))));
CreateRequest update = Requests.newCreateRequest("activity", null, json(object(field("operation", "update"))));
CreateRequest skittle = Requests.newCreateRequest("activity", null, json(object(field("operation", "skittle"))));
// Then
assertTrue(filter.isFiltered(context, create));
assertTrue(filter.isFiltered(context, update));
assertFalse(filter.isFiltered(context, skittle)); // non-RequestTypes are always unfiltered
}
@Test
public void testReconActionFilter() {
// Given
/*
"eventTypes" : {
"recon" : {
"filter" : {
"actions" : [ "link", "unlink" ]
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("recon", object(
field("filter", object(
field("actions", array("link", "unlink"))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context context = mock(Context.class);
// When
CreateRequest link = Requests.newCreateRequest("recon", null, json(object(field("action", "link"))));
CreateRequest unlink = Requests.newCreateRequest("recon", null, json(object(field("action", "unlink"))));
CreateRequest exception = Requests.newCreateRequest("recon", null, json(object(field("action", "exception"))));
CreateRequest skittle = Requests.newCreateRequest("recon", null, json(object(field("action", "skittle"))));
// Then
assertFalse(filter.isFiltered(context, link));
assertFalse(filter.isFiltered(context, unlink));
assertTrue(filter.isFiltered(context, exception));
assertFalse(filter.isFiltered(context, skittle)); // non-Actions are always unfiltered
}
@Test
public void testReconActionFilterWithNonRequestTypeAction() {
// Given
/*
"eventTypes" : {
"recon" : {
"filter" : {
"actions" : [ "skittle" ]
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("recon", object(
field("filter", object(
field("actions", array("skittle"))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context context = mock(Context.class);
// When
CreateRequest link = Requests.newCreateRequest("recon", null, json(object(field("action", "link"))));
CreateRequest unlink = Requests.newCreateRequest("recon", null, json(object(field("action", "unlink"))));
CreateRequest exception = Requests.newCreateRequest("recon", null, json(object(field("action", "exception"))));
CreateRequest skittle = Requests.newCreateRequest("recon", null, json(object(field("action", "skittle"))));
// Then
assertTrue(filter.isFiltered(context, link));
assertTrue(filter.isFiltered(context, unlink));
assertTrue(filter.isFiltered(context, exception));
assertFalse(filter.isFiltered(context, skittle)); // non-Actions are always unfiltered
}
@Test
public void testActivityTriggerFilter() {
// Given
/*
"eventTypes" : {
"activity" : {
"filter" : {
"triggers" : {
"sometrigger" : [
"create",
"update"
]
}
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("triggers", object(
field("sometrigger", array("create"))
))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context noTrigger = mock(Context.class);
Context hasTrigger = new TriggerContext(noTrigger, "sometrigger");
// When
CreateRequest create = Requests.newCreateRequest("activity", null, json(object(field("operation", "create"))));
CreateRequest update = Requests.newCreateRequest("activity", null, json(object(field("operation", "update"))));
CreateRequest skittle = Requests.newCreateRequest("activity", null, json(object(field("operation", "skittle"))));
// Then
assertFalse(filter.isFiltered(noTrigger, create));
assertFalse(filter.isFiltered(noTrigger, update));
assertFalse(filter.isFiltered(noTrigger, skittle));
assertFalse(filter.isFiltered(hasTrigger, create));
assertTrue(filter.isFiltered(hasTrigger, update));
assertFalse(filter.isFiltered(hasTrigger, skittle));// non-RequestTypes are always unfiltered
}
@Test
public void testActivityTriggerFilterWithNonRequestTypeAction() {
// Given
/*
"eventTypes" : {
"activity" : {
"filter" : {
"triggers" : {
"sometrigger" : [ "skittle" ]
}
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("triggers", object(
field("sometrigger", array("skittle"))
))
))
))
))
));
AuditLogFilter filter = auditLogFilterBuilder.build(config);
Context noTrigger = mock(Context.class);
Context hasTrigger = newInternalClientContext(new TriggerContext(noTrigger, "sometrigger"));
// When
CreateRequest create = Requests.newCreateRequest("activity", null, json(object(field("operation", "create"))));
CreateRequest update = Requests.newCreateRequest("activity", null, json(object(field("operation", "update"))));
CreateRequest skittle = Requests.newCreateRequest("activity", null, json(object(field("operation", "skittle"))));
// Then
assertFalse(filter.isFiltered(noTrigger, create));
assertFalse(filter.isFiltered(noTrigger, update));
assertFalse(filter.isFiltered(noTrigger, skittle));
assertTrue(filter.isFiltered(hasTrigger, create));
assertTrue(filter.isFiltered(hasTrigger, update));
assertFalse(filter.isFiltered(hasTrigger, skittle));// non-RequestTypes are always unfiltered
}
@Test
public void testActivityScriptFilter() {
// Given
/*
"eventTypes" : {
"activity" : {
"filter" : {
"script" : {
"type" : "text/javascript",
"name" : "logfilter.js"
}
},
},
}
*/
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("script", object(
field("type", "text/javascript"),
field("name", "logfilter.js")
))
))
))
))
));
AuditLogFilter filter = new AuditLogFilterBuilder()
.add("eventTypes/activity/filter/script",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue scriptConfig) {
try {
return newScriptedFilter(scriptRegistry.takeScript(scriptConfig));
} catch (ScriptException e) {
return NEVER_FILTER;
}
}
})
.build(config);
Context context = mock(Context.class);
// When
CreateRequest create = Requests.newCreateRequest("activity", null, json(object(field("operation", "create"))));
CreateRequest update = Requests.newCreateRequest("activity", null, json(object(field("operation", "update"))));
CreateRequest skittle = Requests.newCreateRequest("activity", null, json(object(field("operation", "skittle"))));
// Then
assertFalse(filter.isFiltered(context, create)); // don't filter creates
assertTrue(filter.isFiltered(context, update)); // filter out updates
assertFalse(filter.isFiltered(context, skittle)); // don't filter weird stuff
}
private AuditLogFilterBuilder getFieldValueFilterBuilder() {
return new AuditLogFilterBuilder()
.add("/",
new AuditLogFilters.JsonValueObjectConverter<AuditLogFilter>() {
@Override
public AuditLogFilter apply(JsonValue filterConfig) {
return newAndCompositeFilter(filterConfig.asList(AS_SINGLE_FIELD_VALUES_FILTER));
}
});
}
@Test
public void testFieldFilter() {
// Filter-in if entryType is summary or reconType is full
// Given
JsonValue config = json(array(
object(
field("name", "entryType"),
field("values", array("summary"))
),
object(
field("name", "reconType"),
field("values", array("full"))
)
));
AuditLogFilter filter = getFieldValueFilterBuilder().build(config);
Context context = mock(Context.class);
// When
CreateRequest start = Requests.newCreateRequest("recon", null, json(object(field("entryType", "start"))));
CreateRequest entry = Requests.newCreateRequest("recon", null, json(object(field("entryType", "entry"))));
CreateRequest summary = Requests.newCreateRequest("recon", null, json(object(field("entryType", "summary"))));
CreateRequest full = Requests.newCreateRequest("recon", null, json(object(field("entryType", "entry"), field("reconType", "full"))));
CreateRequest byId = Requests.newCreateRequest("recon", null, json(object(field("entryType", "entry"), field("reconType", "byId"))));
// Then
assertTrue(filter.isFiltered(context, start)); // filter out start
assertTrue(filter.isFiltered(context, entry)); // filter out entry
assertFalse(filter.isFiltered(context, summary)); // don't filter summary
assertFalse(filter.isFiltered(context, full)); // don't filter full
assertTrue(filter.isFiltered(context, byId)); // filter out byId
}
@Test
public void testFieldFilterDuplicate() {
// test duplicate field filters
// Given
JsonValue config = json(array(
object(
field("name", "entryType"),
field("values", array("summary"))
),
object(
field("name", "entryType"),
field("values", array("start"))
)
));
AuditLogFilter filter = getFieldValueFilterBuilder().build(config);
Context context = mock(Context.class);
// When
CreateRequest start = Requests.newCreateRequest("recon", null, json(object(field("entryType", "start"))));
CreateRequest entry = Requests.newCreateRequest("recon", null, json(object(field("entryType", "entry"))));
CreateRequest summary = Requests.newCreateRequest("recon", null, json(object(field("entryType", "summary"))));
// Then
assertFalse(filter.isFiltered(context, start)); // don't filter start
assertTrue(filter.isFiltered(context, entry)); // filter out entiy
assertFalse(filter.isFiltered(context, summary)); // don't filter summary
}
/** an always-true filter */
private static AuditLogFilter TRUE = new AuditLogFilter() {
@Override
public boolean isFiltered(Context context, CreateRequest request) {
return true;
}
};
/** an always-false filter */
public static AuditLogFilter FALSE = new AuditLogFilter() {
@Override
public boolean isFiltered(Context context, CreateRequest request) {
return false;
}
};
/** Test data for composite audit log filters:
*
* <ul>
* <li>array of 3 filters from which to create a composite filter</li>
* <li>expected result from OrCompositeFilter</li>
* <li>expected result from AndCompositeFilter</li>
* </ul>
*/
@DataProvider(name = "compositeData")
public Object[][] createCompositeData() {
return new Object[][] {
{ new AuditLogFilter[] { TRUE, TRUE, TRUE }, true, true },
{ new AuditLogFilter[] { TRUE, TRUE, FALSE }, true, false },
{ new AuditLogFilter[] { TRUE, FALSE, TRUE }, true, false },
{ new AuditLogFilter[] { TRUE, FALSE, FALSE }, true, false },
{ new AuditLogFilter[] { FALSE, TRUE, TRUE }, true, false },
{ new AuditLogFilter[] { FALSE, TRUE, FALSE }, true, false },
{ new AuditLogFilter[] { FALSE, FALSE, TRUE }, true, false },
{ new AuditLogFilter[] { FALSE, FALSE, FALSE }, false, false }
};
}
@Test(dataProvider = "compositeData")
public void testCompositeIdentity(AuditLogFilter[] filters, boolean orFilterResult, boolean andFilterResult) {
Context context = mock(Context.class);
CreateRequest request = mock(CreateRequest.class);
assertThat(newOrCompositeFilter(Arrays.asList(filters)).isFiltered(context, request)).isEqualTo(orFilterResult);
assertThat(newAndCompositeFilter(Arrays.asList(filters)).isFiltered(context, request)).isEqualTo(andFilterResult);
}
@Test
public void testGetByGlob() {
JsonValue config = json(
object(
field("eventTypes", object(
field("activity", object(
field("filter", object(
field("actions", array("create"))
))
)),
field("recon", object(
field("filter", object(
field("script", object(
field("type", "text/javascript"),
field("name", "reconfilter.js")
))
))
)),
field("custom", object(
field("filter", object(
field("script", object(
field("type", "text/javascript"),
field("name", "customfilter.js")
))
))
))
))
));
JsonValue result = new AuditLogFilterBuilder().getByGlob(config, "eventTypes/*/filter/script");
assertThat(result.isDefined("activity")).isFalse();
assertThat(result.isDefined("recon")).isTrue();
assertThat(result.get("recon").isDefined("type")).isTrue();
assertThat(result.get("recon").isDefined("name")).isTrue();
assertThat(result.get("recon").get("name").asString()).isEqualTo("reconfilter.js");
assertThat(result.isDefined("custom")).isTrue();
assertThat(result.get("custom").isDefined("type")).isTrue();
assertThat(result.get("custom").isDefined("name")).isTrue();
assertThat(result.get("custom").get("name").asString()).isEqualTo("customfilter.js");
}
}