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}