diff --git a/jpa/security/src/main/java/example/springdata/jpa/security/BusinessObject.java b/jpa/security/src/main/java/example/springdata/jpa/security/BusinessObject.java index ceccb8f7..8ea013c1 100644 --- a/jpa/security/src/main/java/example/springdata/jpa/security/BusinessObject.java +++ b/jpa/security/src/main/java/example/springdata/jpa/security/BusinessObject.java @@ -15,7 +15,12 @@ */ package example.springdata.jpa.security; -import javax.persistence.*; +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; /** * @author Thomas Darimont @@ -26,27 +31,76 @@ public class BusinessObject { @Id @GeneratedValue Long id; String data; + @ManyToOne User owner; - @ManyToOne - User owner; + String lastModifiedByUsername; + + Date lastModifiedDate; + + public BusinessObject() {} public BusinessObject(String data, User owner) { this.data = data; this.owner = owner; } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public String getLastModifiedByUsername() { + return lastModifiedByUsername; + } + + public void setLastModifiedByUsername(String lastModifiedByUsername) { + this.lastModifiedByUsername = lastModifiedByUsername; + } + + public Date getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Date lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; BusinessObject that = (BusinessObject) o; - if (data != null ? !data.equals(that.data) : that.data != null) return false; - if (id != null ? !id.equals(that.id) : that.id != null) return false; - if (owner != null ? !owner.equals(that.owner) : that.owner != null) return false; + if (data != null ? !data.equals(that.data) : that.data != null) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (owner != null ? !owner.equals(that.owner) : that.owner != null) + return false; return true; } diff --git a/jpa/security/src/main/java/example/springdata/jpa/security/SecureBusinessObjectRepository.java b/jpa/security/src/main/java/example/springdata/jpa/security/SecureBusinessObjectRepository.java index 4c923dc0..20080c91 100644 --- a/jpa/security/src/main/java/example/springdata/jpa/security/SecureBusinessObjectRepository.java +++ b/jpa/security/src/main/java/example/springdata/jpa/security/SecureBusinessObjectRepository.java @@ -17,6 +17,7 @@ package example.springdata.jpa.security; import java.util.List; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -51,4 +52,11 @@ public interface SecureBusinessObjectRepository extends Repository findBusinessObjectsForCurrentUserById(); + + /** + * Here we demonstrate the use of SecurityContext information in dynamic SPEL parameters in a JPAQL update statement. + */ + @Modifying + @Query("update BusinessObject b set b.data = upper(b.data), b.lastModifiedByUsername = :#{#security.principal.firstname}, b.lastModifiedDate = :#{new java.util.Date()}") + void modifiyDataWithRecordingSecurityContext(); } diff --git a/jpa/security/src/test/java/example/springdata/jpa/security/SecurityIntegrationTests.java b/jpa/security/src/test/java/example/springdata/jpa/security/SecurityIntegrationTests.java index 647d70f3..6b1f8c8a 100644 --- a/jpa/security/src/test/java/example/springdata/jpa/security/SecurityIntegrationTests.java +++ b/jpa/security/src/test/java/example/springdata/jpa/security/SecurityIntegrationTests.java @@ -21,6 +21,8 @@ import static org.junit.Assert.*; import java.util.List; +import javax.persistence.EntityManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,10 +48,15 @@ public class SecurityIntegrationTests { @Autowired UserRepository userRepository; @Autowired BusinessObjectRepository businessObjectRepository; @Autowired SecureBusinessObjectRepository secureBusinessObjectRepository; + @Autowired EntityManager em; User tom; User olli; User admin; + + UsernamePasswordAuthenticationToken olliAuth; + UsernamePasswordAuthenticationToken tomAuth; + UsernamePasswordAuthenticationToken adminAuth; BusinessObject object1; BusinessObject object2; @@ -65,12 +72,16 @@ public class SecurityIntegrationTests { object1 = businessObjectRepository.save(new BusinessObject("object1", olli)); object2 = businessObjectRepository.save(new BusinessObject("object2", olli)); object3 = businessObjectRepository.save(new BusinessObject("object3", tom)); + + olliAuth = new UsernamePasswordAuthenticationToken(olli, "x"); + tomAuth = new UsernamePasswordAuthenticationToken(tom, "x"); + adminAuth = new UsernamePasswordAuthenticationToken(admin, "x", singleton(new SimpleGrantedAuthority("ROLE_ADMIN"))); } @Test public void findBusinessObjectsForCurrentUserShouldReturnOnlyBusinessObjectsWhereCurrentUserIsOwner() { - SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(tom, "x")); + SecurityContextHolder.getContext().setAuthentication(tomAuth); List businessObjects = secureBusinessObjectRepository.findBusinessObjectsForCurrentUser(); @@ -88,8 +99,7 @@ public class SecurityIntegrationTests { @Test public void findBusinessObjectsForCurrentUserShouldReturnAllObjectsForAdmin() { - SecurityContextHolder.getContext().setAuthentication( - new UsernamePasswordAuthenticationToken(admin, "x", singleton(new SimpleGrantedAuthority("ROLE_ADMIN")))); + SecurityContextHolder.getContext().setAuthentication(adminAuth); List businessObjects = secureBusinessObjectRepository.findBusinessObjectsForCurrentUser(); @@ -100,14 +110,14 @@ public class SecurityIntegrationTests { @Test public void findBusinessObjectsForCurrentUserByIdShouldReturnOnlyBusinessObjectsWhereCurrentUserIsOwner() { - SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(tom, "x")); + SecurityContextHolder.getContext().setAuthentication(tomAuth); List businessObjects = secureBusinessObjectRepository.findBusinessObjectsForCurrentUserById(); assertThat(businessObjects, hasSize(1)); assertThat(businessObjects, contains(object3)); - SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(olli, "x")); + SecurityContextHolder.getContext().setAuthentication(olliAuth); businessObjects = secureBusinessObjectRepository.findBusinessObjectsForCurrentUserById(); @@ -118,13 +128,30 @@ public class SecurityIntegrationTests { @Test public void findBusinessObjectsForCurrentUserByIdShouldReturnAllObjectsForAdmin() { - UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(admin, "x", - singleton(new SimpleGrantedAuthority("ROLE_ADMIN"))); - SecurityContextHolder.getContext().setAuthentication(auth); + SecurityContextHolder.getContext().setAuthentication(adminAuth); List businessObjects = secureBusinessObjectRepository.findBusinessObjectsForCurrentUserById(); assertThat(businessObjects, hasSize(3)); assertThat(businessObjects, contains(object1, object2, object3)); } + + @Test + public void customUpdateStatementShouldAllowToUseSecurityContextInformationViaSpelParameters() { + + SecurityContextHolder.getContext().setAuthentication(adminAuth); + + //Detaching items to get them out of the query cache in order to see the updated values. + em.detach(object1); + em.detach(object2); + em.detach(object3); + + secureBusinessObjectRepository.modifiyDataWithRecordingSecurityContext(); + + for(BusinessObject bo : businessObjectRepository.findAll()) { + + assertThat(bo.getLastModifiedDate(), is(notNullValue())); + assertThat(bo.getLastModifiedByUsername(), is("admin")); + } + } }