Skip to content

Commit

Permalink
audit log WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
marcos-lg committed May 11, 2021
1 parent 2096774 commit 3a4d1c0
Show file tree
Hide file tree
Showing 60 changed files with 1,060 additions and 745 deletions.
9 changes: 8 additions & 1 deletion registry-events/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,20 @@
<groupId>org.gbif</groupId>
<artifactId>gbif-httputils</artifactId>
</dependency>

<dependency>
<groupId>org.gbif.registry</groupId>
<artifactId>registry-persistence</artifactId>
</dependency>

<!-- Third party dependencies -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
package org.gbif.registry.events;

import org.gbif.api.model.collections.CollectionEntity;
import org.gbif.api.model.collections.Institution;
import org.gbif.api.model.collections.CollectionEntityType;
import org.gbif.api.model.collections.Person;
import org.gbif.api.model.collections.request.CollectionSearchRequest;
import org.gbif.api.model.collections.request.InstitutionSearchRequest;
import org.gbif.api.model.collections.view.CollectionView;
import org.gbif.api.model.registry.Dataset;
import org.gbif.api.model.registry.Installation;
import org.gbif.api.model.registry.NetworkEntity;
Expand All @@ -32,15 +31,15 @@
import org.gbif.api.service.registry.InstallationService;
import org.gbif.api.service.registry.OrganizationService;
import org.gbif.registry.domain.ws.DerivedDataset;
import org.gbif.registry.events.collections.ChangedCollectionEntityComponentEvent;
import org.gbif.registry.events.collections.CreateCollectionEntityEvent;
import org.gbif.registry.events.collections.DeleteCollectionEntityEvent;
import org.gbif.registry.events.collections.SubEntityCollectionEvent;
import org.gbif.registry.events.collections.UpdateCollectionEntityEvent;
import org.gbif.registry.persistence.mapper.collections.dto.ChangeSuggestionDto;
import org.gbif.varnish.VarnishPurger;

import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

