/* * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.test; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertEquals; import java.util.*; import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.audit.api.AuditResultHandler; import com.evolveum.midpoint.schema.result.OperationResult; import org.apache.commons.lang.StringUtils; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.audit.api.AuditService; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.schema.ObjectDeltaOperation; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.xml.ns._public.common.common_3.CleanupPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.apache.commons.lang.Validate; import javax.xml.datatype.Duration; import javax.xml.namespace.QName; /** * Dummy audit service that only remembers the audit messages in runtime. * Only for use in tests. * * @author semancik * */ public class DummyAuditService implements AuditService, DebugDumpable { private static DummyAuditService instance = null; private List<AuditEventRecord> records = new ArrayList<AuditEventRecord>(); public static DummyAuditService getInstance() { if (instance == null) { instance = new DummyAuditService(); } return instance; } @Override public void audit(AuditEventRecord record, Task task) { records.add(record.clone()); } @Override public void cleanupAudit(CleanupPolicyType policy, OperationResult parentResult) { Validate.notNull(policy, "Cleanup policy must not be null."); Validate.notNull(parentResult, "Operation result must not be null."); if (policy.getMaxAge() == null) { return; } Duration duration = policy.getMaxAge(); if (duration.getSign() > 0) { duration = duration.negate(); } long minValue = duration.getTimeInMillis(new Date()); Iterator<AuditEventRecord> iterator = records.iterator(); while (iterator.hasNext()) { AuditEventRecord record = iterator.next(); Long timestamp = record.getTimestamp(); if (timestamp == null) { continue; } if (timestamp < minValue) { iterator.remove(); } } } public List<AuditEventRecord> getRecords() { return records; } public void clear() { records.clear(); } /** * Asserts that there is a request message followed by execution message. */ public void assertSimpleRecordSanity() { Iterator<AuditEventRecord> iterator = records.iterator(); int num = 0; int numRequests = 0; int numExecutions = 0; while (iterator.hasNext()) { AuditEventRecord record = iterator.next(); num++; assertRecordSanity(""+num+"th record", record); if (record.getEventStage() == AuditEventStage.REQUEST) { numRequests++; } if (record.getEventStage() == AuditEventStage.EXECUTION) { assert numRequests > 0 : "Encountered execution stage audit record without any preceding request: "+record; numExecutions++; } } assert numRequests <= numExecutions : "Strange number of requests and executions; "+numRequests+" requests, "+numExecutions+" executions"; } private void assertRecordSanity(String recordDesc, AuditEventRecord record) { assert record != null : "Null audit record ("+recordDesc+")"; assert !StringUtils.isEmpty(record.getEventIdentifier()) : "No event identifier in audit record ("+recordDesc+")"; assert !StringUtils.isEmpty(record.getTaskIdentifier()) : "No task identifier in audit record ("+recordDesc+")"; // TODO } public void assertRecords(int expectedNumber) { assert records.size() == expectedNumber : "Unexpected number of audit records; expected "+expectedNumber+ " but was "+records.size(); } public List<AuditEventRecord> getRecordsOfType(AuditEventType type) { List<AuditEventRecord> retval = new ArrayList<AuditEventRecord>(); for (AuditEventRecord record : records) { if (record.getEventType() == type) { retval.add(record); } } return retval; } public void assertRecords(AuditEventType type, int expectedNumber) { List<AuditEventRecord> filtered = getRecordsOfType(type); assert filtered.size() == expectedNumber : "Unexpected number of audit records of type " + type + "; expected " + expectedNumber + " but was " + filtered.size(); } public AuditEventRecord getRequestRecord() { assertSingleBatch(); AuditEventRecord requestRecord = records.get(0); assert requestRecord != null : "The first audit record is null"; assert requestRecord.getEventStage() == AuditEventStage.REQUEST : "The first audit record is not request, it is "+requestRecord; return requestRecord; } public AuditEventRecord getExecutionRecord(int index) { assertSingleBatch(); AuditEventRecord executionRecord = records.get(index+1); assert executionRecord != null : "The "+index+"th audit execution record is null"; assert executionRecord.getEventStage() == AuditEventStage.EXECUTION : "The "+index+"th audit execution record is not execution, it is "+executionRecord; return executionRecord; } public List<AuditEventRecord> getExecutionRecords() { assertSingleBatch(); return records.subList(1, records.size()); } private void assertSingleBatch() { assert records.size() > 1 : "Expected at least two audit records but got "+records.size(); Iterator<AuditEventRecord> iterator = records.iterator(); AuditEventRecord requestRecord = iterator.next(); if (requestRecord.getEventType() == AuditEventType.CREATE_SESSION) { requestRecord = iterator.next(); } assert requestRecord.getEventStage() == AuditEventStage.REQUEST : "Expected first record to be request, it was "+requestRecord.getEventStage()+" instead: "+requestRecord; while (iterator.hasNext()) { AuditEventRecord executionRecord = iterator.next(); if (executionRecord.getEventType() == AuditEventType.TERMINATE_SESSION) { break; } assert executionRecord.getEventStage() == AuditEventStage.EXECUTION : "Expected following record to be execution, it was "+executionRecord.getEventStage()+" instead: "+executionRecord; } } public void assertAnyRequestDeltas() { AuditEventRecord requestRecord = getRequestRecord(); Collection<ObjectDeltaOperation<? extends ObjectType>> requestDeltas = requestRecord.getDeltas(); assert requestDeltas != null && !requestDeltas.isEmpty() : "Expected some deltas in audit request record but found none"; } public Collection<ObjectDeltaOperation<? extends ObjectType>> getExecutionDeltas() { return getExecutionDeltas(0); } public Collection<ObjectDeltaOperation<? extends ObjectType>> getExecutionDeltas(int index) { AuditEventRecord executionRecord = getExecutionRecord(index); Collection<ObjectDeltaOperation<? extends ObjectType>> deltas = executionRecord.getDeltas(); assert deltas != null : "Execution audit record has null deltas"; return deltas; } public ObjectDeltaOperation<?> getExecutionDelta(int index) { Collection<ObjectDeltaOperation<? extends ObjectType>> deltas = getExecutionDeltas(index); assert deltas.size() == 1 : "Execution audit record has more than one deltas, it has "+deltas.size(); ObjectDeltaOperation<?> delta = deltas.iterator().next(); return delta; } public <O extends ObjectType> ObjectDeltaOperation<O> getExecutionDelta(int index, ChangeType changeType, Class<O> typeClass) { for (ObjectDeltaOperation<? extends ObjectType> deltaOp: getExecutionDeltas(index)) { ObjectDelta<? extends ObjectType> delta = deltaOp.getObjectDelta(); if (delta.getObjectTypeClass() == typeClass && delta.getChangeType() == changeType) { return (ObjectDeltaOperation<O>) deltaOp; } } return null; } public void assertExecutionDeltaAdd() { ObjectDeltaOperation<?> delta = getExecutionDelta(0); assert delta.getObjectDelta().isAdd() : "Execution audit record is not add, it is "+delta; } public void assertExecutionSuccess() { assertExecutionOutcome(OperationResultStatus.SUCCESS); } public void assertExecutionOutcome(OperationResultStatus expectedStatus) { List<AuditEventRecord> executionRecords = getExecutionRecords(); for (AuditEventRecord executionRecord: executionRecords) { assert executionRecord.getOutcome() == expectedStatus : "Expected execution outcome "+expectedStatus+" in audit record but it was "+executionRecord.getOutcome(); } } public void assertExecutionOutcome(int index, OperationResultStatus expectedStatus) { List<AuditEventRecord> executionRecords = getExecutionRecords(); AuditEventRecord executionRecord = executionRecords.get(index); assert executionRecord.getOutcome() == expectedStatus : "Expected execution outcome "+expectedStatus+" in audit execution record ("+index+") but it was "+executionRecord.getOutcome(); } public void assertExecutionMessage() { List<AuditEventRecord> executionRecords = getExecutionRecords(); for (AuditEventRecord executionRecord: executionRecords) { assert !StringUtils.isEmpty(executionRecord.getMessage()) : "Expected execution message in audit record but there was none; in "+executionRecord; } } public void assertExecutionMessage(int index) { List<AuditEventRecord> executionRecords = getExecutionRecords(); AuditEventRecord executionRecord = executionRecords.get(index); assert !StringUtils.isEmpty(executionRecord.getMessage()) : "Expected execution message in audit record but there was none; in "+executionRecord; } public void assertNoRecord() { assert records.isEmpty() : "Expected no audit record but some sneaked in: "+records; } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(ChangeType expectedChangeType, Class<O> expectedClass) { return assertHasDelta(null, 0, expectedChangeType, expectedClass); } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(ChangeType expectedChangeType, Class<O> expectedClass, OperationResultStatus expextedResult) { return assertHasDelta(null, 0, expectedChangeType, expectedClass, expextedResult); } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(int index, ChangeType expectedChangeType, Class<O> expectedClass) { return assertHasDelta(null, index, expectedChangeType, expectedClass); } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(int index, ChangeType expectedChangeType, Class<O> expectedClass, OperationResultStatus expextedResult) { return assertHasDelta(null, index, expectedChangeType, expectedClass, expextedResult); } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(String message, int index, ChangeType expectedChangeType, Class<O> expectedClass) { return assertHasDelta(message, index, expectedChangeType, expectedClass, null); } public <O extends ObjectType> ObjectDeltaOperation<O> assertHasDelta(String message, int index, ChangeType expectedChangeType, Class<O> expectedClass, OperationResultStatus expextedResult) { ObjectDeltaOperation<O> deltaOp = getExecutionDelta(index, expectedChangeType, expectedClass); assert deltaOp != null : (message==null?"":message+": ")+"Delta for "+expectedClass+" of type "+expectedChangeType+" was not found in audit trail"; if (expextedResult != null) { assertEquals((message==null?"":message+": ")+"Delta for "+expectedClass+" of type "+expectedChangeType+" has unexpected result", deltaOp.getExecutionResult().getStatus(), expextedResult); } return deltaOp; } public void assertExecutionDeltas(int expectedNumber) { assertExecutionDeltas(0, expectedNumber); } public void assertExecutionDeltas(int index, int expectedNumber) { assertEquals("Wrong number of execution deltas in audit trail (index "+index+")", expectedNumber, getExecutionDeltas(index).size()); } public void assertTarget(String expectedOid, AuditEventStage stage) { Collection<PrismReferenceValue> targets = new ArrayList<>(); for(AuditEventRecord record: records) { PrismReferenceValue target = record.getTarget(); if (stage == null || stage == record.getEventStage()) { if (target != null && expectedOid.equals(target.getOid())) { return; } if (target != null) { targets.add(target); } } } assert false : "Target "+expectedOid+" not found in audit records (stage="+stage+"); found "+targets; } public void assertTarget(String expectedOid) { assertTarget(expectedOid, AuditEventStage.REQUEST); assertTarget(expectedOid, AuditEventStage.EXECUTION); } public <O extends ObjectType,T> void assertOldValue(ChangeType expectedChangeType, Class<O> expectedClass, QName attrName, T expectedValue) { assertOldValue(null, 0, expectedChangeType, expectedClass, new ItemPath(attrName), expectedValue); } public <O extends ObjectType,T> void assertOldValue(ChangeType expectedChangeType, Class<O> expectedClass, ItemPath propPath, T expectedValue) { assertOldValue(null, 0, expectedChangeType, expectedClass, propPath, expectedValue); } public <O extends ObjectType,T> void assertOldValue(int index, ChangeType expectedChangeType, Class<O> expectedClass, ItemPath propPath, T expectedValue) { assertOldValue(null, index, expectedChangeType, expectedClass, propPath, expectedValue); } public <O extends ObjectType,T> void assertOldValue(String message, int index, ChangeType expectedChangeType, Class<O> expectedClass, ItemPath propPath, T... expectedValues) { ObjectDeltaOperation<O> deltaOp = getExecutionDelta(index, expectedChangeType, expectedClass); assert deltaOp != null : (message==null?"":message+": ")+"Delta for "+expectedClass+" of type "+expectedChangeType+" was not found in audit trail"; PropertyDelta<Object> propDelta = deltaOp.getObjectDelta().findPropertyDelta(propPath); assert propDelta != null : "No property delta for "+propPath+" in Delta for "+expectedClass+" of type "+expectedChangeType; Collection<PrismPropertyValue<Object>> estimatedOldValues = propDelta.getEstimatedOldValues(); assert estimatedOldValues != null && !estimatedOldValues.isEmpty() : "No old values in property delta for "+propPath+" in Delta for "+expectedClass+" of type "+expectedChangeType; PrismAsserts.assertValues((message==null?"":message+": ") +"Wrong old values in property delta for "+propPath+" in Delta for "+expectedClass+" of type "+expectedChangeType, estimatedOldValues, expectedValues); } /** * Checks that the first record is login and the last is logout. */ public void assertLoginLogout() { assertLoginLogout(null); } /** * Checks that the first record is login and the last is logout. */ public void assertLoginLogout(String expectedChannel) { AuditEventRecord firstRecord = records.get(0); assertEquals("Wrong type of first audit record: "+firstRecord.getEventType(), AuditEventType.CREATE_SESSION, firstRecord.getEventType()); assertEquals("Wrong outcome of first audit record: "+firstRecord.getOutcome(), OperationResultStatus.SUCCESS, firstRecord.getOutcome()); AuditEventRecord lastRecord = records.get(records.size()-1); assertEquals("Wrong type of last audit record: "+lastRecord.getEventType(), AuditEventType.TERMINATE_SESSION, lastRecord.getEventType()); assertEquals("Wrong outcome of last audit record: "+lastRecord.getOutcome(), OperationResultStatus.SUCCESS, lastRecord.getOutcome()); assertEquals("Audit session ID does not match", firstRecord.getSessionIdentifier(), lastRecord.getSessionIdentifier()); assertFalse("Same login and logout event IDs", firstRecord.getEventIdentifier().equals(lastRecord.getEventIdentifier())); if (expectedChannel != null) { assertEquals("Wrong channel in first audit record", expectedChannel, firstRecord.getChannel()); assertEquals("Wrong channel in last audit record", expectedChannel, lastRecord.getChannel()); } } public void assertFailedLogin(String expectedChannel) { AuditEventRecord firstRecord = records.get(0); assertEquals("Wrong type of first audit record: "+firstRecord.getEventType(), AuditEventType.CREATE_SESSION, firstRecord.getEventType()); assertEquals("Wrong outcome of first audit record: "+firstRecord.getOutcome(), OperationResultStatus.FATAL_ERROR, firstRecord.getOutcome()); if (expectedChannel != null) { assertEquals("Wrong channel in first audit record", expectedChannel, firstRecord.getChannel()); } } public void assertFailedProxyLogin(String expectedChannel) { AuditEventRecord firstRecord = records.get(0); assertEquals("Wrong type of first audit record (service authN): "+firstRecord.getEventType(), AuditEventType.CREATE_SESSION, firstRecord.getEventType()); assertEquals("Wrong outcome of first audit record (service authN): "+firstRecord.getOutcome(), OperationResultStatus.SUCCESS, firstRecord.getOutcome()); if (expectedChannel != null) { assertEquals("Wrong channel in first audit record", expectedChannel, firstRecord.getChannel()); } AuditEventRecord secondRecord = records.get(1); assertEquals("Wrong type of second audit record (proxy authN): "+secondRecord.getEventType(), AuditEventType.CREATE_SESSION, secondRecord.getEventType()); assertEquals("Wrong outcome of second audit record (proxy authN): "+secondRecord.getOutcome(), OperationResultStatus.FATAL_ERROR, secondRecord.getOutcome()); if (expectedChannel != null) { assertEquals("Wrong channel in second audit record (proxy authN)", expectedChannel, secondRecord.getChannel()); } } @Override public String toString() { return "DummyAuditService(" + records + ")"; } @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append("DummyAuditService: ").append(records.size()).append(" records\n"); DebugUtil.debugDump(sb, records, indent + 1, false); return sb.toString(); } @Override public List<AuditEventRecord> listRecords(String query, Map<String, Object> params) { throw new UnsupportedOperationException("Object retrieval not supported"); } @Override public long countObjects(String query, Map<String, Object> params){ throw new UnsupportedOperationException("Object retrieval not supported"); } @Override public boolean supportsRetrieval() { return false; } @Override public void listRecordsIterative(String query, Map<String, Object> params, AuditResultHandler auditResultHandler) { // TODO Auto-generated method stub } @Override public void reindexEntry(AuditEventRecord record) { // TODO Auto-generated method stub } }