...
 
Commits (53)
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\-/
# Changelog
## 1.5 Release
January 24, 2019.
- 394bf92 Using a `ThreadLocal` stack for audit logs entries for individual (sub) transactions
- 25f2518 added listClientDetails by Page, fixed Json for `OAuthClient#scopes`
- 431f9c7 `SpringSecurityAuditorAware`: Reduce logging level when security principal not available
- 4d0de99 createdDate, lastModifiedDate marked with Public JsonView
- ab895bc Fix: Auth failure listener -- apparently it can be a String
- c074fd8 Fix: Auth failure listener
- 967826d ES: Set createdBy, lastModifiedBy to not_analyzed string type
- 222e045 Do not deserialize Audited properties createdBy, lastModifiedBy from JSON
- 7ef27f2 Fix: `AclObjectIdentityExt` cannot serialize `OAuthClient#roles`
- ce45ba7 `AclObjectIdentityExt` provides extended information on Object Identity
- f70eb99 ACL removePermission for SID
- c161f60 Introduced ClassAclOid as AclAwareModel
- e136701 Serialize Audited `lastModifiedBy` and `createdBy` using JsonSidConverter
- a3c4dfd SpringSecurityAuditorAware fetches AclSid#id directly with getSidId()
- bd408e8 added clearing Acl cache after updating permissions
- f5163f3 Extend ACL service
- a05c655 Fix: `Permissions.public = false` when EVERYONE's permissions cannot be read
- b89154b Fix: SecurityContextUtil should re-check context for `permissionEvaluator`
- f1e08ed mvn surefire plugin problem
- a16fb36 Include VersionedModel#version in default serialization
- 40f0a90 ACL: Added support for #aclParentObjectIdentity()
- 6f25333 Fix: `CurrentPermissionsWriter` for ADMIN
- cd95da7 Added `Permissions#isPublic` = EVERYONE can read the object
- c091982 Update core filters
- de9e43d Better logging for ACL cleanUp
- 6ac18d8 Stddev function for HSQL
- 9b43f46 hasRole and hasPermission added to SecurityContextUtil
- b38eb76 Updated UuidModel class
- 2791bd6 Updated `AclSid` class: serialize the identity of the SID
- 9fa5496 FIX: Properly handle Hibernate proxy classes
- 7099be8 FIX: only set ownerSid if it is persisted
- 9f21c3d FIX: creatorPermission ensures objectIdentity entry even if owner is null
## 1.4 Release
September 19, 2018.
......
......@@ -21,7 +21,7 @@ The **core** provides common data models: `BasicModel`, `VersionedModel`, `Audit
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-core</artifactId>
<version>1.4</version>
<version>1.5</version>
</dependency>
```
......@@ -33,7 +33,7 @@ The **security** module provides generic `User` declaration, ACL model and servi
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-security</artifactId>
<version>1.4</version>
<version>1.5</version>
</dependency>
```
......@@ -45,6 +45,6 @@ The **auditlog** provides the model and services to capture changes to JPA entit
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-auditlog</artifactId>
<version>1.4</version>
<version>1.5</version>
</dependency>
```
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-parent</artifactId>
<version>1.5-SNAPSHOT</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-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-core</artifactId>
<version>1.5-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
......
......@@ -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-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>application-blocks-core</artifactId>
......
......@@ -20,12 +20,19 @@ import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.util.JsonSidConverter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
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.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* The Class AuditedVersionedModel.
......@@ -40,10 +47,13 @@ public abstract class AuditedVersionedModel extends VersionedModel {
@JsonView(JsonViews.Protected.class)
@CreatedBy
@Column(updatable = false)
@JsonSerialize(converter = JsonSidConverter.class)
@Field(type = FieldType.String, index = FieldIndex.not_analyzed)
@JsonProperty(access = Access.READ_ONLY)
private Long createdBy;
/** The created date. */
@JsonView(JsonViews.Protected.class)
@JsonView(JsonViews.Public.class)
@CreatedDate
@Column(name = "createdDate", updatable = false)
private Date createdDate;
......@@ -51,10 +61,13 @@ public abstract class AuditedVersionedModel extends VersionedModel {
/** The last modified by. */
@JsonView(JsonViews.Protected.class)
@LastModifiedBy
@JsonSerialize(converter = JsonSidConverter.class)
@Field(type = FieldType.String, index = FieldIndex.not_analyzed)
@JsonProperty(access = Access.READ_ONLY)
private Long lastModifiedBy;
/** The last modified date. */
@JsonView(JsonViews.Protected.class)
@JsonView(JsonViews.Public.class)
@LastModifiedDate
@Column(name = "lastModifiedDate")
private Date lastModifiedDate;
......
......@@ -20,12 +20,19 @@ import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.util.JsonSidConverter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
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.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* The Class AuditedVersionedModelWithoutId.
......@@ -40,10 +47,13 @@ public abstract class AuditedVersionedModelWithoutId extends VersionedModelWitho
@JsonView(JsonViews.Protected.class)
@CreatedBy
@Column(updatable = false)
@JsonSerialize(converter = JsonSidConverter.class)
@Field(type = FieldType.String, index = FieldIndex.not_analyzed)
@JsonProperty(access = Access.READ_ONLY)
private Long createdBy;
/** The created date. */
@JsonView(JsonViews.Protected.class)
@JsonView(JsonViews.Public.class)
@CreatedDate
@Column(name = "createdDate", updatable = false)
private Date createdDate;
......@@ -51,10 +61,13 @@ public abstract class AuditedVersionedModelWithoutId extends VersionedModelWitho
/** The last modified by. */
@JsonView(JsonViews.Protected.class)
@LastModifiedBy
@JsonSerialize(converter = JsonSidConverter.class)
@Field(type = FieldType.String, index = FieldIndex.not_analyzed)
@JsonProperty(access = Access.READ_ONLY)
private Long lastModifiedBy;
/** The last modified date. */
@JsonView(JsonViews.Protected.class)
@JsonView(JsonViews.Public.class)
@LastModifiedDate
@Column(name = "lastModifiedDate")
private Date lastModifiedDate;
......
......@@ -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;
}
}
/*
* 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.util;
import com.fasterxml.jackson.databind.util.StdConverter;
/**
* The JsonSidConverter converts SID IDs to SID names.
*/
public class JsonSidConverter extends StdConverter<Long, String> {
private static SidProvider SID_PROVIDER;
public static interface SidProvider {
String getSidName(long id);
}
/**
* Sets the sid provider.
*
* @param sidProvider the new sid provider
*/
public static void setSidProvider(SidProvider sidProvider) {
SID_PROVIDER = sidProvider;
}
/**
* Convert SID ID to SID name using SID_PROVIDER (when available)
*/
@Override
public String convert(Long value) {
if (value == null) {
return null;
} else {
if (SID_PROVIDER == null) {
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-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
</parent>
<artifactId>application-blocks-parent</artifactId>
<packaging>pom</packaging>
......@@ -218,6 +218,19 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>2.1.15.RELEASE</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
......
......@@ -18,7 +18,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks</artifactId>
<version>1.5-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Application Building Blocks</name>
<description>This is the shared project pom.</description>
......
......@@ -19,7 +19,7 @@
<parent>
<groupId>org.genesys-pgr</groupId>
<artifactId>application-blocks-parent</artifactId>
<version>1.5-SNAPSHOT</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-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
</dependency>
<dependency>
......
......@@ -29,6 +29,7 @@ import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.PostLoad;
......@@ -37,6 +38,7 @@ import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.Copyable;
import org.genesys.blocks.model.JsonViews;
......@@ -127,7 +129,7 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
/** The roles. */
@JsonView(JsonViews.Protected.class)
@ElementCollection
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "oauthclientrole", joinColumns = @JoinColumn(name = "clientId"))
@Column(name = "oauthclientrole")
......@@ -152,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.
*/
......@@ -177,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(";"));
}
/**
......@@ -199,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));
}
}
/*
......@@ -415,6 +431,7 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
* (non-Javadoc)
* @see org.springframework.security.oauth2.provider.ClientDetails#getScope()
*/
@JsonProperty("clientScopes")
@Override
public Set<String> getScope() {
return scopes;
......@@ -425,6 +442,7 @@ public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuth
*
* @param scopes the new scopes
*/
@JsonProperty("clientScopes")
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
......@@ -641,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;
}
}
......@@ -18,6 +18,8 @@ package org.genesys.blocks.oauth.service;
import java.util.List;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.oauth2.provider.ClientDetailsService;
/**
......@@ -32,6 +34,13 @@ public interface OAuthClientDetailsService extends ClientDetailsService {
*/
List<OAuthClient> listClientDetails();
/**
* List client details.
*
* @return page with Client details
*/
Page<OAuthClient> listClientDetails(Pageable pageable);
/**
* Gets the client.
*
......
......@@ -40,7 +40,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
......@@ -558,6 +560,11 @@ public class OAuthServiceImpl implements OAuthClientDetailsService, OAuthTokenSt
return oauthClientRepository.findAll(new Sort("clientId"));
}
@Override
public Page<OAuthClient> listClientDetails(Pageable pageable) {
return oauthClientRepository.findAll(pageable);
}
/*
* (non-Javadoc)
* @see org.genesys.blocks.oauth.service.OAuthTokenStoreService#
......@@ -611,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;
......@@ -674,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);
......
......@@ -56,18 +56,21 @@ public class SecurityContextUtil {
* @return true if obtained, false if not obtained.
*/
private static synchronized boolean updatePermissionEvaluator() {
return (permissionEvaluator = updateBean(PermissionEvaluator.class)) != null;
}
static final <T> T updateBean(Class<T> clazz) {
ApplicationContext context = CurrentApplicationContext.getContext();
if (context != null) {
try {
permissionEvaluator = context.getBean(PermissionEvaluator.class);
return true;
return context.getBean(clazz);
} catch (BeansException e) {
LOG.warn("Could not find PermissionEvaluator instance in your context");
LOG.warn("Could not find {} instance in your context: {}", clazz, e.getMessage());