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;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
......@@ -41,9 +42,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable;
import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.security.model.AclSid;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.ClientDetails;
......@@ -54,7 +55,8 @@ import org.springframework.security.oauth2.provider.ClientDetails;
*/
@Entity
@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. */
private static final long serialVersionUID = -4204753722663196007L;
......@@ -149,10 +151,21 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
@Lob
private String description;
public OAuthClient() {
setPrincipal(true);
}
@PrePersist
private void assignSid() {
flatten();
// Use clientId as SID name
setSid(clientId);
}
/**
* Flatten.
*/
@PrePersist
@PreUpdate
private void flatten() {
resource = resourceIds.stream().collect(Collectors.joining(";"));
......@@ -528,6 +541,11 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
return description;
}
@Override
public String getFullName() {
return this.clientId;
}
/**
* Returns null.
*
......@@ -545,19 +563,19 @@ public class OAuthClient extends AuditedVersionedModel implements ClientDetails,
this.autoApproveScopes.clear();
this.autoApproveScopes.addAll(source.autoApproveScopes);
this.grantTypes.clear();
this.grantTypes.addAll(source.grantTypes);
this.redirectUris.clear();
this.redirectUris.addAll(source.redirectUris);
this.resourceIds.clear();
this.resourceIds.addAll(source.resourceIds);
this.roles.clear();
this.roles.addAll(source.roles);
this.scopes.clear();
this.scopes.addAll(source.scopes);
......
......@@ -16,6 +16,7 @@
package org.genesys.blocks.security;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.model.BasicUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
......@@ -59,16 +60,16 @@ public class SecurityContextUtil {
}
/**
* Gets the current user.
* Gets the current principal (User or OAuthClient)
*
* @param <T> the generic type
* @return the current user
* @return the current security principal
*/
@SuppressWarnings("unchecked")
public static <T extends BasicUser<?>> T getCurrentUser() {
public static <T extends AclSid> T getCurrentUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if ((authentication != null) && (authentication.getPrincipal() instanceof BasicUser)) {
if ((authentication != null) && (authentication.getPrincipal() instanceof AclSid)) {
return (T) authentication.getPrincipal();
}
......
......@@ -17,7 +17,7 @@ package org.genesys.blocks.security;
import org.apache.commons.logging.Log;
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.stereotype.Component;
......@@ -36,13 +36,13 @@ public class SpringSecurityAuditorAware implements AuditorAware<Long> {
*/
@Override
public Long getCurrentAuditor() {
final BasicUser<?> user = SecurityContextUtil.getCurrentUser();
final AclSid sid = SecurityContextUtil.getCurrentUser();
if (user == null) {
SpringSecurityAuditorAware.LOG.trace("No User in security context, can't specify createdBy/lastUpdatedBy");
if (sid == null) {
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 @@
package org.genesys.blocks.security.component;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -45,7 +46,7 @@ public class AclAssignerAspect {
/** The acl service. */
@Autowired
private CustomAclService aclService;
/**
* Instantiates a new acl assigner aspect.
*/
......@@ -98,13 +99,19 @@ public class AclAssignerAspect {
*
* @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) {
final Object arg0 = joinPoint.getArgs()[0];
try {
if (arg0 instanceof Long) {
// 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) {
final AclAwareModel aclModel = (AclAwareModel) arg0;
maybeRemovePermissions(aclModel);
......
......@@ -15,7 +15,6 @@
*/
package org.genesys.blocks.security.model;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
......@@ -38,12 +37,12 @@ public class AclEntry extends BasicModel {
private static final long serialVersionUID = -1047000445685485825L;
/** 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)
private AclObjectIdentity aclObjectIdentity;
/** The acl sid. */
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@ManyToOne(fetch = FetchType.LAZY, cascade = {}, optional = false)
@JoinColumn(name = "sid", nullable = false)
private AclSid aclSid;
......
......@@ -55,7 +55,7 @@ public class AclObjectIdentity extends BasicModel {
private AclObjectIdentity parentObject;
/** The owner sid. */
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@ManyToOne(fetch = FetchType.EAGER, cascade = {})
@JoinColumn(name = "owner_sid", nullable = true)
private AclSid ownerSid;
......
......@@ -19,14 +19,20 @@ import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
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"
......@@ -34,7 +40,16 @@ import org.genesys.blocks.model.BasicModel;
*/
@Entity
@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. */
private static final long serialVersionUID = -8665345718313672678L;
......@@ -49,12 +64,14 @@ public class AclSid extends BasicModel {
/** The object identities. */
@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;
/** The acl entries. */
@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;
/**
......@@ -128,4 +145,14 @@ public class AclSid extends BasicModel {
public void setAclEntries(final List<AclEntry> 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;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
......@@ -39,7 +40,6 @@ import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.JsonViews;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
......@@ -48,10 +48,15 @@ import org.springframework.security.core.userdetails.UserDetails;
/**
* The Class BasicUser.
*
* When you extend this class, make sure you use:
*
* <pre>@DiscriminatorValue(value = "1")</pre>
*
* @param <R> the generic type
*/
@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. */
private static final long serialVersionUID = -5318892732608111516L;
......@@ -124,6 +129,10 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi
@Column(length = 20)
private AccountType accountType = AccountType.LOCAL;
public BasicUser() {
setPrincipal(true);
}
/**
* Ensure UUID.
*/
......@@ -132,6 +141,8 @@ public abstract class BasicUser<R extends GrantedAuthority> extends AuditedVersi
if (this.uuid == null) {
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
*
* @return the full name
*/
@Override
public String getFullName() {
return fullName;
}
......
......@@ -21,6 +21,7 @@ import org.genesys.blocks.security.model.AclEntry;
import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.model.AclSid;
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.repository.query.Param;
......@@ -39,48 +40,27 @@ public interface AclEntryPersistence extends JpaRepository<AclEntry, Long> {
List<AclEntry> findByObjectIdentity(@Param("aclObjectIdentity") AclObjectIdentity aclObjectIdentity);
/**
* Find by object identity and object class id and sid.
*
* @param objectIdentityId - id of domain object
* @param aclClass - domain class
* @param sid - user's email
* @return - returns lists of user's permissions
* List by SID and Object Identity
*
* @param sid
* @param objectIdentity
* @return
*/
@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")
List<AclEntry> findByObjectIdentityAndObjectClassIdAndSid(long objectIdentityId, String aclClass, String sid);
@Query("select ae from AclEntry ae where ae.aclSid = :aclSid and ae.aclObjectIdentity = :aclObjectIdentity")
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 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 sid - SID
* @param aclClass - class of domain object
* @param mask - mask for permissions
* @param mask - permissions
* @return - returns lists of user's permissions on domain class
*/
@Query("select aoi.objectIdIdentity from AclEntry ae join ae.aclObjectIdentity aoi join aoi.aclClass ac join ae.aclSid sid where sid.sid=?1 and ac.aclClass=?2 and ae.mask=?3")
List<Long> findObjectIdentitiesBySidAndAclClassAndMask(String sid, String aclClass, long mask);
@Query("select ae.aclObjectIdentity.objectIdIdentity from AclEntry ae where ae.aclSid=?1 and ae.aclObjectIdentity.aclClass.aclClass=?2 and ae.mask=?3 and ae.granting=true")
List<Long> findObjectIdentitiesForSidAndAclClassAndMask(AclSid sid, String aclClass, long mask);
/**
* Calculates max. ace_order for acl_object_identity to avoid DuplicateIndex
......@@ -95,11 +75,19 @@ public interface AclEntryPersistence extends JpaRepository<AclEntry, Long> {
/**
* Gets the sids.
*
* @param objectIdIdentity the object id identity
* @param aclClass the acl class
* @param objectIdentity the object identity
* @return the sids
*/
@Query("select distinct ae.aclSid from AclEntry ae join ae.aclObjectIdentity aoi join aoi.aclClass ac where aoi.objectIdIdentity = :objectIdIdentity and ac.aclClass = :aclClass")
List<AclSid> getSids(@Param("objectIdIdentity") long objectIdIdentity, @Param("aclClass") String aclClass);
@Query("select distinct ae.aclSid from AclEntry ae where ae.aclObjectIdentity = :objectIdentity")
List<AclSid> getSids(@Param("objectIdentity") AclObjectIdentity objectIdentity);
/**
* Delete AclEntries for a SID
* @param sid the SID
*/
@Modifying
@Query("delete from AclEntry ae where ae.aclSid = :sid")
<T extends AclSid> int deleteForSid(@Param("sid") T sid);
}
......@@ -33,5 +33,5 @@ public interface AclObjectIdentityPersistence extends JpaRepository<AclObjectIde
* @return the acl object identity
*/
@Query("select aoi from AclObjectIdentity aoi where aoi.objectIdIdentity = :objectIdIdentity and aoi.aclClass.aclClass = :aclClass")
AclObjectIdentity findByObjectIdIdentityAndClassName(@Param("objectIdIdentity") long objectIdentityId, @Param("aclClass") String aclClass);
AclObjectIdentity findByIdAndClassname(@Param("objectIdIdentity") long objectIdentityId, @Param("aclClass") String aclClass);
}
......@@ -44,4 +44,5 @@ public interface AclSidPersistence extends JpaRepository<AclSid, Long> {
*/
@Query("select distinct sid from AclSid sid where sid.id in :ids")
List<AclSid> listById(@Param("ids") Iterable<Long> ids);
}
......@@ -23,13 +23,28 @@ import org.genesys.blocks.security.model.AclEntry;
import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.model.AclSid;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.userdetails.UserDetails;
/**
* The Interface CustomAclService.
*/
public interface CustomAclService {
/**
* Get SID by ID
*
* @param id -- ID of an AclSid entity
* @return
*/
AclSid getSid(Long id);
/**
* Gets the sid of the specified authority
*
* @param authority the authority
* @return the authority sid
*/
AclSid getAuthoritySid(String authority);
/**
* Adds the creator permissions.
*
......@@ -38,28 +53,27 @@ public interface CustomAclService {
void addCreatorPermissions(AclAwareModel target);
/**
* Removes the permissions.
* Removes the permissions on ACL model.
*
* @param target the target
*/
void removePermissions(AclAwareModel target);
/**
* Gets the object identity.
* Removes the all permissions of SID.
*
* @param clazz the clazz
* @param id the id
* @return the object identity
* @param sid the sid
*/
AclObjectIdentity getObjectIdentity(String clazz, long id);
void removePermissionsFor(AclSid sid);
/**
* Gets the object identity.
*
* @param clazz the clazz
* @param id the id
* @return the object identity
*/
AclObjectIdentity getObjectIdentity(long id);
AclObjectIdentity getObjectIdentity(String clazz, long id);
/**
* Gets the object identity.
......@@ -103,13 +117,15 @@ public interface CustomAclService {
List<AclEntry> getAclEntries(AclObjectIdentity objectIdentity);
/**
* Update permission.
* Update permissions.
*
* @param entity the entity
* @param sid the sid
* @param permissionMap the permission map
*/
void updatePermission(AclObjectIdentity entity, String sid, Map<Integer, Boolean> permissionMap);
void updatePermissions(AclAwareModel entity, AclSid sid, Map<Integer, Boolean> permissionMap);
void updatePermissions(AclObjectIdentity objectIdentity, AclSid sid, Map<Integer, Boolean> permissionMap);
/**
* Gets the acl entries.
......@@ -137,23 +153,25 @@ public interface CustomAclService {
List<AclSid> getSids(AclAwareModel entity);
/**
* Gets the all sids.
* Adds the permissions.
*
* @return the all sids
* @param objectIdIdentity the object id identity
* @param className the class name
* @param sid TODO
* @param permissions the permissions
* @return true, if successful
*/
List<AclSid> getAllSids();
boolean addPermissions(long objectIdIdentity, String className, AclSid sid, Map<Integer, Boolean> permissions);
/**
* Adds the permissions.
*
* @param objectIdIdentity the object id identity
* @param className the class name
* @param uuid the uuid
* @param principal the principal
* @param permissions the permissions
* @param entity the entity
* @param sid the sid
* @param permissionMap the permission map
* @return true, if successful
*/
boolean addPermissions(long objectIdIdentity, String className, String uuid, boolean principal, Map<Integer, Boolean> permissions);
boolean addPermissions(AclAwareModel entity, AclSid sid, Map<Integer, Boolean> permissionMap);
/**
* Ensure object identity.
......@@ -165,22 +183,20 @@ public interface CustomAclService {
AclObjectIdentity ensureObjectIdentity(String className, long objectIdIdentity);
/**
* Permissions by sid.
* List IDs of the specified class for the SID with specified permissions.
*
* @param className the class name
* @param id the id
* @param clazz the clazz
* @param sid the sid
* @param permission the permission
* @return the list