001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.audit;
019
020import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDdateTime;
021import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDstring;
022import static org.apache.jena.rdf.model.ResourceFactory.createResource;
023import static org.apache.jena.rdf.model.ResourceFactory.createTypedLiteral;
024import static java.util.Collections.emptySet;
025import static java.util.Collections.singleton;
026import static org.fcrepo.audit.AuditNamespaces.AUDIT;
027import static org.fcrepo.audit.AuditNamespaces.EVENT_TYPE;
028import static org.fcrepo.audit.AuditNamespaces.PREMIS;
029import static org.fcrepo.audit.AuditNamespaces.PROV;
030import static org.fcrepo.audit.AuditNamespaces.REPOSITORY;
031import static org.fcrepo.kernel.api.RdfLexicon.RDF_NAMESPACE;
032import static org.fcrepo.kernel.api.observer.EventType.RESOURCE_CREATION;
033import static org.fcrepo.kernel.api.observer.EventType.RESOURCE_DELETION;
034import static org.fcrepo.kernel.api.observer.EventType.RESOURCE_MODIFICATION;
035import static org.fcrepo.kernel.api.observer.OptionalValues.BASE_URL;
036import static org.fcrepo.kernel.api.observer.OptionalValues.USER_AGENT;
037import static org.junit.Assert.assertEquals;
038import static org.mockito.Matchers.any;
039import static org.mockito.Matchers.anyObject;
040import static org.mockito.Matchers.anyString;
041import static org.mockito.Matchers.eq;
042import static org.mockito.Mockito.mock;
043import static org.mockito.Mockito.never;
044import static org.mockito.Mockito.spy;
045import static org.mockito.Mockito.verify;
046import static org.mockito.Mockito.when;
047import static org.mockito.MockitoAnnotations.initMocks;
048import static org.mockito.internal.util.collections.Sets.newSet;
049import static org.springframework.test.util.ReflectionTestUtils.setField;
050
051import java.time.Instant;
052import java.util.HashMap;
053import java.util.Map;
054import java.util.Set;
055
056import javax.jcr.Node;
057import javax.jcr.PropertyType;
058import javax.jcr.RepositoryException;
059import javax.security.auth.login.LoginException;
060
061import org.fcrepo.kernel.api.observer.FedoraEvent;
062import org.fcrepo.kernel.api.services.ContainerService;
063import org.fcrepo.kernel.modeshape.ContainerImpl;
064
065import org.fcrepo.kernel.api.observer.EventType;
066import org.junit.Before;
067import org.junit.Test;
068import org.mockito.Mock;
069import org.modeshape.jcr.api.JcrTools;
070import org.modeshape.jcr.api.Repository;
071import org.modeshape.jcr.api.Session;
072
073import com.google.common.eventbus.EventBus;
074import org.apache.jena.rdf.model.Resource;
075
076/**
077 * @author mohideen
078 * @since 4/16/15.
079 */
080public class InternalAuditorTest {
081
082    private InternalAuditor testTnternalAuditor;
083
084    private static final String AUDIT_CONTAINER = "fcrepo.audit.container";
085
086    private static final String identifier = "27c605e4-98c6-4240-86be-f1bb1971d694";
087
088    private static final String identifierPath = "27/c6/05/e4/" + identifier;
089
090    private static final Instant timestamp = Instant.ofEpochMilli(1428676236521L);
091
092    private static final String userID = "bypassAdmin";
093
094    private static final String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36";
095
096    private static final Map<String, String> auxInfo = new HashMap<>();
097
098    private String baseUrl = "http://localhost:8080/rest";
099
100    @Mock
101    private EventBus mockBus;
102
103    @Mock
104    private Repository mockRepository;
105
106    @Mock
107    private ContainerService mockContainerService;
108
109    @Mock
110    private Session mockSession;
111
112    @Mock
113    private static ContainerImpl mockContainer;
114
115    @Mock
116    private static Node mockNode;
117
118    private static final String CONTENT_MOD = AUDIT + "contentModification";
119    private static final String CONTENT_REM = AUDIT + "contentRemoval";
120    private static final String METADATA_MOD = AUDIT + "metadataModification";
121
122    private static final String CONTENT_ADD = EVENT_TYPE + "ing";
123    private static final String OBJECT_ADD = EVENT_TYPE + "cre";
124    private static final String OBJECT_REM = EVENT_TYPE + "del";
125
126    @Before
127    public void setUp() {
128        testTnternalAuditor = spy(new InternalAuditor());
129        auxInfo.put(BASE_URL, baseUrl);
130        auxInfo.put(USER_AGENT, userAgent);
131        initMocks(this);
132        setField(testTnternalAuditor, "eventBus", mockBus);
133        setField(testTnternalAuditor, "repository", mockRepository);
134        setField(testTnternalAuditor, "session", mockSession);
135        setField(testTnternalAuditor, "containerService", mockContainerService);
136        setField(testTnternalAuditor, "AUDIT_CONTAINER_LOCATION", "/audit");
137    }
138
139    @Test
140    public void testRegister() throws RepositoryException, LoginException {
141        System.setProperty(AUDIT_CONTAINER, "/audit");
142        when(mockRepository.login()).thenReturn(mockSession);
143        setField(testTnternalAuditor, "jcrTools", mock(JcrTools.class));
144        testTnternalAuditor.register();
145        verify(mockContainerService).findOrCreate(mockSession, "/audit");
146        verify(mockBus).register(any(InternalAuditor.class));
147        System.clearProperty(AUDIT_CONTAINER);
148    }
149
150    @Test
151    public void testRegisterUnsuccessful() throws RepositoryException, LoginException  {
152        when(mockRepository.login()).thenReturn(mockSession);
153        testTnternalAuditor.register();
154        verify(mockContainerService, never()).findOrCreate(mockSession, eq(anyString()));
155        verify(mockBus, never()).register(any(InternalAuditor.class));
156    }
157
158    @Test
159    public void testUnregister() {
160        testTnternalAuditor.releaseConnections();
161        verify(mockBus).unregister(anyObject());
162    }
163
164    @Test
165    public void testNodeAddedWithProperties() throws Exception {
166        final Set<EventType> eventTypes = newSet(RESOURCE_CREATION, RESOURCE_MODIFICATION);
167        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Container");
168        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
169        final String eventID = "urn:uuid:" + identifier;
170        when(mockFedoraEvent.getEventID()).thenReturn(eventID);
171        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
172        when(mockContainer.getNode()).thenReturn(mockNode);
173        testTnternalAuditor.recordEvent(mockFedoraEvent);
174        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(RDF_NAMESPACE + "type"),
175                eq(createResource(AUDIT + "InternalEvent")));
176        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(RDF_NAMESPACE + "type"),
177                eq(createResource(PREMIS + "Event")));
178        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(RDF_NAMESPACE + "type"),
179                eq(createResource(PROV + "InstantaneousEvent")));
180        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventDateTime"),
181                eq(createTypedLiteral("2015-04-10T14:30:36Z", XSDdateTime)));
182        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventRelatedAgent"),
183                eq(createTypedLiteral(userID, XSDstring)));
184        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventRelatedAgent"),
185                eq(createTypedLiteral(userAgent, XSDstring)));
186        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
187                eq(createResource(OBJECT_ADD)));
188
189        verify(mockNode).setProperty(eq("premis:hasEventRelatedObject"),
190                eq("http://localhost:8080/rest/non/audit/container/path"), eq(PropertyType.URI));
191        verify(mockContainerService).findOrCreate( any(Session.class), eq("/audit/" + identifierPath));
192    }
193
194    @Test
195    public void testNodeRemoved() throws Exception {
196        final Set<EventType> eventTypes = singleton(RESOURCE_DELETION);
197        final Set<String> resourceTypes = emptySet();
198        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
199        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
200        when(mockContainer.getNode()).thenReturn(mockNode);
201        testTnternalAuditor.recordEvent(mockFedoraEvent);
202        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
203                eq(createResource(OBJECT_REM)));
204    }
205
206    @Test
207    public void testPropertiesChanged() throws Exception {
208        final Set<EventType> eventTypes = singleton(RESOURCE_MODIFICATION);
209        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Container");
210        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
211        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
212        when(mockContainer.getNode()).thenReturn(mockNode);
213        testTnternalAuditor.recordEvent(mockFedoraEvent);
214        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
215                eq(createResource(METADATA_MOD)));
216    }
217
218    @Test
219    public void testFileAdded() throws Exception {
220        final Set<EventType> eventTypes = newSet(RESOURCE_CREATION, RESOURCE_MODIFICATION);
221        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Binary");
222        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
223        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
224        when(mockContainer.getNode()).thenReturn(mockNode);
225        testTnternalAuditor.recordEvent(mockFedoraEvent);
226        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
227                eq(createResource(CONTENT_ADD)));
228    }
229
230    @Test
231    public void testFileChanged() throws Exception {
232        final Set<EventType> eventTypes = singleton(RESOURCE_MODIFICATION);
233        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Binary");
234        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
235        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
236        when(mockContainer.getNode()).thenReturn(mockNode);
237        testTnternalAuditor.recordEvent(mockFedoraEvent);
238        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
239                eq(createResource(CONTENT_MOD)));
240    }
241
242    @Test
243    public void testFileRemoved() throws Exception {
244        final Set<EventType> eventTypes = singleton(RESOURCE_DELETION);
245        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Binary");
246        final FedoraEvent mockFedoraEvent = setupMockEvent(eventTypes, resourceTypes);
247        when(mockContainerService.findOrCreate(any(Session.class), anyString())).thenReturn(mockContainer);
248        when(mockContainer.getNode()).thenReturn(mockNode);
249        testTnternalAuditor.recordEvent(mockFedoraEvent);
250        verify(testTnternalAuditor).createStatement(any(Resource.class), eq(PREMIS + "hasEventType"),
251                eq(createResource(CONTENT_REM)));
252    }
253
254    @Test
255    public void testGetAuditEventTypeCreation() throws Exception {
256        final Set<String> eventTypes = newSet(RESOURCE_CREATION.getType(), RESOURCE_MODIFICATION.getType());
257        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Container");
258        assertEquals(AuditUtils.getAuditEventType(eventTypes, resourceTypes), OBJECT_ADD);
259    }
260
261    @Test
262    public void testGetAuditEventTypeModification() throws Exception {
263        final Set<String> eventTypes = newSet(RESOURCE_MODIFICATION.getType());
264        final Set<String> resourceTypes = newSet(REPOSITORY + "Resource", REPOSITORY + "Container");
265        assertEquals(AuditUtils.getAuditEventType(eventTypes, resourceTypes), METADATA_MOD);
266    }
267
268    @Test
269    public void testGetAuditEventTypeRemoval() throws Exception {
270        final Set<String> eventTypes = newSet(RESOURCE_DELETION.getType());
271        final Set<String> resourceTypes = emptySet();
272        assertEquals(AuditUtils.getAuditEventType(eventTypes, resourceTypes), OBJECT_REM);
273    }
274
275    @Test (expected = IllegalArgumentException.class)
276    public void testGetEventPathInvalid() {
277        testTnternalAuditor.getEventPath(identifier);
278    }
279
280
281    @Test
282    public void testGetEventPath() {
283        final String path = testTnternalAuditor.getEventPath("urn:uuid:" + identifier);
284        assertEquals(identifierPath, path);
285    }
286
287
288    private static FedoraEvent setupMockEvent(final Set<EventType> eventTypes,
289                                           final Set<String> resourceTypes) throws RepositoryException {
290        final FedoraEvent mockFedoraEvent = mock(FedoraEvent.class);
291        when(mockFedoraEvent.getDate()).thenReturn(timestamp);
292        when(mockFedoraEvent.getUserID()).thenReturn(userID);
293        when(mockFedoraEvent.getInfo()).thenReturn(auxInfo);
294        when(mockFedoraEvent.getPath()).thenReturn("/non/audit/container/path");
295        when(mockFedoraEvent.getEventID()).thenReturn("urn:uuid:" + identifier);
296        when(mockFedoraEvent.getTypes()).thenReturn(eventTypes);
297        when(mockFedoraEvent.getResourceTypes()).thenReturn(resourceTypes);
298        return mockFedoraEvent;
299    }
300}