...
 
Commits (33)
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
stages:
- compile
- deploy
......@@ -6,7 +21,7 @@ run tests in branches:
stage: compile
image: maven:3-jdk-8
script:
- MAVEN_OPTS="${MAVEN_OPTS} -Dorg.slf4j.simpleLogger.defaultLogLevel=warn" mvn test -B
- mvn test -B
except:
- master
- /^application\-blocks\-/
......@@ -29,6 +44,6 @@ publish artifacts on central:
- echo "${GPG_KEY_SECRET}" | gpg --batch --import
- gpg --list-keys
script:
- MAVEN_OPTS="${MAVEN_OPTS} -Dorg.slf4j.simpleLogger.defaultLogLevel=warn" mvn -P release clean deploy --settings .ci-maven-settings.xml -B -U
- mvn -P release clean deploy --settings .ci-maven-settings.xml -B -U
only:
- /^application\-blocks\-/
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-parent</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-blocks-auditlog</artifactId>
......@@ -79,13 +79,13 @@
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-security</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-core</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
......
......@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
......@@ -427,7 +428,7 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
deleted = convertEntityId(deleted);
}
LOG.trace("prev=" + deleted.toString() + " curr=" + null);
LOG.trace("prev={} curr=null", deleted);
recordDelete(pc.getOwner(), (Long) key, propertyName, deleted.toString(), referencedEntity);
}
......@@ -466,8 +467,10 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
Collection<?> previous = null;
final Serializable snap = pc.getStoredSnapshot();
if (snap instanceof Map<?, ?>) {
// LOG.trace("Snap keys: {}", ((Map) snap).keySet());
// LOG.trace("Snap vals: {}", ((Map) snap).values());
final Map<?, ?> snapMap = (Map<?, ?>) snap;
previous = snapMap.values();
previous = snapMap.keySet();
} else if (snap instanceof Collection<?>) {
final Collection<?> snapList = (Collection<?>) snap;
previous = snapList;
......@@ -484,8 +487,29 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
remaining = convertEntityId(remaining);
}
LOG.trace("prev=" + previous + " curr=" + remaining);
recordChange(pc.getOwner(), (Long) key, propertyName, previous.toString(), remaining.toString(), referencedEntity);
LOG.trace("prev={} curr={}", previous, remaining);
recordChange(pc.getOwner(), (Long) key, propertyName, collectionToStringSorted(previous), collectionToStringSorted(remaining), referencedEntity);
}
/**
* Produce a string representation of the collection. Set elements are sorted,
* other collection types return `#toString`.
*
* @param collection the collection
* @return the string
*/
private String collectionToStringSorted(Collection<?> collection) {
if (collection == null) {
return null;
}
if (collection instanceof Set<?>) {
LOG.trace("Converting to sorted list {} -> {}", collection, collection.stream().sorted().map(Object::toString).collect(Collectors.joining(", ", "[", "]")));
return collection.stream().sorted().map(Object::toString).collect(Collectors.joining(", ", "[", "]"));
} else {
LOG.trace("Not sorting {}: {}", collection.getClass(), collection);
}
return collection.toString();
}
/**
......@@ -501,7 +525,7 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
// Field
final Field field = ReflectionUtils.findField(class1, propertyName);
if (field != null) {
LOG.trace("Found field: " + field + "\n\ttype=" + field.getType() + "\n\tgeneric=" + field.getGenericType() + "\n\tgenericTN=" + field.getGenericType().getTypeName());
LOG.trace("Found field: {}\n\ttype={}\n\tgeneric={}\n\tgenericTN={}", field, field.getType(), field.getGenericType(), field.getGenericType().getTypeName());
final ResolvableType t = ResolvableType.forField(field, class1);
if (t.hasGenerics()) {
LOG.trace("\tResoved={} returning={}", t.toString(), t.resolveGeneric(0));
......@@ -516,7 +540,7 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
try {
final Method method = class1.getMethod("get" + StringUtils.capitalize(propertyName));
if (method != null) {
LOG.trace("Didn't find field, found the method: " + method.getReturnType().getTypeParameters()[0]);
LOG.trace("Didn't find field, found the method: {}", method.getReturnType().getTypeParameters()[0]);
}
} catch (SecurityException | NoSuchMethodException e) {
e.printStackTrace();
......@@ -531,7 +555,7 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
* @return the collection
*/
private Collection<Object> convertEntityId(final Collection<?> previous) {
final Collection<Object> converted = new ArrayList<>();
final List<Object> converted = new ArrayList<>();
for (final Object p : previous) {
if (p instanceof EntityId) {
converted.add(((EntityId) p).getId());
......@@ -539,6 +563,13 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
converted.add(p);
}
}
converted.sort((a, b) -> {
if (a instanceof Long && b instanceof Long) {
return Long.compare((Long) a, (Long) b);
} else {
return Integer.compare(a.hashCode(), b.hashCode());
}
});
return converted;
}
......@@ -785,12 +816,12 @@ public class AuditTrailInterceptor extends EmptyInterceptor implements Initializ
if (getter != null) {
final Object result = getter.invoke(entity);
if (result == null) {
LOG.trace(entity + " is transient, has " + methodName + " == null");
LOG.trace("{} is transient, has {} == null", entity, methodName);
return true;
} else if (result instanceof Number) {
final Number r = (Number) result;
if (r.longValue() < 0) {
LOG.trace(entity + " is transient, has " + methodName + " = " + result + " < 0");
LOG.trace("{} is transient, has {} = {} < 0", entity, methodName, result);
return true;
} else {
// LOG.trace(entity + " is not transient, has " + methodName + " = " + result +
......
......@@ -39,6 +39,8 @@ import org.genesys.blocks.model.ClassPK;
import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedBy;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The Class AuditLog.
*/
......@@ -102,10 +104,12 @@ public class AuditLog implements Serializable {
/** The previous entity. */
@Transient
@JsonProperty
private Object previousEntity;
/** The next entity. */
@Transient
@JsonProperty
private Object newEntity;
/**
......@@ -294,9 +298,9 @@ public class AuditLog implements Serializable {
* @param entity the new previous entity
*/
public void setPreviousEntity(Object entity) {
this.previousEntity=entity;
this.previousEntity = entity;
}
/**
* Gets the previous entity.
*
......@@ -305,7 +309,7 @@ public class AuditLog implements Serializable {
public Object getPreviousEntity() {
return previousEntity;
}
/**
* Sets the new entity.
*
......@@ -314,7 +318,7 @@ public class AuditLog implements Serializable {
public void setNewEntity(Object newEntity) {
this.newEntity = newEntity;
}
/**
* Gets the new entity.
*
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -19,12 +19,14 @@ package org.genesys.blocks.auditlog.model;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
......@@ -56,6 +58,9 @@ public class ExampleAuditedEntity extends BasicModel {
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "entity_set", joinColumns = @JoinColumn(name = "entityId", referencedColumnName = "id"))
private Set<String> set;
@ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
private List<OtherEntity> others;
/**
* Gets the name.
......@@ -128,4 +133,22 @@ public class ExampleAuditedEntity extends BasicModel {
public void setSet(Set<String> set) {
this.set = set;
}
/**
* Gets the others.
*
* @return the others
*/
public List<OtherEntity> getOthers() {
return others;
}
/**
* Sets the others.
*
* @param others the new others
*/
public void setOthers(List<OtherEntity> others) {
this.others = others;
}
}
/*
* Copyright 2019 Global Crop Diversity Trust
*
* 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 org.genesys.blocks.auditlog.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import org.genesys.blocks.model.BasicModel;
/**
* The Class OtherEntity.
*/
@Entity
public class OtherEntity extends BasicModel {
private static final long serialVersionUID = 1L;
@Column(unique = true)
private String name;
public OtherEntity() {
}
public OtherEntity(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/*
* Copyright 2019 Global Crop Diversity Trust
*
* 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 org.genesys.blocks.auditlog.persistence;
import org.genesys.blocks.auditlog.model.OtherEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* The OtherEntityRepository.
*
* @author Matija Obreza
*/
@Repository
public interface OtherEntityRepository extends JpaRepository<OtherEntity, Long> {
OtherEntity findByName(String name);
}
......@@ -15,28 +15,27 @@
*/
package org.genesys.blocks.auditlog.service;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.genesys.blocks.auditlog.model.AuditAction;
import org.genesys.blocks.auditlog.model.AuditLog;
import org.genesys.blocks.auditlog.model.ExampleAuditedEntity;
import org.genesys.blocks.auditlog.model.OtherEntity;
import org.genesys.blocks.auditlog.persistence.ExampleAuditedEntityRepository;
import org.genesys.blocks.auditlog.persistence.OtherEntityRepository;
import org.genesys.blocks.auditlog.test.ServiceTest;
import org.genesys.blocks.model.ClassPK;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* The Class AuditTrailServiceTest.
*/
......@@ -54,9 +53,13 @@ public class AuditTrailServiceTest extends ServiceTest {
@Autowired
private ExampleAuditedEntityRepository exampleAuditedEntityRepository;
@Autowired
private OtherEntityRepository otherEntityRepository;
@Override
public void cleanup() {
exampleAuditedEntityRepository.deleteAll();
otherEntityRepository.deleteAll();
auditLogRepository.deleteAll();
}
......@@ -242,15 +245,15 @@ public class AuditTrailServiceTest extends ServiceTest {
assertThat(lastLog.getPreviousState(), is("[1, 2, 3]"));
assertThat(lastLog.getNewState(), is("[2, 3, 4]"));
entity.getSet().clear();
entity.getSet().remove("piper");
entity = exampleAuditedEntityService.save(entity);
// printAuditLogs(entity);
assertThat(listAuditLogs(entity), hasSize(2));
lastLog = lastAuditLog(entity);
assertThat(lastLog.getAction(), is(AuditAction.UPDATE));
assertThat(lastLog.getPropertyName(), is("set"));
assertThat(lastLog.getPreviousState(), is("[zebra, banana, piper]"));
assertThat(lastLog.getNewState(), is("[]"));
assertThat(lastLog.getPreviousState(), is("[banana, piper, zebra]"));
assertThat(lastLog.getNewState(), is("[banana, zebra]"));
entity.getSet().clear();
entity.getSet().add("c++");
......@@ -260,7 +263,7 @@ public class AuditTrailServiceTest extends ServiceTest {
lastLog = lastAuditLog(entity);
assertThat(lastLog.getAction(), is(AuditAction.UPDATE));
assertThat(lastLog.getPropertyName(), is("set"));
assertThat(lastLog.getPreviousState(), is("[]"));
assertThat(lastLog.getPreviousState(), is("[banana, zebra]"));
assertThat(lastLog.getNewState(), is("[c++]"));
assertThat(entity.getList(), hasSize(3));
......@@ -274,9 +277,63 @@ public class AuditTrailServiceTest extends ServiceTest {
lastLog = lastAuditLog(entity);
assertThat(lastLog.getAction(), is(AuditAction.UPDATE));
assertThat(lastLog.getPropertyName(), is("set"));
assertThat(lastLog.getPreviousState(), is("[]"));
assertThat(lastLog.getPreviousState(), is("[banana, zebra]"));
assertThat(lastLog.getNewState(), is("[c++]"));
}
/**
* Test referenced entity audit log.
*/
@Test
public void testReferencedEntityListAuditLog() {
OtherEntity other1 = new OtherEntity("Other1");
other1 = otherEntityRepository.save(other1);
OtherEntity other2 = new OtherEntity("Other2");
other2 = otherEntityRepository.save(other2);
ExampleAuditedEntity entity = new ExampleAuditedEntity();
entity.setName("Test 1");
entity = exampleAuditedEntityService.save(entity);
assertThat(listAuditLogs(entity), hasSize(0));
entity.setOthers(Arrays.asList(other1));
entity = exampleAuditedEntityService.save(entity);
assertThat(listAuditLogs(entity), hasSize(1));
assertThat(listAuditLogs(entity).get(0).getPreviousEntity(), is(nullValue()));
assertThat(listAuditLogs(entity).get(0).getNewEntity(), is(nullValue()));
assertThat(listAuditLogs(entity).get(0).getNewState(), is(Arrays.toString(new Long[] { other1.getId() })));
entity.getOthers().clear();
entity.getOthers().add(other1);
entity.getOthers().add(other2);
entity = exampleAuditedEntityService.save(entity);
assertThat(listAuditLogs(entity), hasSize(2));
assertThat(listAuditLogs(entity).get(0).getNewState(), is(Arrays.toString(new Long[] { other1.getId(), other2.getId() })));
entity.getOthers().clear();
entity.getOthers().add(other2);
entity.getOthers().add(other1);
entity = exampleAuditedEntityService.save(entity);
assertThat(listAuditLogs(entity), hasSize(2));
assertThat(listAuditLogs(entity).get(0).getNewState(), is(Arrays.toString(new Long[] { other1.getId(), other2.getId() })));
entity.getOthers().clear();
entity.getOthers().add(other1);
entity.getOthers().add(other2);
entity = exampleAuditedEntityService.save(entity);
assertThat(listAuditLogs(entity), hasSize(2));
assertThat(listAuditLogs(entity).get(0).getNewState(), is(Arrays.toString(new Long[] { other1.getId(), other2.getId() })));
entity.getOthers().clear();
entity.getOthers().add(other2);
entity = exampleAuditedEntityService.save(entity);
// printAuditLogs(entity);
// System.out.println("\n\n\n");
assertThat(listAuditLogs(entity), hasSize(3));
assertThat(listAuditLogs(entity).get(0).getNewState(), is(Arrays.toString(new Long[] { other2.getId() })));
}
}
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-parent</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-blocks-core</artifactId>
......
......@@ -23,8 +23,9 @@ import java.util.UUID;
* UUID column should not be updatable:
*
* <pre>
* &#64;Column(updatable = false, nullable = false, columnDefinition = "binary(16)")
* private UUID uuid;
* &#64;Column(unique = true, updatable = false, nullable = false)
* &#64;@Type(type = "uuid-binary")
* protected UUID uuid;
* </pre>
*
* The entity must generate a UUID before persisting the data:
......
......@@ -21,6 +21,11 @@ import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import org.hibernate.annotations.Type;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldIndex;
import org.springframework.data.elasticsearch.annotations.FieldType;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
......@@ -35,7 +40,9 @@ public abstract class UuidModel extends AuditedVersionedModel implements IdUUID
private static final long serialVersionUID = 2639238742778129554L;
/** The uuid. */
@Column(unique = true, updatable = false, nullable = false, columnDefinition = "binary(16)")
@Column(unique = true, updatable = false, nullable = false)
@Type(type = "uuid-binary")
@Field(type = FieldType.String, store = false, index = FieldIndex.not_analyzed)
protected UUID uuid;
/**
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,15 +15,16 @@
*/
package org.genesys.blocks.model.filters;
import java.util.HashSet;
import java.util.Set;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.EntityPathBase;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.QAuditedVersionedModel;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.EntityPathBase;
/**
* {@link AuditedVersionedModel} match by sample filters.
*
......@@ -67,4 +68,52 @@ public abstract class AuditedVersionedModelFilter<T extends AuditedVersionedMode
builder.and(lastModifiedDate.buildQuery(auditedVersionedModel.lastModifiedDate));
}
}
/**
* Created by.
*
* @return the sets the
*/
public synchronized Set<Long> createdBy() {
if (createdBy == null) {
createdBy = new HashSet<>();
}
return createdBy;
}
/**
* Last modified by.
*
* @return the sets the
*/
public synchronized Set<Long> lastModifiedBy() {
if (lastModifiedBy == null) {
lastModifiedBy = new HashSet<>();
}
return lastModifiedBy;
}
/**
* Created date.
*
* @return the date filter
*/
public synchronized DateFilter createdDate() {
if (createdDate == null) {
createdDate = new DateFilter();
}
return createdDate;
}
/**
* Last modified date.
*
* @return the date filter
*/
public synchronized DateFilter lastModifiedDate() {
if (lastModifiedDate == null) {
lastModifiedDate = new DateFilter();
}
return lastModifiedDate;
}
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,9 +15,14 @@
*/
package org.genesys.blocks.model.filters;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import com.querydsl.core.types.dsl.CollectionPathBase;
import com.querydsl.core.types.dsl.DslExpression;
import com.querydsl.core.types.dsl.PathBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.BasicModel;
import org.genesys.blocks.model.QBasicModel;
......@@ -43,7 +48,7 @@ public abstract class BasicModelFilter<T extends BasicModelFilter<T, R>, R exten
static {
// Any objectMapper configuration goes here
jsonizer.setSerializationInclusion(Include.NON_DEFAULT);
jsonizer.setSerializationInclusion(Include.NON_NULL);
}
/** The id. */
......@@ -60,11 +65,11 @@ public abstract class BasicModelFilter<T extends BasicModelFilter<T, R>, R exten
public Set<String> NOTNULL;
/**
* Builds the DSL query.
* Builds the DSL predicate.
*
* @return the predicate
*/
public abstract Predicate buildQuery();
public abstract Predicate buildPredicate();
/**
* Builds the query.
......@@ -73,23 +78,66 @@ public abstract class BasicModelFilter<T extends BasicModelFilter<T, R>, R exten
* @param basicModel the basic model
* @param builder the builder
*/
protected void buildQuery(final EntityPathBase<R> instance, final QBasicModel basicModel, final BooleanBuilder builder) {
protected void buildPredicate(final EntityPathBase<R> instance, final QBasicModel basicModel, final BooleanBuilder builder) {
if (CollectionUtils.isNotEmpty(id)) {
builder.and(basicModel.id.in(id));
}
if (NULL != null && !NULL.isEmpty()) {
Class<?> clazz = instance.getClass();
NULL.forEach(nullProp -> builder.and(getProperty(instance, clazz, nullProp).isNull()));
final Class<?> clazz = instance.getClass();
NULL.forEach(nullProp -> {
DslExpression expression = getProperty(instance, clazz, nullProp);
if (expression instanceof SimpleExpression) {
builder.and(((SimpleExpression) expression).isNull());
} else if (expression instanceof CollectionPathBase) {
builder.and(((CollectionPathBase) expression).size().eq(0));
}
});
}
if (NOTNULL != null && !NOTNULL.isEmpty()) {
Class<?> clazz = instance.getClass();
NOTNULL.forEach(notNullProp -> builder.and(getProperty(instance, clazz, notNullProp).isNotNull()));
final Class<?> clazz = instance.getClass();
NOTNULL.forEach(notNullProp -> {
DslExpression expression = getProperty(instance, clazz, notNullProp);
if (expression instanceof SimpleExpression) {
builder.and(((SimpleExpression) expression).isNotNull());
} else if (expression instanceof CollectionPathBase) {
builder.and(((CollectionPathBase) expression).size().gt(0));
}
});
}
if (NOT != null) {
builder.and(NOT.buildQuery().not());
builder.and(NOT.buildPredicate().not());
}
}
public void clearFilter(String jsonPath) throws NoSuchFieldException, IllegalAccessException {
String[] paths = jsonPath.split("\\.");
Object toClear = this;
Class<?> clazz = this.getClass();
Field field = clazz.getField(paths[0]);
for (int i = 1; i < paths.length; i++) {
clazz = field.getType();
if (!BasicModelFilter.class.isAssignableFrom(clazz))
break;
toClear = field.get(toClear);
if (toClear == null)
return;
field = clazz.getField(paths[i]);
}
if (this.NULL != null) {
this.NULL.remove(jsonPath);
}
if (this.NOTNULL != null) {
this.NOTNULL.remove(jsonPath);
}
field.set(toClear, null);
}
// @Override
// public int hashCode() {
// String str = toString();
......@@ -114,25 +162,110 @@ public abstract class BasicModelFilter<T extends BasicModelFilter<T, R>, R exten
// return true;
// }
private SimpleExpression<?> getProperty(final EntityPathBase<R> instance, final Class<?> clazz, final String nullProp) {
private DslExpression<?> getProperty(final EntityPathBase<R> instance, final Class<?> clazz, final String nullProp) {
try {
Field prop = clazz.getField(nullProp);
if (SimpleExpression.class.isAssignableFrom(prop.getType())) {
return (SimpleExpression<?>) prop.get(instance);
// build path for nesting filters
if (nullProp.contains(".")) {
String paths[] = nullProp.split("\\.");
PathBuilder<?> pathBuilder = new PathBuilder<>(instance.getType(), instance.getMetadata());
Class clazzToCheck = clazz;
for (String path: paths) {
Field field = clazzToCheck.getField(path);
if (CollectionPathBase.class.isAssignableFrom(field.getType())) {
pathBuilder.getSet(path, field.getDeclaringClass());
} else {
pathBuilder.getSimple(path, field.getDeclaringClass());
}
clazzToCheck = field.getType();
}
return pathBuilder.getSimple(nullProp, clazzToCheck);
} else {
throw new NoSuchFieldException("Property " + nullProp + " is not a SimpleExpression");
final Field prop = clazz.getField(nullProp);
if (SimpleExpression.class.isAssignableFrom(prop.getType())) {
return (SimpleExpression<?>) prop.get(instance);
} else {
throw new NoSuchFieldException("Property " + nullProp + " is not a SimpleExpression");
}
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
// test if nested objects have required property
// will test only the first level of nesting
if (e instanceof NoSuchFieldException) {
Field fields[] = clazz.getDeclaredFields();
for (Field field: fields) {
Class superClazz = field.getType().getSuperclass();
if (superClazz != null && EntityPathBase.class.isAssignableFrom(superClazz)) {
try {
Field result = field.getType().getField(nullProp);
PathBuilder<?> pathBuilder = new PathBuilder<>(instance.getType(), instance.getMetadata());
if (CollectionPathBase.class.isAssignableFrom(result.getType())) {
return pathBuilder.getSet(nullProp, result.getDeclaringClass());
}
return pathBuilder.getSimple(nullProp, result.getDeclaringClass());
} catch (NoSuchFieldException e1) {
}
}
}
}
throw new RuntimeException("Error accessing field " + nullProp + " for isNull() in " + instance.getClass());
}
}
/**
* Copy by serializing to JSON and de-serializing to specified type.
*
* @param <X> the generic type
* @param targetType the target type
* @return the x
* @throws IOException Signals that an I/O exception has occurred.
*/
public <X> X copy(final Class<X> targetType) throws IOException {
return jsonizer.readValue(jsonizer.writeValueAsString(this), targetType);
}
@Override
public String toString() {
try {
return jsonizer.writeValueAsString(this);
} catch (JsonProcessingException e) {
} catch (final JsonProcessingException e) {
throw new RuntimeException("Could not serialize to JSON: " + e.getMessage(), e);
}
}
/**
* Id.
*
* @return the sets the
*/
public synchronized Set<Long> id() {
if (id == null) {
id = new HashSet<>();
}
return id;
}
/**
* Checks if is null.
*
* @return the sets the
*/
public synchronized Set<String> isNull() {
if (NULL == null) {
NULL = new HashSet<>();
}
return NULL;
}
/**
* Not null.
*
* @return the sets the
*/
public synchronized Set<String> notNull() {
if (NOTNULL == null) {
NOTNULL = new HashSet<>();
}
return NOTNULL;
}
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -47,6 +47,7 @@ public class DateFilter {
*
* @deprecated Use {@link #ge} and {@link #le}
*/
@Deprecated
public Date[] between;
/**
......@@ -69,7 +70,7 @@ public class DateFilter {
if (lt != null) {
and.and(date.lt(lt));
}
if ((between != null) && (between.length == 2)) {
if (between != null && between.length == 2) {
and.and(date.between(between[0], between[1]));
}
return and;
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,6 +15,7 @@
*/
package org.genesys.blocks.model.filters;
import java.util.HashSet;
import java.util.Set;
import com.querydsl.core.BooleanBuilder;
......@@ -47,8 +48,26 @@ public class NumberFilter<T extends Number & Comparable<?>> {
*
* @deprecated Use {@link #ge} and {@link #le}
*/
@Deprecated
public T[] between;
/**
* Instantiates a new number filter.
*/
public NumberFilter() {
}
/**
* Instantiates a new number filter.
*
* @param ge the ge
* @param le the le
*/
public NumberFilter(final T ge, final T le) {
this.ge = ge;
this.le = le;
}
/**
* Builds the query.
*
......@@ -72,9 +91,21 @@ public class NumberFilter<T extends Number & Comparable<?>> {
if (le != null) {
and.and(val.loe(le));
}
if ((between != null) && (between.length == 2)) {
if (between != null && between.length == 2) {
and.and(val.between(between[0], between[1]));
}
return and;
}
/**
* Eq.
*
* @return the sets the
*/
public synchronized Set<T> eq() {
if (eq == null) {
eq = new HashSet<>();
}
return eq;
}
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,6 +15,7 @@
*/
package org.genesys.blocks.model.filters;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
......@@ -51,4 +52,16 @@ public abstract class UuidModelFilter<T extends UuidModelFilter<T, R>, R extends
builder.and(uuidModel.uuid.in(uuid));
}
}
/**
* Uuid.
*
* @return the sets the
*/
public synchronized Set<UUID> uuid() {
if (uuid == null) {
uuid = new HashSet<>();
}
return uuid;
}
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,15 +15,16 @@
*/
package org.genesys.blocks.model.filters;
import java.util.HashSet;
import java.util.Set;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.EntityPathBase;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.QVersionedModel;
import org.genesys.blocks.model.VersionedModel;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.EntityPathBase;
/**
* {@link VersionedModel} match by sample filters.
*
......@@ -46,7 +47,7 @@ public abstract class VersionedModelFilter<T extends VersionedModelFilter<T, R>,
* @param builder the builder
*/
protected void buildQuery(final EntityPathBase<R> instance, final QVersionedModel versionedModel, final BooleanBuilder builder) {
super.buildQuery(instance, versionedModel._super, builder);
super.buildPredicate(instance, versionedModel._super, builder);
if (CollectionUtils.isNotEmpty(version)) {
builder.and(versionedModel.version.in(version));
}
......@@ -54,4 +55,16 @@ public abstract class VersionedModelFilter<T extends VersionedModelFilter<T, R>,
builder.and(versionedModel.active.eq(active));
}
}
/**
* Version.
*
* @return the sets the
*/
public synchronized Set<Integer> version() {
if (version == null) {
version = new HashSet<>();
}
return version;
}
}
......@@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.util.StdConverter;
/**
* The JsonSidConverter converts SID IDs to SID names.
*/
public class JsonSidConverter extends StdConverter<Long, Object> {
public class JsonSidConverter extends StdConverter<Long, String> {
private static SidProvider SID_PROVIDER;
......@@ -41,12 +41,12 @@ public class JsonSidConverter extends StdConverter<Long, Object> {
* Convert SID ID to SID name using SID_PROVIDER (when available)
*/
@Override
public Object convert(Long value) {
public String convert(Long value) {
if (value == null) {
return null;
} else {
if (SID_PROVIDER == null) {
return value;
return Long.toString(value);
} else {
return SID_PROVIDER.getSidName(value);
}
......
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
</parent>
<artifactId>application-blocks-parent</artifactId>
<packaging>pom</packaging>
......
......@@ -18,7 +18,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Application Building Blocks</name>
<description>This is the shared project pom.</description>
......@@ -49,7 +49,7 @@
<connection>scm:https://gitlab.croptrust.org/genesys-pgr/application-blocks</connection>
<developerConnection>scm:git:git@gitlab.croptrust.org:genesys-pgr/application-blocks.git</developerConnection>
<url>git@gitlab.croptrust.org:genesys-pgr/application-blocks.git</url>
<tag>application-blocks-1.5</tag>
<tag>HEAD</tag>
</scm>
<issueManagement>
......
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-parent</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-blocks-security</artifactId>
......@@ -80,7 +80,7 @@
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-core</artifactId>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
......
......@@ -154,6 +154,16 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
@Type(type = "org.hibernate.type.TextType")
private String description;
/** Allowed CORS origins */
@Transient
@JsonView(JsonViews.Protected.class)
private Set<String> allowedOrigins = new HashSet<>();
/** The origins. */
@JsonIgnore
@Column(nullable = true, length = 200)
private String origins;
/**
* Instantiates a new o auth client.
*/
......@@ -179,6 +189,7 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
autoApproveScope = autoApproveScopes.stream().collect(Collectors.joining(";"));
grants = grantTypes.stream().collect(Collectors.joining(";"));
redirect = redirectUris.stream().collect(Collectors.joining(";"));
origins = allowedOrigins.stream().collect(Collectors.joining(";"));
}
/**
......@@ -201,6 +212,9 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
if (redirect != null) {
Arrays.stream(StringUtils.split(redirect, ";")).filter(r -> StringUtils.isNotBlank(r)).forEach(u -> redirectUris.add(u));
}
if (origins != null) {
Arrays.stream(StringUtils.split(origins, ";")).filter(r -> StringUtils.isNotBlank(r)).forEach(u -> allowedOrigins.add(u));
}
}
/*
......@@ -645,6 +659,25 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
this.scopes.clear();
this.scopes.addAll(source.scopes);
this.allowedOrigins.clear();
this.allowedOrigins.addAll(source.allowedOrigins);
return this;
}
public String getOrigins() {
return origins;
}
public void setOrigins(String origins) {
this.origins = origins;
}
public Set<String> getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(Set<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
}
......@@ -618,6 +618,8 @@ public class OAuthServiceImpl implements OAuthClientDetailsService, OAuthTokenSt
*/
@Override
@Transactional
@CacheEvict(cacheNames = { "oauthclient" }, key = "#client.clientId", condition = "#client != null")
public OAuthClient removeClient(final OAuthClient client) {
oauthClientRepository.delete(client);
return client;
......@@ -681,6 +683,7 @@ public class OAuthServiceImpl implements OAuthClientDetailsService, OAuthTokenSt
*/
@Override
@Transactional
@CacheEvict(cacheNames = { "oauthclient" }, key = "#updates.clientId", condition = "#updates != null")
public OAuthClient updateClient(final long id, final int version, final OAuthClient updates) {
OAuthClient client = oauthClientRepository.findByIdAndVersion(id, version);
client.apply(updates);
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* 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 org.genesys.blocks.security.component;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.filter.OncePerRequestFilter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* Filter OAuth2 requests and check that Origin header matches what we have on
* file.
*/
public class OAuthClientOriginCheckFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("oauthService")
private ClientDetailsService clientDetailsService;
private LoadingCache<String, Set<String>> clientOriginsCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(
new CacheLoader<String, Set<String>>() {
public Set<String> load(String clientId) {
if (logger.isInfoEnabled()) {
logger.info("Loading allowed origins for client: " + clientId);
}
OAuthClient clientDetails = (OAuthClient) clientDetailsService.loadClientByClientId(clientId);
return clientDetails.getAllowedOrigins();
}
});
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication instanceof OAuth2Authentication) {
if (!checkValidOrigin(request, (OAuth2Authentication) authentication)) {
response.sendError(403, "Request origin not valid");
return;
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Authentication null for origin: " + request.getHeader("Origin"));
}
}
filterChain.doFilter(request, response);
}
private boolean checkValidOrigin(HttpServletRequest request, OAuth2Authentication authAuth) {
if (logger.isTraceEnabled()) {
logger.trace(request.getRequestURI());
for (String headerName : Collections.list(request.getHeaderNames())) {
logger.trace(">> " + headerName + ": " + request.getHeader(headerName));
}
}
String reqOrigin = request.getHeader("Origin");
String reqReferrer = request.getHeader("Referer"); // GET requests don't carry Origin?
if (authAuth.getOAuth2Request() != null) {
boolean isGet = request.getMethod().equalsIgnoreCase("get");
String clientId = authAuth.getOAuth2Request().getClientId();
try {
Set<String> allowedOrigins = clientOriginsCache.get(clientId);
if (!allowedOrigins.isEmpty()) {
if (reqOrigin == null && reqReferrer == null) {
if (logger.isInfoEnabled()) {
logger.info("No origin/referrer header in request. Denying.");
}
return false;
}
for (String allowedOrigin : allowedOrigins) {
if (reqOrigin != null && reqOrigin.startsWith(allowedOrigin)) {
if (logger.isDebugEnabled()) {
logger.debug("Origin match: " + reqOrigin + " for " + allowedOrigin);
}
return true;
}
if ((isGet || reqOrigin == null) && reqReferrer != null && reqReferrer.startsWith(allowedOrigin)) {
if (logger.isDebugEnabled()) {
logger.debug("Referrer match: " + reqReferrer + " for " + allowedOrigin);
}
return true;
}
}
// No declared origins match
if (logger.isInfoEnabled()) {
logger.info("No origin/referrer match: " + reqOrigin + " or " + reqReferrer + " in " + allowedOrigins.toString());
}
return false;
}
} catch (ExecutionException e) {
logger.warn("Error loading client origins", e);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Allowing request with Origin: " + reqOrigin);
}
return true;
}
}
\ No newline at end of file
......@@ -207,8 +207,9 @@ public class CustomAclServiceImpl implements CustomAclService {
}
objectIdentity = aclObjectIdentityPersistence.save(objectIdentity);
clearAclCache();
}
clearAclCache();
}
@Override
......@@ -285,8 +286,9 @@ public class CustomAclServiceImpl implements CustomAclService {
aclEntryPersistence.delete(aclEntries);
}
aclObjectIdentityPersistence.delete(aclObjectIdentity);
clearAclCache();
}
clearAclCache();
}
/*
......