Expand Down Expand Up @@ -111,8 +110,38 @@
* <li>derivedDataset/dataset/{doiPrefix}/{doiSuffix} BAN
* <li>derivedDataset/user/{user} BAN
* </ul>
* <h4>Institution</h4>
* <ul>
* <li>grscicoll/institution/{key} PURGE
* <li>grscicoll/institution/{d.parentKey} PURGE
* <li>grscicoll/institution BAN
* <li>grscicoll/institution/suggest BAN
* <li>grscicoll/search BAN
* </ul>
* <h4>Collection</h4>
* <ul>
* <li>grscicoll/collection/{key} PURGE
* <li>grscicoll/collection/{d.parentKey} PURGE
* <li>grscicoll/collection BAN
* <li>grscicoll/collection/suggest BAN
* <li>grscicoll/search BAN
* </ul>
* <h4>Person</h4>
* <ul>
* <li>grscicoll/person/{key} PURGE
* <li>grscicoll/person/{d.parentKey} PURGE
* <li>grscicoll/person BAN
* <li>grscicoll/person/suggest BAN
* <li>grscicoll/institution/{key}/contact
* <li>grscicoll/collection/{key}/contact
* </ul>
* <h4>ChangeSuggestion</h4>
* <ul>
* <li>grscicoll/{collection|institution}/changeSuggestion/{key} PURGE
* <li>grscicoll/{collection|institution}/changeSuggestion BAN
* </ul>
*/
// TODO: documentation grscicoll
// TODO: clear grscicoll cache
public class VarnishPurgeListener {

private static final Logger LOG = LoggerFactory.getLogger(VarnishPurgeListener.class);
Expand Down Expand Up @@ -206,47 +235,65 @@ public final <T> void deleted(DeleteEvent<T> event) {
public final <T extends CollectionEntity> void createdCollection(
CreateCollectionEntityEvent<T> event) {
purgeEntityAndBanLists(
path("grscicoll", event.getObjectClass().getSimpleName().toLowerCase()),
path("grscicoll", event.getCollectionEntityType().name().toLowerCase()),
event.getNewObject().getKey());

if (event.getObjectClass().equals(Person.class)) {
if (event.getCollectionEntityType() == CollectionEntityType.PERSON) {
cascadePersonChange((Person) event.getNewObject());
}

purger.ban("grscicoll/search");
}

@Subscribe
public final <T extends CollectionEntity> void updatedCollection(
UpdateCollectionEntityEvent<T> event) {
purgeEntityAndBanLists(
path("grscicoll", event.getObjectClass().getSimpleName().toLowerCase()),
path("grscicoll", event.getCollectionEntityType().name().toLowerCase()),
event.getOldObject().getKey());

if (event.getObjectClass().equals(Person.class)) {
cascadePersonChange((Person) event.getOldObject(), (Person) event.getNewObject());
if (event.getCollectionEntityType() == CollectionEntityType.PERSON) {
cascadePersonChange((Person) event.getOldObject());
}

purger.ban("grscicoll/search");
}

@Subscribe
public final <T extends CollectionEntity> void deletedCollection(
DeleteCollectionEntityEvent<T> event) {
purgeEntityAndBanLists(
path("grscicoll", event.getObjectClass().getSimpleName().toLowerCase()),
path("grscicoll", event.getCollectionEntityType().name().toLowerCase()),
event.getOldObject().getKey());

if (event.getObjectClass().equals(Person.class)) {
if (event.getCollectionEntityType() == CollectionEntityType.PERSON) {
cascadePersonChange((Person) event.getOldObject());
}

purger.ban("grscicoll/search");
}

@Subscribe
public final void collectionEntityComponentChange(ChangedCollectionEntityComponentEvent event) {
purgeEntityAndBanLists(
path("grscicoll", event.getTargetClass().getSimpleName().toLowerCase()),
event.getTargetEntityKey());
public final <T extends CollectionEntity, R> void collectionSubEntityChange(
SubEntityCollectionEvent<T, R> event) {
if (event.getSubEntityClass().equals(ChangeSuggestionDto.class)) {
purgeEntityAndBanLists(
path(
"grscicoll",
event.getCollectionEntityType().name().toLowerCase(),
"changeSuggestion"),
event.getSubEntityKey());
} else {
purgeEntityAndBanLists(
path("grscicoll", event.getCollectionEntityType().name().toLowerCase()),
event.getCollectionEntityKey());
}

if (event.getTargetClass().equals(Person.class)) {
cascadePersonChange(personService.get(event.getTargetEntityKey()));
if (event.getCollectionEntityType() == CollectionEntityType.PERSON) {
cascadePersonChange(personService.get(event.getCollectionEntityKey()));
}

purger.ban("grscicoll/search");
}

@Subscribe
Expand Down Expand Up @@ -325,21 +372,17 @@ private void cascadeInstallationChange(Installation... installations) {

private void cascadePersonChange(Person... persons) {
Set<UUID> collectionKeys = new UUIDHashSet();
for (Person p : persons) {
List<CollectionView> collections =
collectionService
.list(CollectionSearchRequest.builder().contact(p.getKey()).build())
.getResults();
collections.forEach(c -> collectionKeys.add(c.getCollection().getKey()));
}

Set<UUID> institutionKeys = new UUIDHashSet();
for (Person p : persons) {
List<Institution> institutions =
institutionService
.list(InstitutionSearchRequest.builder().contact(p.getKey()).build())
.getResults();
institutions.forEach(i -> institutionKeys.add(i.getKey()));
collectionService
.list(CollectionSearchRequest.builder().contact(p.getKey()).build())
.getResults()
.forEach(c -> collectionKeys.add(c.getCollection().getKey()));

institutionService
.list(InstitutionSearchRequest.builder().contact(p.getKey()).build())
.getResults()
.forEach(i -> institutionKeys.add(i.getKey()));
}

// /collection/{collectionKey}/contact BAN
Expand All @@ -362,15 +405,23 @@ private void cascadeDerivedDatasetChange(DerivedDataset derivedDataset) {
* check which entity class was supplied, but as it is some type of NetworkEntity we deal with the
* right urls.
*/
private void purgeEntityAndBanLists(String rootPath, UUID key) {
private void purgeEntityAndBanLists(String rootPath, String entityPath) {

// purge entity detail
purger.purge(path(rootPath, key));
purger.purge(entityPath);

// banRegex lists and searches
purger.ban(String.format("%s(/search|/suggest)?[^/]*$", rootPath));
}

private void purgeEntityAndBanLists(String rootPath, UUID key) {
purgeEntityAndBanLists(rootPath, path(rootPath, key));
}

private void purgeEntityAndBanLists(String rootPath, int key) {
purgeEntityAndBanLists(rootPath, path(rootPath, key));
}

private void purgeEntityAndBanLists(Class cl, UUID key) {
purgeEntityAndBanLists(path(cl.getSimpleName().toLowerCase()), key);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.gbif.registry.events.collections;

import org.gbif.api.model.collections.CollectionEntity;
import org.gbif.registry.persistence.mapper.collections.AuditLogMapper;
import org.gbif.registry.persistence.mapper.collections.dto.AuditLogDto;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import brave.Tracer;

// TODO: listeners que sean @Async?? luego poner @EnableAsync en Application

@Component
public class AuditLogger {

private final Tracer tracer;
private final AuditLogMapper auditLogMapper;
private final ObjectMapper objectMapper;

@Autowired
public AuditLogger(Tracer tracer, AuditLogMapper auditLogMapper, ObjectMapper objectMapper) {
this.tracer = tracer;
this.auditLogMapper = auditLogMapper;
this.objectMapper = objectMapper;
}

@EventListener
public <T extends CollectionEntity> void logCreatedEvents(CreateCollectionEntityEvent<T> event) {
AuditLogDto dto = collectionBaseEventToDto(event);
dto.setCollectionEntityKey(event.getNewObject().getKey());
dto.setPostState(toJson(event.getNewObject()));
auditLogMapper.create(dto);
}

@EventListener
public <T extends CollectionEntity> void logUpdatedEvents(UpdateCollectionEntityEvent<T> event) {
AuditLogDto dto = collectionBaseEventToDto(event);
dto.setCollectionEntityKey(event.getNewObject().getKey());
dto.setPreState(toJson(event.getOldObject()));
dto.setPostState(toJson(event.getNewObject()));
auditLogMapper.create(dto);
}

@EventListener
public <T extends CollectionEntity> void logDeletedEvents(DeleteCollectionEntityEvent<T> event) {
AuditLogDto dto = collectionBaseEventToDto(event);
dto.setCollectionEntityKey(event.getOldObject().getKey());
dto.setPreState(toJson(event.getOldObject()));
dto.setPostState(toJson(event.getDeletedObject()));
auditLogMapper.create(dto);
}

@EventListener
public <T extends CollectionEntity> void logReplacedEvents(ReplaceEntityEvent<T> event) {
AuditLogDto dto = collectionBaseEventToDto(event);
dto.setCollectionEntityKey(event.getTargetEntityKey());
dto.setReplacementKey(event.getReplacementKey());
auditLogMapper.create(dto);
}

@EventListener
public <T extends CollectionEntity, R> void logSubEntityEvents(
SubEntityCollectionEvent<T, R> event) {
AuditLogDto dto = subEntityToDto(event);
auditLogMapper.create(dto);
}

private <T extends CollectionEntity, R> AuditLogDto subEntityToDto(
SubEntityCollectionEvent<T, R> event) {
AuditLogDto dto = collectionBaseEventToDto(event);
dto.setSubEntityType(event.getSubEntity().getClass().getSimpleName());
dto.setCollectionEntityKey(event.getCollectionEntityKey());
dto.setSubEntityKey(event.getSubEntityKey());

if (event.getEventType() == EventType.CREATE) {
dto.setPostState(toJson(event.getSubEntity()));
} else if (event.getEventType() == EventType.DELETE) {
dto.setPreState(toJson(event.getSubEntity()));
}

return dto;
}

private <T extends CollectionEntity> AuditLogDto collectionBaseEventToDto(
CollectionsBaseEvent<T> event) {
AuditLogDto dto = new AuditLogDto();
dto.setTraceId(tracer.currentSpan().context().traceId());
dto.setCollectionEntityType(event.getCollectionEntityType());
dto.setOperation(event.getEventType().name());
dto.setCreatedBy(getUsername());
return dto;
}

// TODO: getusername y toJson deberian ir a una clase de utils ya q lo uso en changeSuggestion tb

protected String getUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName();
}

protected String toJson(Object entity) {
try {
return objectMapper.writeValueAsString(entity);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Cannot serialize entity", e);
}
}
}
Loading

0 comments on commit 3a4d1c0

Please sign in to comment.