diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownable.java new file mode 100644 index 000000000..c184b9679 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownable.java @@ -0,0 +1,13 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +public interface Ownable { + /** + * @return representation of the id of the object. This is likely (but not limited to) the resource id of the object + */ + public String getObjectId(); + + /** + * @return the OwnableType that describes the Ownable object + */ + public OwnableType getOwnableType(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java new file mode 100644 index 000000000..0cf82714c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +public enum OwnableType { + USER, ENTITY_DESCRIPTOR, METADATA_PROVIDER +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Owner.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Owner.java new file mode 100644 index 000000000..ee7224335 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Owner.java @@ -0,0 +1,13 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +public interface Owner { + /** + * @return representation of the id of the owner. This is likely (but not limited to) the resource id of the owner + */ + public String getOwnerId(); + + /** + * @return the type describing the owner + */ + public OwnerType getOwnerType(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnerType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnerType.java new file mode 100644 index 000000000..ccec1b869 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnerType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +public enum OwnerType { + USER, GROUP +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownership.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownership.java new file mode 100644 index 000000000..3f44e8317 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownership.java @@ -0,0 +1,31 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "ownership") +@Data +@NoArgsConstructor +public class Ownership { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + protected Long id; + + private String ownedId; + private String ownedType; + private String ownerId; + private String ownerType; + + public Ownership(Owner owner, Ownable ownedObject) { + ownerId = owner.getOwnerId(); + ownerType = owner.getOwnerType().name(); + + ownedId = ownedObject.getObjectId(); + ownedType = ownedObject.getOwnableType().name(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepository.java new file mode 100644 index 000000000..ee4919210 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepository.java @@ -0,0 +1,59 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import java.util.List; +import java.util.Set; + +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; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; + +public interface OwnershipRepository extends JpaRepository { + /** + * Delete the user from any groups they may in. + */ + @Query("DELETE FROM ownership o WHERE o.ownedId = :username AND o.ownedType = 'USER' AND o.ownerType = 'GROUP'") + @Modifying(clearAutomatically = true, flushAutomatically = true) + void clearUsersGroups(@Param("username") String username); + + /** + * Remove any ownership of the ownable object + */ + @Query("DELETE FROM ownership o WHERE o.ownedId = :#{#ownedObject.getObjectId()} AND o.ownedType = :#{#ownedObject.getOwnableType().toString()}") + @Modifying(clearAutomatically = true, flushAutomatically = true) + void deleteEntriesForOwnedObject(@Param("ownedObject") Ownable ownedObject); + + /** + * Find all items an owner owns + */ + @Query("SELECT o FROM ownership o WHERE o.ownerId = :#{#owner.getOwnerId()} AND o.ownerType = :#{#owner.getOwnerType().toString()}") + Set findAllByOwner(@Param("owner") Owner owner); + + /** + * Find all the groups that a user belongs to + */ + @Query("SELECT o FROM ownership o WHERE o.ownedId = :username AND o.ownedType = 'USER' AND o.ownerType = 'GROUP' ") + Set findAllGroupsForUser(@Param("username") String username); + + /** + * Find the owner of this object + */ + @Query("SELECT o FROM ownership o WHERE o.ownedId = :#{#ownedObject.getObjectId()} AND o.ownedType = :#{#ownedObject.getOwnableType().toString()}") + Set findOwnableObjectOwners(@Param("ownedObject") Ownable ownedObject); + + /** + * Find all things the user owns + */ + @Query("SELECT o FROM ownership o WHERE o.ownerId = :username AND o.ownerType = 'USER' ") + List findOwnedByUser(@Param("username") String username); + + /** + * Find only the users that the owner owns + */ + @Query("SELECT o FROM ownership o WHERE o.ownerId = :#{#owner.getOwnerId()} AND o.ownerType = :#{#owner.getOwnerType().toString()} AND o.ownedType='USER'") + Set findUsersByOwner(@Param("owner") Owner owner); +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepositoryTests.groovy new file mode 100644 index 000000000..229763c58 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepositoryTests.groovy @@ -0,0 +1,273 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.annotation.Rollback +import org.springframework.test.context.ContextConfiguration +import org.springframework.transaction.annotation.Transactional + +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType +import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership +import spock.lang.Specification + +/** + * Tests to validate the repo and model for groups + * @author chasegawa + */ +@DataJpaTest +@ContextConfiguration(classes=[InternationalizationConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class OwnershipRepositoryTests extends Specification { + @Autowired + OwnershipRepository repo + + @Transactional + def setup() { + repo.deleteAll() + def ownerships = [ + new Ownership().with { + it.ownedId = "aaa" + it.ownedType = "USER" + it.ownerId = "g1" + it.ownerType = "GROUP" + it + }, + new Ownership().with { + it.ownedId = "bbb" + it.ownedType = "USER" + it.ownerId = "g1" + it.ownerType = "GROUP" + it + }, + new Ownership().with { + it.ownedId = "ccc" + it.ownedType = "USER" + it.ownerId = "g1" + it.ownerType = "GROUP" + it + }, + new Ownership().with { + it.ownedId = "ccc" + it.ownedType = "USER" + it.ownerId = "g2" + it.ownerType = "GROUP" + it + }, + new Ownership().with { + it.ownedId = "bbb" + it.ownedType = "ENTITY_DESCRIPTOR" + it.ownerId = "aaa" + it.ownerType = "GROUP" + it + }, + new Ownership().with { + it.ownedId = "aaa" + it.ownedType = "ENTITY_DESCRIPTOR" + it.ownerId = "aaa" + it.ownerType = "USER" + it + } + ] + ownerships.each { + repo.save(it) + } + } + + @Rollback + def "test clearUsersGroups"() { + when: "remove entries where the user is the owned object of a group" + repo.clearUsersGroups("aaa"); + def result = repo.findAllGroupsForUser("aaa") + + then: + result.size() == 0 + + when: "find objects owned by user aaa has not changed" + result = repo.findOwnedByUser("aaa") + + then: + result.size() == 1 + result.each { + it.ownerId == "aaa" + it.ownerType == "USER" + it.ownedId == "aaa" + it.ownedType == "ENTITY_DESCRIPTOR" + } + + when: "remove entries where the user is the owned object of groups" + repo.clearUsersGroups("ccc"); + result = repo.findAllGroupsForUser("ccc") + + then: + result.size() == 0 + } + + @Rollback + def "test deleteEntriesForOwnedObject"() { + when: "remove entries where the user is the owned object of a group" + repo.deleteEntriesForOwnedObject(new Ownable() { + public String getObjectId() { return "aaa" } + + public OwnableType getOwnableType() { OwnableType.USER } + }) + def result = repo.findAllGroupsForUser("aaa") + + then: + result.size() == 0 + + when: "find objects owned by user aaa has not changed" + result = repo.findOwnedByUser("aaa") + + then: + result.size() == 1 + result.each { + it.ownerId == "aaa" + it.ownerType == "USER" + it.ownedId == "aaa" + it.ownedType == "ENTITY_DESCRIPTOR" + } + + when: "remove entries where the user is the owned object of groups" + repo.deleteEntriesForOwnedObject(new Ownable() { + public String getObjectId() { return "ccc" } + + public OwnableType getOwnableType() { OwnableType.USER } + }); + result = repo.findAllGroupsForUser("ccc") + + then: + result.size() == 0 + } + + def "test findUsersByOwner"() { + when: "find all the users owned by group g1" + ArrayList userIds = new ArrayList<>() + userIds.add("aaa") + userIds.add("bbb") + userIds.add("ccc") + def result = repo.findUsersByOwner(new Owner() { + public String getOwnerId() { return "g1" } + + public OwnerType getOwnerType() { OwnerType.GROUP } + }) + + then: + result.size() == 3 + result.each { + userIds.contains(it.getOwnedId()) + it.ownedType == "USER" + } + + when: + result = repo.findUsersByOwner(new Owner() { + public String getOwnerId() { return "aaa" } + + public OwnerType getOwnerType() { return OwnerType.USER } + }) + + then: + result.size() == 0 + } + + def "test findOwnedByUser"() { + when: "find objects owned by user" + def result = repo.findOwnedByUser("aaa") + + then: + result.size() == 1 + result.each { + it.ownerId == "aaa" + it.ownerType == "USER" + it.ownedId == "aaa" + it.ownedType == "ENTITY_DESCRIPTOR" + } + } + + def "test findOwnableObjectOwners"() { + when: "find owners for OWNABLE" + ArrayList groupIds = new ArrayList<>() + groupIds.add("g1") + groupIds.add("g2") + def result = repo.findOwnableObjectOwners(new Ownable() { + public String getObjectId() { return "ccc" } + public OwnableType getOwnableType() { return OwnableType.USER } + }) + + then: + result.size() == 2 + result.each { + it.ownerType == "GROUP" + it.ownedId == "ccc" + groupIds.contains(it.getOwnedId()) + } + } + + def "test findAllGroupsForUser"() { + when: "find all groups for user aaa" + def result = repo.findAllGroupsForUser("aaa") + + then: + result.size() == 1 + result.each { + it.ownedId == "g1" + it.ownedType == "GROUP" + } + + when: "find all groups for user ccc" + ArrayList groupIds = new ArrayList<>() + groupIds.add("g1") + groupIds.add("g2") + result = repo.findAllGroupsForUser("ccc") + + then: + result.size() == 2 + result.each { + it.ownerType == "GROUP" + it.ownedId == "ccc" + groupIds.contains(it.getOwnedId()) + } + } + + def "test findAllByOwner" () { + when: "Find all for group g1" + ArrayList userIds = new ArrayList() + userIds.add("aaa") + userIds.add("bbb") + userIds.add("ccc") + def result = repo.findAllByOwner(new Owner() { + public String getOwnerId() { return "g1" } + + public OwnerType getOwnerType() { return OwnerType.GROUP } + }) + + then: + result.size() == 3 + result.each { + userIds.contains(it.getOwnedId()) + it.ownedType == "USER" + } + + when: "Find all items owned by user aaa" + result = repo.findAllByOwner(new Owner() { + public String getOwnerId() { return "aaa" } + + public OwnerType getOwnerType() { return OwnerType.USER } + }) + + then: + result.size() == 1 + result.each { + it.ownerId == "aaa" + it.ownerType == "USER" + it.ownedId == "aaa" + it.ownedType == "ENTITY_DESCRIPTOR" + } + } +} \ No newline at end of file