Commit f575f633 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '22-user-and-oauth-as-principals' into 'master'

Resolve "User and OAuth as principals"

Closes #22

See merge request genesys-pgr/application-blocks!22
parents 54afbc25 f1f0894a
...@@ -25,6 +25,7 @@ import java.util.stream.Collectors; ...@@ -25,6 +25,7 @@ import java.util.stream.Collectors;
import javax.persistence.CollectionTable; import javax.persistence.CollectionTable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
...@@ -41,9 +42,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore; ...@@ -41,9 +42,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable; import org.genesys.blocks.model.Copyable;
import org.genesys.blocks.model.JsonViews; import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.security.model.AclSid;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
...@@ -54,7 +55,8 @@ import org.springframework.security.oauth2.provider.ClientDetails; ...@@ -54,7 +55,8 @@ import org.springframework.security.oauth2.provider.ClientDetails;
*/ */
@Entity @Entity
@Table(name = "oauthclient") @Table(name = "oauthclient")
public class OAuthClient extends AuditedVersionedModel implements ClientDetails, Copyable<OAuthClient> { @DiscriminatorValue(value = "2")
public class OAuthClient extends AclSid implements ClientDetails, Copyable<OAuthClient> {
/** The Constant serialVersionUID. */ /** The Constant serialVersionUID. */
private static final long serialVersionUID = -4204753722663196007L; private static final long serialVersionUID = -4204753722663196007L;
...@@ -149,10 +151,21 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails, ...@@ -149,10 +151,21 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
@Lob @Lob
private String description; private String description;
public OAuthClient() {
setPrincipal(true);
}
@PrePersist
private void assignSid() {
flatten();
// Use clientId as SID name
setSid(clientId);
}
/** /**
* Flatten. * Flatten.
*/ */
@PrePersist
@PreUpdate @PreUpdate
private void flatten() { private void flatten() {
resource = resourceIds.stream().collect(Collectors.joining(";")); resource = resourceIds.stream().collect(Collectors.joining(";"));
...@@ -528,6 +541,11 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails, ...@@ -528,6 +541,11 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
return description; return description;
} }
@Override
public String getFullName() {
return this.clientId;
}
/** /**
* Returns null. * Returns null.
* *
...@@ -545,19 +563,19 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails, ...@@ -545,19 +563,19 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
this.autoApproveScopes.clear(); this.autoApproveScopes.clear();
this.autoApproveScopes.addAll(source.autoApproveScopes); this.autoApproveScopes.addAll(source.autoApproveScopes);
this.grantTypes.clear(); this.grantTypes.clear();
this.grantTypes.addAll(source.grantTypes); this.grantTypes.addAll(source.grantTypes);
this.redirectUris.clear(); this.redirectUris.clear();
this.redirectUris.addAll(source.redirectUris); this.redirectUris.addAll(source.redirectUris);
this.resourceIds.clear(); this.resourceIds.clear();
this.resourceIds.addAll(source.resourceIds); this.resourceIds.addAll(source.resourceIds);
this.roles.clear(); this.roles.clear();
this.roles.addAll(source.roles); this.roles.addAll(source.roles);
this.scopes.clear(); this.scopes.clear();
this.scopes.addAll(source.scopes); this.scopes.addAll(source.scopes);
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.genesys.blocks.security; package org.genesys.blocks.security;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.model.BasicUser; import org.genesys.blocks.security.model.BasicUser;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
...@@ -59,16 +60,16 @@ public class SecurityContextUtil { ...@@ -59,16 +60,16 @@ public class SecurityContextUtil {
} }
/** /**
* Gets the current user. * Gets the current principal (User or OAuthClient)
* *
* @param <T> the generic type * @param <T> the generic type
* @return the current user * @return the current security principal
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends BasicUser<?>> T getCurrentUser() { public static <T extends AclSid> T getCurrentUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if ((authentication != null) && (authentication.getPrincipal() instanceof BasicUser)) { if ((authentication != null) && (authentication.getPrincipal() instanceof AclSid)) {
return (T) authentication.getPrincipal(); return (T) authentication.getPrincipal();
} }
......
...@@ -17,7 +17,7 @@ package org.genesys.blocks.security; ...@@ -17,7 +17,7 @@ package org.genesys.blocks.security;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.genesys.blocks.security.model.BasicUser; import org.genesys.blocks.security.model.AclSid;
import org.springframework.data.domain.AuditorAware; import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -36,13 +36,13 @@ public class SpringSecurityAuditorAware implements AuditorAware<Long> { ...@@ -36,13 +36,13 @@ public class SpringSecurityAuditorAware implements AuditorAware<Long> {
*/ */
@Override @Override
public Long getCurrentAuditor() { public Long getCurrentAuditor() {
final BasicUser<?> user = SecurityContextUtil.getCurrentUser(); final AclSid sid = SecurityContextUtil.getCurrentUser();
if (user == null) { if (sid == null) {
SpringSecurityAuditorAware.LOG.trace("No User in security context, can't specify createdBy/lastUpdatedBy"); SpringSecurityAuditorAware.LOG.trace("No AclSid in security context, can't specify createdBy/lastUpdatedBy");
} }
return user == null ? null : user.getId(); return sid == null ? null : sid.getId();
} }
} }
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
package org.genesys.blocks.security.component; package org.genesys.blocks.security.component;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.genesys.blocks.security.model.AclAwareModel; import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService; import org.genesys.blocks.security.service.CustomAclService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -45,7 +46,7 @@ public class AclAssignerAspect { ...@@ -45,7 +46,7 @@ public class AclAssignerAspect {
/** The acl service. */ /** The acl service. */
@Autowired @Autowired
private CustomAclService aclService; private CustomAclService aclService;
/** /**
* Instantiates a new acl assigner aspect. * Instantiates a new acl assigner aspect.
*/ */
...@@ -98,13 +99,19 @@ public class AclAssignerAspect { ...@@ -98,13 +99,19 @@ public class AclAssignerAspect {
* *
* @param joinPoint the join point * @param joinPoint the join point
*/ */
@After("execution(* org.springframework.data.repository.*.delete(..)) || execution(* org.springframework.data.jpa.repository.*.deleteInBatch(..))") @Before("execution(* org.springframework.data.repository.*.delete(..)) || execution(* org.springframework.data.jpa.repository.*.deleteInBatch(..))")
public void afterDeleteAclObject(final JoinPoint joinPoint) { public void afterDeleteAclObject(final JoinPoint joinPoint) {
final Object arg0 = joinPoint.getArgs()[0]; final Object arg0 = joinPoint.getArgs()[0];
try { try {
if (arg0 instanceof Long) { if (arg0 instanceof Long) {
// NOOP // NOOP
} else if (arg0 instanceof AclSid) {
final AclSid aclSid = (AclSid) arg0;
// Remove permissions owned by SID
aclService.removePermissionsFor(aclSid);
// Remove permissions on SID
maybeRemovePermissions(aclSid);
} else if (arg0 instanceof AclAwareModel) { } else if (arg0 instanceof AclAwareModel) {
final AclAwareModel aclModel = (AclAwareModel) arg0; final AclAwareModel aclModel = (AclAwareModel) arg0;
maybeRemovePermissions(aclModel); maybeRemovePermissions(aclModel);
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package org.genesys.blocks.security.model; package org.genesys.blocks.security.model;
import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
...@@ -38,12 +37,12 @@ public class AclEntry extends BasicModel { ...@@ -38,12 +37,12 @@ public class AclEntry extends BasicModel {
private static final long serialVersionUID = -1047000445685485825L; private static final long serialVersionUID = -1047000445685485825L;
/** The acl object identity. */ /** The acl object identity. */
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @ManyToOne(fetch = FetchType.LAZY, cascade = {}, optional = false)
@JoinColumn(name = "acl_object_identity", nullable = false) @JoinColumn(name = "acl_object_identity", nullable = false)
private AclObjectIdentity aclObjectIdentity; private AclObjectIdentity aclObjectIdentity;
/** The acl sid. */ /** The acl sid. */
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @ManyToOne(fetch = FetchType.LAZY, cascade = {}, optional = false)
@JoinColumn(name = "sid", nullable = false) @JoinColumn(name = "sid", nullable = false)
private AclSid aclSid; private AclSid aclSid;
......
...@@ -55,7 +55,7 @@ public class AclObjectIdentity extends BasicModel { ...@@ -55,7 +55,7 @@ public class AclObjectIdentity extends BasicModel {
private AclObjectIdentity parentObject; private AclObjectIdentity parentObject;
/** The owner sid. */ /** The owner sid. */
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @ManyToOne(fetch = FetchType.EAGER, cascade = {})
@JoinColumn(name = "owner_sid", nullable = true) @JoinColumn(name = "owner_sid", nullable = true)
private AclSid ownerSid; private AclSid ownerSid;
......
...@@ -19,14 +19,20 @@ import java.util.List; ...@@ -19,14 +19,20 @@ import java.util.List;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import org.genesys.blocks.model.BasicModel; import org.genesys.blocks.model.AuditedVersionedModel;
import org.hibernate.annotations.DiscriminatorOptions;
/** /**
* ACL SID uniquely identifies any principal or authority in the system ("SID" * ACL SID uniquely identifies any principal or authority in the system ("SID"
...@@ -34,7 +40,16 @@ import org.genesys.blocks.model.BasicModel; ...@@ -34,7 +40,16 @@ import org.genesys.blocks.model.BasicModel;
*/ */
@Entity @Entity
@Table(name = "acl_sid") @Table(name = "acl_sid")
public class AclSid extends BasicModel {
/// User and OAuthClient are both SID entities and we need a way to pull them
/// together. Using AclSid seems like the best approach.
/// JOINED inheritance must be used because we need to be able to query acl_sid
/// table with JDBC.
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue(value = "0")
@DiscriminatorOptions(force = false)
public class AclSid extends AuditedVersionedModel {
/** The Constant serialVersionUID. */ /** The Constant serialVersionUID. */
private static final long serialVersionUID = -8665345718313672678L; private static final long serialVersionUID = -8665345718313672678L;
...@@ -49,12 +64,14 @@ public class AclSid extends BasicModel { ...@@ -49,12 +64,14 @@ public class AclSid extends BasicModel {
/** The object identities. */ /** The object identities. */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "ownerSid", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true) // no cascade delete, those object identities must stay because other ACL
// entries rely on them
@OneToMany(mappedBy = "ownerSid", fetch = FetchType.LAZY, cascade = {})
private List<AclObjectIdentity> objectIdentities; private List<AclObjectIdentity> objectIdentities;
/** The acl entries. */ /** The acl entries. */
@JsonIgnore @JsonIgnore
@OneToMany(mappedBy = "aclSid", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true) @OneToMany(mappedBy = "aclSid", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<AclEntry> aclEntries; private List<AclEntry> aclEntries;
/** /**
...@@ -128,4 +145,14 @@ public class AclSid extends BasicModel { ...@@ -128,4 +145,14 @@ public class AclSid extends BasicModel {
public void setAclEntries(final List<AclEntry> aclEntries) { public void setAclEntries(final List<AclEntry> aclEntries) {
this.aclEntries = aclEntries; this.aclEntries = aclEntries;
} }
/**
* Subclasses should override this method and return a sensible display name for
* the SID
*
* @return SID full name
*/
public String getFullName() {
return this.sid;
}
} }
...@@ -25,6 +25,7 @@ import java.util.stream.Collectors; ...@@ -25,6 +25,7 @@ import java.util.stream.Collectors;
import javax.persistence.CollectionTable; import javax.persistence.CollectionTable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
...@@ -39,7 +40,6 @@ import javax.persistence.Transient; ...@@ -39,7 +40,6 @@ import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.JsonViews; import org.genesys.blocks.model.JsonViews;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
...@@ -48,10 +48,15 @@ import org.springframework.security.core.userdetails.UserDetails; ...@@ -48,10 +48,15 @@ import org.springframework.security.core.userdetails.UserDetails;
/** /**
* The Class BasicUser. * The Class BasicUser.
* *
* When you extend this class, make sure you use:
*
* <pre>@DiscriminatorValue(value = "1")</pre>
*
* @param <R> the generic type * @param <R> the generic type
*/ */
@MappedSuperclass @MappedSuperclass
public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersionedModel implements UserDetails { @DiscriminatorValue(value = "1")
public abstract class BasicUser<R extends GrantedAuthority> extends AclSid implements UserDetails {
/** The Constant serialVersionUID. */ /** The Constant serialVersionUID. */
private static final long serialVersionUID = -5318892732608111516L; private static final long serialVersionUID = -5318892732608111516L;
...@@ -124,6 +129,10 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi ...@@ -124,6 +129,10 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi
@Column(length = 20) @Column(length = 20)
private AccountType accountType = AccountType.LOCAL; private AccountType accountType = AccountType.LOCAL;
public BasicUser() {
setPrincipal(true);
}
/** /**
* Ensure UUID. * Ensure UUID.
*/ */
...@@ -132,6 +141,8 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi ...@@ -132,6 +141,8 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi
if (this.uuid == null) { if (this.uuid == null) {
this.uuid = UUID.randomUUID().toString(); this.uuid = UUID.randomUUID().toString();
} }
// Use #uuid as SID name
setSid(this.uuid.toString());
} }
/** /**
...@@ -175,6 +186,7 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi ...@@ -175,6 +186,7 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi
* *
* @return the full name * @return the full name
*/ */
@Override
public String getFullName() { public String getFullName() {
return fullName; return fullName;
} }
......
...@@ -21,6 +21,7 @@ import org.genesys.blocks.security.model.AclEntry; ...@@ -21,6 +21,7 @@ import org.genesys.blocks.security.model.AclEntry;
import org.genesys.blocks.security.model.AclObjectIdentity; import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.model.AclSid; import org.genesys.blocks.security.model.AclSid;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
...@@ -39,48 +40,27 @@ public interface AclEntryPersistence extends JpaRepository<AclEntry, Long> { ...@@ -39,48 +40,27 @@ public interface AclEntryPersistence extends JpaRepository<AclEntry, Long> {
List<AclEntry> findByObjectIdentity(@Param("aclObjectIdentity") AclObjectIdentity aclObjectIdentity); List<AclEntry> findByObjectIdentity(@Param("aclObjectIdentity") AclObjectIdentity aclObjectIdentity);
/** /**
* Find by object identity and object class id and sid. * List by SID and Object Identity
* *
* @param objectIdentityId - id of domain object * @param sid
* @param aclClass - domain class * @param objectIdentity
* @param sid - user's email * @return
* @return - returns lists of user's permissions
*/ */
@Query("select ae from AclEntry ae join ae.aclObjectIdentity aoi join aoi.aclClass ac join ae.aclSid sid where aoi.objectIdIdentity=?1 and ac.aclClass=?2 and sid.sid=?3") @Query("select ae from AclEntry ae where ae.aclSid = :aclSid and ae.aclObjectIdentity = :aclObjectIdentity")
List<AclEntry> findByObjectIdentityAndObjectClassIdAndSid(long objectIdentityId, String aclClass, String sid); List<AclEntry> findBySidAndObjectIdentity(@Param("aclSid") AclSid sid, @Param("aclObjectIdentity") AclObjectIdentity objectIdentity);
/** /**
* Find by sid and acl class.
*
* @param sid - user's email
* @param aclClass - class of domain object
* @return - returns lists of user's permissions on domain class
*/
@Query("select ae from AclEntry ae join ae.aclObjectIdentity aoi join aoi.aclClass ac join ae.aclSid sid where sid.sid=?1 and ac.aclClass=?2")
List<AclEntry> findBySidAndAclClass(String sid, String aclClass);
/** /**
* Find by sid and object identity and mask. * Find IDs of object of aclClass for sid with specified permissions.
* *
* @param sid - user's email * @param sid - SID
* @param objectIdIdentity - id of domain object
* @param mask - mask for permissions
* @param className - class name
* @return - returns lists of user's permissions on domain class
*/
@Query("select count(ae) from AclEntry ae join ae.aclObjectIdentity aoi join aoi.aclClass ac join ae.aclSid sid where sid.sid=?1 and aoi.objectIdIdentity=?2 and ae.mask=?3 and ac.aclClass=?4")
Long findBySidAndObjectIdentityAndMask(String sid, long objectIdIdentity, long mask, String className);
/**
* Find object identities by sid and acl class and mask.
*
* @param sid - user's email
* @param aclClass - class of domain object * @param aclClass - class of domain object
* @param mask - mask for permissions * @param mask - permissions
* @return - returns lists of user's permissions on domain class