From 4a8cfe6f7d8e63be2ff0163782646770ae6b9077 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Sun, 8 Aug 2021 11:41:12 -0700 Subject: [PATCH 1/8] SHIBUI-2003 First pass - ownership items --- .../admin/ui/security/model/Ownable.java | 13 + .../admin/ui/security/model/OwnableType.java | 5 + .../admin/ui/security/model/Owner.java | 13 + .../admin/ui/security/model/OwnerType.java | 5 + .../admin/ui/security/model/Ownership.java | 31 ++ .../repository/OwnershipRepository.java | 59 ++++ .../OwnershipRepositoryTests.groovy | 273 ++++++++++++++++++ 7 files changed, 399 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownable.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Owner.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnerType.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Ownership.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepository.java create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/OwnershipRepositoryTests.groovy 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 From d43407efb7c293400e91ab3506c1557579cff27a Mon Sep 17 00:00:00 2001 From: chasegawa Date: Sun, 8 Aug 2021 11:54:00 -0700 Subject: [PATCH 2/8] SHIBUI-2003 Adjusting hibernate properties to enforce not-null declaration --- backend/src/main/resources/application.properties | 2 +- .../CustomEntityAttributeDefinitionRepositoryTests.groovy | 2 +- .../admin/ui/security/repository/GroupsRepositoryTests.groovy | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index de3f94759..83f2635e0 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -43,7 +43,7 @@ spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.show-sql=false spring.jpa.properties.hibernate.format_sql=false - +spring.jpa.properties.hibernate.check_nullability=true spring.jpa.hibernate.use-new-id-generator-mappings=true #Envers versioning diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/CustomEntityAttributeDefinitionRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/CustomEntityAttributeDefinitionRepositoryTests.groovy index 5c7f8cf8e..64dc3cdd3 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/CustomEntityAttributeDefinitionRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/CustomEntityAttributeDefinitionRepositoryTests.groovy @@ -85,7 +85,7 @@ class CustomEntityAttributeDefinitionRepositoryTests extends Specification { then: // Missing non-nullable field should thrown error - final def exception = thrown(javax.persistence.PersistenceException) + final def exception = thrown(org.springframework.dao.DataIntegrityViolationException) } def "basic CRUD operations validated"() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy index a5c8f07f5..805c8d27c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy @@ -82,7 +82,7 @@ class GroupsRepositoryTests extends Specification { then: // Missing non-nullable field (name) should thrown error - final def exception = thrown(javax.persistence.PersistenceException) + final def exception = thrown(org.springframework.dao.DataIntegrityViolationException) } def "basic CRUD operations validated"() { From 154cea4c58520981ff423d7b3f3c9e130440192a Mon Sep 17 00:00:00 2001 From: chasegawa Date: Sun, 8 Aug 2021 20:40:10 -0700 Subject: [PATCH 3/8] SHIBUI-2003 changed to group and users as owners and owned --- .../admin/ui/configuration/DevConfig.groovy | 6 +- .../CoreShibUiConfiguration.java | 23 +- .../MigrationTasksContextLoadedListener.java | 67 ++++ .../admin/ui/controller/DangerController.java | 6 +- .../admin/ui/domain/EntityDescriptor.java | 27 +- .../EntityDescriptorRepresentation.java | 5 +- .../EntityDescriptorRepository.java | 8 +- .../security/controller/UsersController.java | 53 ++- .../exception/OwnershipConflictException.java | 7 + .../admin/ui/security/model/Group.java | 74 ++-- .../admin/ui/security/model/User.java | 118 +++---- .../admin/ui/security/model/UserGroup.java | 35 -- .../admin/ui/security/model/UserGroupKey.java | 20 -- .../listener/GroupUpdatedEntityListener.java | 39 +++ .../model/listener/ILazyLoaderHelper.java | 10 + .../listener/UserUpdatedEntityListener.java | 50 +++ .../repository/OwnershipRepository.java | 7 + .../repository/UserGroupRepository.java | 18 - .../ui/security/service/GroupServiceImpl.java | 91 ++--- .../ui/security/service/IGroupService.java | 6 +- .../ui/security/service/UserService.java | 104 +++--- .../JPAEntityDescriptorServiceImpl.java | 41 +-- .../ui/configuration/TestConfiguration.groovy | 7 +- .../EntityDescriptorControllerTests.groovy | 130 ++++--- .../ui/domain/EntityDescriptorTest.groovy | 10 +- .../EntityDescriptorRepositoryTest.groovy | 29 +- .../GroupsControllerIntegrationTests.groovy | 2 + .../UsersControllerIntegrationTests.groovy | 322 ++++++++++++------ .../repository/GroupsRepositoryTests.groovy | 141 ++++++-- .../service/GroupServiceForTesting.groovy | 21 ++ .../security/service/UserServiceTests.groovy | 156 +++++++-- .../service/AuxiliaryIntegrationTests.groovy | 2 +- ...JPAMetadataResolverServiceImplTests.groovy | 11 +- ...PAEntityDescriptorServiceImplTests2.groovy | 31 +- ...JPAMetadataResolverServiceImplTests.groovy | 10 +- .../ui/service/UserBootstrapTests.groovy | 7 +- 36 files changed, 1081 insertions(+), 613 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/MigrationTasksContextLoadedListener.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/exception/OwnershipConflictException.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroup.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroupKey.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserGroupRepository.java create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index 65fcb7ed4..46ff633cd 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -18,6 +18,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions @@ -47,7 +48,8 @@ class DevConfig { MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository, - OpenSamlObjects openSamlObjects) { + OpenSamlObjects openSamlObjects, + IGroupService groupService) { this.userRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository @@ -55,6 +57,8 @@ class DevConfig { this.entityDescriptorRepository = entityDescriptorRepository this.openSamlObjects = openSamlObjects this.groupsRepository = groupsRepository + + groupService.ensureAdminGroupExists() } @Transactional diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index b9006e56a..c117b415f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -28,8 +28,13 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository; import edu.internet2.tier.shibboleth.admin.ui.scheduled.EntityDescriptorFilesScheduledTasks; import edu.internet2.tier.shibboleth.admin.ui.scheduled.MetadataProvidersScheduledTasks; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.DefaultMetadataResolversPositionOrderContainerService; import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryService; @@ -201,8 +206,8 @@ public ModelRepresentationConversions modelRepresentationConversions() { } @Bean - public UserService userService(RoleRepository roleRepository, UserRepository userRepository) { - return new UserService(roleRepository, userRepository); + public UserService userService(IGroupService groupService, OwnershipRepository ownershipRepository, RoleRepository roleRepository, UserRepository userRepository) { + return new UserService(groupService, ownershipRepository, roleRepository, userRepository); } @Bean @@ -216,4 +221,18 @@ public EntityDescriptorConversionUtils EntityDescriptorConverstionUtilsInit(Enti EntityDescriptorConversionUtils.setOpenSamlObjects(oso); return new EntityDescriptorConversionUtils(); } + + @Bean + public GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo) { + GroupUpdatedEntityListener listener = new GroupUpdatedEntityListener(); + listener.init(repo); + return listener; + } + + @Bean + public UserUpdatedEntityListener userUpdatedEntityListener(OwnershipRepository repo, GroupsRepository groupRepo) { + UserUpdatedEntityListener listener = new UserUpdatedEntityListener(); + listener.init(repo, groupRepo); + return listener; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/MigrationTasksContextLoadedListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/MigrationTasksContextLoadedListener.java new file mode 100644 index 000000000..f14d35763 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/MigrationTasksContextLoadedListener.java @@ -0,0 +1,67 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration.auto; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; + +/** + * After the context loads, do any needed migration tasks + */ +@Component +public class MigrationTasksContextLoadedListener implements ApplicationListener { + @Autowired + private EntityDescriptorRepository entityDescriptorRepository; + + @Autowired + private IGroupService groupService; + + @Autowired + private OwnershipRepository ownershipRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + doshibui_1740_migration(); // do first + } + + @Transactional + private void doshibui_1740_migration() { + groupService.ensureAdminGroupExists(); // do first + + // SHIBUI-1740: Adding admin group to all existing entity descriptors that do not have a group already. + // the ADMIN_GROUP has already been setup (just above) + try { + entityDescriptorRepository.findAllByIdOfOwnerIsNull().forEach(ed -> { + ed.setIdOfOwner(Group.ADMIN_GROUP.getOwnerId()); + ed = entityDescriptorRepository.saveAndFlush(ed); + ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, ed)); + }); + } + catch (NullPointerException e) { + // This block was added due to a number of mock test where NPEs happened. Rather than wire more mock junk + // into tests that are only trying to compensate for this migration, this is here + } + + userRepository.findAll().forEach(user -> { + if (user.getGroupId() == null) { + userService.save(user); // this will ensure group is set as the default user group + } + }); + + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java index cb1f9719f..532731bfc 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java @@ -12,6 +12,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import lombok.extern.slf4j.Slf4j; @@ -39,6 +40,9 @@ public class DangerController { @Autowired private MetadataResolversPositionOrderContainerRepository metadataResolversPositionOrderContainerRepository; + @Autowired + private OwnershipRepository ownershipRepository; + @Transactional @GetMapping public ResponseEntity wipeOut() { @@ -46,7 +50,7 @@ public ResponseEntity wipeOut() { try { ed.setServiceEnabled(false); edRepo.save(ed); - groupService.removeEntityFromGroup(ed); + ownershipRepository.deleteEntriesForOwnedObject(ed); entityDescriptorService.delete(ed.getResourceId()); } catch (Throwable e) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java index dacc88bfc..c090db618 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java @@ -5,6 +5,8 @@ import com.google.common.collect.Lists; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -36,7 +38,7 @@ @Entity @EqualsAndHashCode(callSuper = true) @Audited -public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor { +public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor, Ownable { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "entitydesc_addlmetdatlocations_id") @OrderColumn @@ -61,18 +63,15 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml private String entityID; - @ManyToOne - @JoinColumn(name = "group_resource_id") - @EqualsAndHashCode.Exclude - @Setter - @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) - private Group group; - private String localId; @OneToOne(cascade = CascadeType.ALL) private Organization organization; + @Getter + @Setter + private String idOfOwner; + @OneToOne(cascade = CascadeType.ALL) @NotAudited private PDPDescriptor pdpDescriptor; @@ -144,10 +143,6 @@ public IDPSSODescriptor getIDPSSODescriptor(String s) { .orElse(null); } - public Group getGroup() { - return group == null ? Group.ADMIN_GROUP : group; - } - @Transient public Optional getOptionalSPSSODescriptor() { return this.getOptionalSPSSODescriptor(""); @@ -302,4 +297,12 @@ public String toString() { .add("id", id) .toString(); } + + public String getObjectId() { + return entityID; + } + + public OwnableType getOwnableType() { + return OwnableType.ENTITY_DESCRIPTOR; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index 6ca5d7a4d..7062c9ec7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -36,7 +36,8 @@ public class EntityDescriptorRepresentation implements Serializable { private String entityId; @Setter - private String groupId; + @Getter + private String idOfOwner; private String id; @@ -110,7 +111,7 @@ public String getId() { } public String getGroupId() { - return groupId == null ? Group.ADMIN_GROUP.getResourceId() : groupId; + return idOfOwner == null ? Group.ADMIN_GROUP.getResourceId() : idOfOwner; } public List getLogoutEndpoints() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java index 41edbe2fe..5e28e7d75 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java @@ -22,16 +22,16 @@ public interface EntityDescriptorRepository extends JpaRepository findAllStreamByCustomQuery(); + Stream findAllStreamByIdOfOwner(String ownerId); + @Query("select e from EntityDescriptor e, User u join u.roles r " + "where e.createdBy = u.username and e.serviceEnabled = false and r.name in ('ROLE_USER', 'ROLE_NONE')") Stream findAllDisabledAndNotOwnedByAdmin(); - - Stream findAllStreamByGroup_resourceId(String resourceId); - + /** * SHIBUI-1740 This is here to aid in migration of systems using the SHIBUI prior to group functionality being added * @deprecated - this is intended to be removed at some future date and is here only for migration purposes. */ @Deprecated - List findAllByGroupIsNull(); + List findAllByIdOfOwnerIsNull(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java index 354f0f1df..93fec88aa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java @@ -1,20 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.security.controller; -import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; -import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; -import edu.internet2.tier.shibboleth.admin.ui.security.model.User; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; -import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; -import groovy.util.logging.Slf4j; -import jline.internal.Log; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +import java.security.Principal; +import java.util.List; +import java.util.Optional; import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,11 +22,14 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpClientErrorException; -import java.security.Principal; -import java.util.List; -import java.util.Optional; - -import static org.springframework.http.HttpStatus.NOT_FOUND; +import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.security.exception.OwnershipConflictException; +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import groovy.util.logging.Slf4j; +import jline.internal.Log; /** * Implementation of the REST resource endpoints exposing system users. @@ -43,16 +38,10 @@ @RequestMapping("/api/admin/users") @Slf4j public class UsersController { - @Autowired - private GroupsRepository groupRepo; - - @Autowired - private IGroupService groupService; - private UserRepository userRepository; private UserService userService; - public UsersController(UserRepository userRepository, RoleRepository roleRepository, UserService userService) { + public UsersController(UserRepository userRepository, UserService userService) { this.userRepository = userRepository; this.userService = userService; } @@ -67,12 +56,15 @@ public ResponseEntity deleteOne(@PathVariable String username) { catch (EntityNotFoundException e) { throw new HttpClientErrorException(NOT_FOUND, String.format("User with username [%s] not found", username)); } + catch (OwnershipConflictException e) { + throw new HttpClientErrorException(HttpStatus.CONFLICT, e.getMessage()); + } return ResponseEntity.noContent().build(); } private User findUserOrThrowHttp404(String username) { - return userRepository.findByUsername(username) - .orElseThrow(() -> new HttpClientErrorException(NOT_FOUND, String.format("User with username [%s] not found", username))); + Optional result = userRepository.findByUsername(username); + return result.orElseThrow(() -> new HttpClientErrorException(NOT_FOUND, String.format("User with username [%s] not found", username))); } @PreAuthorize("hasRole('ADMIN')") @@ -134,6 +126,7 @@ ResponseEntity saveOne(@RequestBody User user) { @PatchMapping("/{username}") ResponseEntity updateOne(@PathVariable(value = "username") String username, @RequestBody User user) { User persistedUser = findUserOrThrowHttp404(username); + if (StringUtils.isNotBlank(user.getFirstName())) { persistedUser.setFirstName(user.getFirstName()); } @@ -147,8 +140,10 @@ ResponseEntity updateOne(@PathVariable(value = "username") String username, @ persistedUser.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt())); } if (StringUtils.isNotBlank(user.getRole())) { - persistedUser.setRole(user.getRole()); - userService.updateUserRole(persistedUser); + if (!user.getRole().equals(persistedUser.getRole())) { + persistedUser.setRole(user.getRole()); + userService.updateUserRole(persistedUser); + } } persistedUser.setGroupId(user.getGroupId()); User savedUser = userService.save(persistedUser); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/exception/OwnershipConflictException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/exception/OwnershipConflictException.java new file mode 100644 index 000000000..21b33e9e1 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/exception/OwnershipConflictException.java @@ -0,0 +1,7 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.exception; + +public class OwnershipConflictException extends Exception { + public OwnershipConflictException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java index a3bd133f8..5af5aac3f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java @@ -4,67 +4,75 @@ import java.util.Set; import java.util.UUID; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.FetchType; +import javax.persistence.EntityListeners; import javax.persistence.Id; -import javax.persistence.OneToMany; import javax.persistence.Transient; import com.fasterxml.jackson.annotation.JsonIgnore; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.ILazyLoaderHelper; import lombok.Data; -import lombok.EqualsAndHashCode; +import lombok.EqualsAndHashCode.Exclude; +import lombok.NoArgsConstructor; -@Entity(name = "user_groups") @Data -public class Group { +@NoArgsConstructor +@EntityListeners(GroupUpdatedEntityListener.class) +@Entity(name = "user_groups") +public class Group implements Owner { @Transient @JsonIgnore public static Group ADMIN_GROUP; - + @Column(name = "group_description", nullable = true) String description; - @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @Transient @JsonIgnore - @EqualsAndHashCode.Exclude - Set entityDescriptors = new HashSet<>(); - + @Exclude + private ILazyLoaderHelper lazyLoaderHelper; + @Column(nullable = false) - String name; + private String name; + + @Transient + @JsonIgnore + private Set ownedItems = new HashSet<>(); @Id @Column(name = "resource_id") - String resourceId = UUID.randomUUID().toString(); + private String resourceId = UUID.randomUUID().toString(); - @OneToMany(mappedBy = "group", fetch = FetchType.EAGER) - @EqualsAndHashCode.Exclude - @JsonIgnore - private Set userGroups = new HashSet<>(); - - public Group() { - } - + /** + * Define a Group object based on the user + */ public Group(User user) { resourceId = user.getUsername(); name = user.getUsername(); description = "default user-group"; - } + } - public void addUser(User user) { - if (userGroups == null) { - userGroups = new HashSet<>(); - } - userGroups.add(new UserGroup(this, user)); + @Override + public String getOwnerId() { + return resourceId; + } + + @Override + public OwnerType getOwnerType() { + return OwnerType.GROUP; + } + + public void registerLoader(ILazyLoaderHelper lazyLoaderHelper) { + this.lazyLoaderHelper = lazyLoaderHelper; } - - public Set getUserGroups() { - if (userGroups == null) { - userGroups = new HashSet<>(); + + public Set getOwnedItems() { + if (lazyLoaderHelper != null) { + lazyLoaderHelper.loadOwnedItems(this); } - return userGroups; + return ownedItems; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java index b15a4ace0..cb769c662 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java @@ -1,41 +1,46 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; -import org.apache.commons.lang.StringUtils; +import java.util.HashSet; +import java.util.Set; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EntityListeners; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; -import java.util.HashSet; -import java.util.Set; + +import org.apache.commons.lang.StringUtils; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.ILazyLoaderHelper; +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; /** * Models a basic administrative user in the system. * * @author Dmitriy Kopylenko */ -@Entity @NoArgsConstructor @Getter @Setter @EqualsAndHashCode(callSuper = true) @ToString(exclude = "roles") +@EntityListeners(UserUpdatedEntityListener.class) +@Entity @Table(name = "USERS") -public class User extends AbstractAuditable { +public class User extends AbstractAuditable implements Owner, Ownable { private String emailAddress; private String firstName; @@ -45,11 +50,11 @@ public class User extends AbstractAuditable { private String groupId; // simplifies the ui/api private String lastName; - + @Transient @JsonIgnore @EqualsAndHashCode.Exclude - private Set oldUserGroups = new HashSet<>(); + private ILazyLoaderHelper lazyLoaderHelper; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @Column(nullable = false) @@ -65,17 +70,13 @@ public class User extends AbstractAuditable { @EqualsAndHashCode.Exclude private Set roles = new HashSet<>(); - @OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @EqualsAndHashCode.Exclude - @JsonIgnore // do not remove this or all hell will break loose - private Set userGroups = new HashSet<>(); + @JsonIgnore + @Transient + private Set userGroups = new HashSet<>(); @Column(nullable = false, unique = true) private String username; - - public void clearOldUserGroups() { - oldUserGroups.clear(); - } /** * @return the initial implementation, while supporting a user having multiple groups in the db side, acts as if the @@ -83,17 +84,36 @@ public void clearOldUserGroups() { */ @JsonIgnore public Group getGroup() { - - return userGroups.isEmpty() ? null : ((UserGroup)userGroups.toArray()[0]).getGroup(); + return getUserGroups().isEmpty() ? null : (Group) userGroups.toArray()[0]; } public String getGroupId() { if (groupId == null) { - groupId = userGroups.isEmpty() ? null : getGroup().getResourceId(); + groupId = getUserGroups().isEmpty() ? null : getGroup().getResourceId(); } return groupId; } + @Override + public String getObjectId() { + return username; + } + + @Override + public OwnableType getOwnableType() { + return OwnableType.USER; + } + + @Override + public String getOwnerId() { + return username; + } + + @Override + public OwnerType getOwnerType() { + return OwnerType.USER; + } + public String getRole() { if (StringUtils.isBlank(this.role)) { Set roles = this.getRoles(); @@ -105,50 +125,24 @@ public String getRole() { return this.role; } - public Set getUserGroups() { - if (userGroups == null) { - userGroups = new HashSet<>(); + public Set getUserGroups() { + if (lazyLoaderHelper != null) { + lazyLoaderHelper.loadGroups(this); } return userGroups; } public void setGroup(Group g) { groupId = g.getResourceId(); - updateUserGroupsWithGroup(g); + userGroups.clear(); + userGroups.add(g); } - + public void setGroups(Set groups) { - oldUserGroups.addAll(getUserGroups()); - getUserGroups().clear(); - groups.forEach(g -> userGroups.add(new UserGroup(g, this))); + this.userGroups = groups; } - - /** - * If we change groups, we have to manually manage the set of UserGroups so that we don't have group associations - * we didn't intend (thanks JPA!!). - */ - public void updateUserGroupsWithGroup(Group assignedGroup) { - final Set setWithNewGroup= new HashSet<>(); - // Go through the existing UserGroups: - // 1) If a UG doesn't match the incoming assignment, move it out of the list and into the old for deletion - // 2) If it DOES match, update the group object so hibernate doesn't have a cow - userGroups.forEach(ug -> { - if (ug.getGroup().getResourceId().equals(assignedGroup.getResourceId())) { - ug.setGroup(assignedGroup); - setWithNewGroup.add(ug); - } else { - oldUserGroups.add(ug); - } - }); - userGroups = setWithNewGroup; - - // Assign the new group - if (userGroups.isEmpty()) { - UserGroup ug = new UserGroup(assignedGroup, this); - userGroups.add(ug); - } - // Set reference for the UI - groupId = assignedGroup.getResourceId(); + public void registerLoader(ILazyLoaderHelper lazyLoaderHelper) { + this.lazyLoaderHelper = lazyLoaderHelper; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroup.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroup.java deleted file mode 100644 index 492fc600b..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroup.java +++ /dev/null @@ -1,35 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.model; - -import javax.persistence.CascadeType; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.MapsId; - -import lombok.Data; - -@Entity -@Data -public class UserGroup { - public UserGroup() { - } - - public UserGroup(Group group, User user) { - this.group = group; - this.user = user; - } - - @EmbeddedId - UserGroupKey id = new UserGroupKey(); - - @ManyToOne - @MapsId("resourceId") - @JoinColumn(name = "resource_id") - Group group; - - @ManyToOne - @MapsId("userId") - @JoinColumn(name = "user_id") - User user; -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroupKey.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroupKey.java deleted file mode 100644 index d5408f3ef..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserGroupKey.java +++ /dev/null @@ -1,20 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.model; - -import java.io.Serializable; - -import javax.persistence.Column; -import javax.persistence.Embeddable; - -import lombok.Data; - -@Embeddable -@Data -public class UserGroupKey implements Serializable { - private static final long serialVersionUID = 1L; - - @Column(name = "group_resource_id") - private String resourceId; - - @Column(name = "user_id") - private long userId; -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java new file mode 100644 index 000000000..b9dc9c5d0 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java @@ -0,0 +1,39 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model.listener; + +import java.util.Set; + +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PostUpdate; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; + +public class GroupUpdatedEntityListener implements ILazyLoaderHelper { + private static OwnershipRepository ownershipRepository; + + /** + * @see https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener + */ + public void init(OwnershipRepository repo) { + GroupUpdatedEntityListener.ownershipRepository = repo; + } + + @PostPersist + @PostUpdate + @PostLoad + public synchronized void groupSavedOrFetched(Group group) { + // Because of the JPA spec, the listener can't do queries in the callback, so we force lazy loading through + // another callback to this at the time that the owned items are needed + group.registerLoader(this); + } + + @Override + public void loadOwnedItems(Group group) { + group.registerLoader(null); // once loaded, remove the helper from the group + Set ownedItems = ownershipRepository.findAllByOwner(group); + group.setOwnedItems(ownedItems); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java new file mode 100644 index 000000000..f2b4df092 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java @@ -0,0 +1,10 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model.listener; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; + +public interface ILazyLoaderHelper { + default public void loadOwnedItems(Group g) { } + + default public void loadGroups(User u) { } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java new file mode 100644 index 000000000..96826f254 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java @@ -0,0 +1,50 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model.listener; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PostUpdate; + +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; + +public class UserUpdatedEntityListener implements ILazyLoaderHelper { + private static GroupsRepository groupRepository; + private static OwnershipRepository ownershipRepository; + + /** + * @see https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener + */ + public void init(OwnershipRepository repo, GroupsRepository groupRepo) { + UserUpdatedEntityListener.ownershipRepository = repo; + UserUpdatedEntityListener.groupRepository = groupRepo; + } + + @PostPersist + @PostUpdate + @PostLoad + @Transactional(propagation = Propagation.REQUIRES_NEW) + public synchronized void userSavedOrFetched(User user) { + // Because of the JPA spec, the listener can't do queries in the callback, so we force lazy loading through + // another callback to this at the time that the groups are needed + user.registerLoader(this); + } + + public void loadGroups(User user) { + user.setLazyLoaderHelper(null); + Set ownerships = ownershipRepository.findAllGroupsForUser(user.getUsername()); + HashSet groups = new HashSet<>(); + ownerships.forEach(ownership -> { + groups.add(groupRepository.findByResourceId(ownership.getOwnerId())); + }); + user.setGroups(groups); + } +} \ 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 index ee4919210..4aa8f4dfd 100644 --- 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 @@ -13,6 +13,13 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; public interface OwnershipRepository extends JpaRepository { + /** + * Clear out anything owned by any group + */ + @Query("DELETE FROM ownership o WHERE o.ownerType = 'GROUP'") + @Modifying(clearAutomatically = true, flushAutomatically = true) + void clearAllOwnedByGroup(); + /** * Delete the user from any groups they may in. */ diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserGroupRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserGroupRepository.java deleted file mode 100644 index 3de20d7f6..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserGroupRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.repository; - -import java.util.List; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; -import edu.internet2.tier.shibboleth.admin.ui.security.model.User; -import edu.internet2.tier.shibboleth.admin.ui.security.model.UserGroup; -import edu.internet2.tier.shibboleth.admin.ui.security.model.UserGroupKey; - -public interface UserGroupRepository extends JpaRepository { - - List findAllByUser(User user); - - List findAllByGroup(Group group); -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java index a6e35d470..2501f007e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java @@ -1,59 +1,31 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; -import java.util.HashSet; import java.util.List; -import java.util.Optional; -import java.util.Set; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupDeleteException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; -import edu.internet2.tier.shibboleth.admin.ui.security.model.UserGroup; import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserGroupRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; +import lombok.NoArgsConstructor; @Service -public class GroupServiceImpl implements IGroupService, InitializingBean { +@NoArgsConstructor +public class GroupServiceImpl implements IGroupService { @Autowired - private GroupsRepository repo; + protected GroupsRepository groupRepository; @Autowired - private UserGroupRepository userGroupRepo; + protected OwnershipRepository ownershipRepository; - public GroupServiceImpl() { - } - - public GroupServiceImpl(GroupsRepository repo) { - this.repo = repo; - } - - /** - * Ensure (mostly for migrations) that we have defined a default admin group - */ - @Override - @Transactional - public void afterPropertiesSet() { - Group g = repo.findByResourceId("admingroup"); - if (g == null) { - g = new Group(); - g.setName("ADMIN-GROUP"); - g.setResourceId("admingroup"); - g = repo.save(g); - } - Group.ADMIN_GROUP = g; - } - - @Override - public void clearAllForTesting() { - repo.deleteAll(); - afterPropertiesSet(); + public GroupServiceImpl(GroupsRepository repo, OwnershipRepository ownershipRepository) { + this.groupRepository = repo; + this.ownershipRepository = ownershipRepository; } @Override @@ -66,46 +38,43 @@ public Group createGroup(Group group) throws GroupExistsConflictException { String.format("Call update (PUT) to modify the group with resource id: [%s] and name: [%s]", foundGroup.getResourceId(), foundGroup.getName())); } - return repo.save(group); + return groupRepository.save(group); } @Override @Transactional public void deleteDefinition(String resourceId) throws EntityNotFoundException, GroupDeleteException { - Group g = find(resourceId); - List userGroups = userGroupRepo.findAllByGroup(g); - if (!userGroups.isEmpty() || !g.getEntityDescriptors().isEmpty()) { + Group group = find(resourceId); + if (!ownershipRepository.findAllByOwner(group).isEmpty()) { throw new GroupDeleteException(String.format( - "Unable to delete group with resource id: [%s] - remove all users and entities from group first", + "Unable to delete group with resource id: [%s] - remove all items owned by / associated with the group first", resourceId)); } - repo.delete(g); + groupRepository.delete(group); } @Override @Transactional - public Group find(String resourceId) { - return repo.findByResourceId(resourceId); + public void ensureAdminGroupExists() { + Group g = groupRepository.findByResourceId("admingroup"); + if (g == null) { + g = new Group(); + g.setName("ADMIN-GROUP"); + g.setResourceId("admingroup"); + g = groupRepository.save(g); + } + Group.ADMIN_GROUP = g; } @Override - public List findAll() { - return repo.findAll(); + @Transactional + public Group find(String resourceId) { + return groupRepository.findByResourceId(resourceId); } @Override - @Transactional - public void removeEntityFromGroup(final EntityDescriptor ed) { - Group g = repo.findByResourceId(ed.getGroup().getResourceId()); - Set eds = g.getEntityDescriptors(); - final HashSet updatedSet = new HashSet<>(); - eds.forEach(entityDesc -> { - if (!entityDesc.getEntityID().equals(ed.getEntityID())) { - updatedSet.add(entityDesc); - } - }); - g.setEntityDescriptors(updatedSet); - repo.save(g); + public List findAll() { + return groupRepository.findAll(); } @Override @@ -115,6 +84,6 @@ public Group updateGroup(Group group) throws EntityNotFoundException { throw new EntityNotFoundException(String.format("Unable to find group with resource id: [%s] and name: [%s]", group.getResourceId(), group.getName())); } - return repo.save(group); + return groupRepository.save(group); } -} +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IGroupService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IGroupService.java index 8972aa09f..1c4229d84 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IGroupService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IGroupService.java @@ -10,18 +10,16 @@ public interface IGroupService { - void clearAllForTesting(); - Group createGroup(Group group) throws GroupExistsConflictException; void deleteDefinition(String resourceId) throws EntityNotFoundException, GroupDeleteException; + void ensureAdminGroupExists(); + Group find(String resourceId); List findAll(); - void removeEntityFromGroup(EntityDescriptor ed); - Group updateGroup(Group g) throws EntityNotFoundException; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java index 378a82a92..a04e2b574 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java @@ -5,7 +5,6 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -13,62 +12,55 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; +import edu.internet2.tier.shibboleth.admin.ui.security.exception.OwnershipConflictException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; import edu.internet2.tier.shibboleth.admin.ui.security.model.Role; import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserGroupRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; +import lombok.NoArgsConstructor; @Service -public class UserService implements InitializingBean { +@NoArgsConstructor +public class UserService { @Autowired private IGroupService groupService; @Autowired - private RoleRepository roleRepository; + private OwnershipRepository ownershipRepository; @Autowired - UserGroupRepository userGroupRepository; + private RoleRepository roleRepository; @Autowired private UserRepository userRepository; - - public UserService() { - } - /** - * Primarily for testing purposes so we can control the injections - */ - public UserService(RoleRepository roleRepository, UserRepository userRepository) { + public UserService(IGroupService groupService, OwnershipRepository ownershipRepository, RoleRepository roleRepository, UserRepository userRepository) { + this.groupService = groupService; + this.ownershipRepository = ownershipRepository; this.roleRepository = roleRepository; this.userRepository = userRepository; } - @Override - @Transactional - public void afterPropertiesSet() { - userRepository.findAll().forEach(user -> { - if (user.getGroupId() == null) { - save(user); // this will ensure group is set as the default user group - } - }); - } - public boolean currentUserIsAdmin() { User user = getCurrentUser(); return user != null && user.getRole().equals("ROLE_ADMIN"); } - + @Transactional - public void delete(String username) throws EntityNotFoundException { + public void delete(String username) throws EntityNotFoundException, OwnershipConflictException { Optional userToRemove = userRepository.findByUsername(username); if (userToRemove.isEmpty()) throw new EntityNotFoundException("User does not exist"); - User user = userToRemove.get(); - // remove all group references from the user - user.getUserGroups().forEach(userGroup -> userGroupRepository.delete(userGroup)); - user.getUserGroups().clear(); + if (!ownershipRepository.findOwnedByUser(username).isEmpty()) throw new OwnershipConflictException("User ["+username+"] has ownership of entities in the system. Please remove all items before attemtping to delete the user."); + // ok, user exists and doesn't own anything in the system, so delete them + // If the user is owned by anything, clear that first + ownershipRepository.clearUsersGroups(username); + User user = userToRemove.get(); userRepository.delete(user); } @@ -119,25 +111,24 @@ public Set getUserRoles(String username) { return result; } - public boolean isAuthorizedFor(Group objectGroup) { - String objectGroupId = objectGroup == null ? Group.ADMIN_GROUP.getResourceId() : objectGroup.getResourceId(); - return isAuthorizedFor(objectGroupId); - } - - public boolean isAuthorizedFor(String objectGroupResourceId) { - switch (getCurrentUserAccess()) { // no user returns NONE - case ADMIN: + public boolean isAuthorizedFor(Ownable ownableObject) { + switch (getCurrentUserAccess()) { + case ADMIN: // Pure admin is authorized to do anything return true; - case GROUP: - User currentUser = getCurrentUser(); - // Shouldn't be null, but for safety... - String groupId = objectGroupResourceId == null ? "" : objectGroupResourceId; - return groupId.equals(currentUser.getGroupId()); - default: + case GROUP: // if the current user's group matches the object's group we are good. + Set owners = ownershipRepository.findOwnableObjectOwners(ownableObject); + String currentUsersGroupId = getCurrentUser().getGroupId(); + for (Ownership owner : owners) { + if (currentUsersGroupId.equals(owner.getOwnerId()) && OwnerType.valueOf(owner.getOwnerType()) == OwnerType.GROUP) { + return true; + } + } + return false; + default: // Currently the only cases are ADMIN or GROUP return false; } } - + /** * Creating users should always have a group. If the user isn't assigned to a group, create one based on their name. * If the user has the ADMIN role, they are always solely assigned to the admin group. @@ -146,11 +137,12 @@ public boolean isAuthorizedFor(String objectGroupResourceId) { */ @Transactional public User save(User user) { - if (user.getUserGroups().size() < 2) { + if (user.getRole().equalsIgnoreCase("ROLE_ADMIN") || user.getUserGroups().size() < 2) { Group g; if (user.getRole().equalsIgnoreCase("ROLE_ADMIN")) { g = groupService.find(Group.ADMIN_GROUP.getResourceId()); - } else if (user.getGroupId() == null) { // Find or create the "user's default" group + } else if (user.getGroupId() == null) { + // Find or create the "user's default" group g = new Group(user); try { g = groupService.createGroup(g); @@ -161,31 +153,27 @@ public User save(User user) { } else { g = groupService.find(user.getGroupId()); } - user.updateUserGroupsWithGroup(g); + ownershipRepository.clearUsersGroups(user.getUsername()); + ownershipRepository.saveAndFlush(new Ownership(g, user)); } else { + ownershipRepository.clearUsersGroups(user.getUsername()); user.getUserGroups().forEach(ug -> { - Group g = groupService.find(ug.getGroup().getResourceId()); + Group g = groupService.find(ug.getResourceId()); if (g == null) { try { - Group newGroup = ug.getGroup(); - newGroup.addUser(user); + Group newGroup = ug; + Ownership o = ownershipRepository.saveAndFlush(new Ownership(newGroup, user)); g = groupService.createGroup(newGroup); } catch (GroupExistsConflictException e) { // we just checked, this shouldn't happen - g = ug.getGroup(); + g = ug; } } - ug.setGroup(g); + ownershipRepository.saveAndFlush(new Ownership(g, user)); }); } - // Cleanup any group changes before saving new state - user.getOldUserGroups().forEach(userGroup -> { - userGroupRepository.delete(userGroup); - }); - user.clearOldUserGroups(); - - return userRepository.save(user); + return userRepository.saveAndFlush(user); } /** diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index 6484ea4ad..bc739ea1b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -23,6 +23,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.util.MDDCConstants; @@ -48,7 +49,7 @@ @Slf4j @Service -public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService, InitializingBean { +public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { @Autowired EntityDescriptorRepository entityDescriptorRepository; @@ -57,31 +58,16 @@ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService, @Autowired private OpenSamlObjects openSamlObjects; + + @Autowired + private OwnershipRepository ownershipRepository; @Autowired UserService userService; - @Override - @Transactional - public void afterPropertiesSet() { - // SHIBUI-1740: Adding admin group to all existing entity descriptors that do not have a group already. - // Because this class has a GroupService, we assume the ADMIN_GROUP has already been setup prior to being - // autowired into this class. - try { - entityDescriptorRepository.findAllByGroupIsNull().forEach(ed -> { - ed.setGroup(Group.ADMIN_GROUP); - entityDescriptorRepository.save(ed); - }); - } - catch (NullPointerException e) { - // This block was added due to a number of mock test where NPEs happened. Rather than wire more mock junk - // into tests that are only trying to compensate for this migration, this is here - } - } - private EntityDescriptor buildDescriptorFromRepresentation(final EntityDescriptor ed, final EntityDescriptorRepresentation representation) { ed.setEntityID(representation.getEntityId()); - ed.setGroup(groupService.find(representation.getGroupId())); + ed.setIdOfOwner(representation.getIdOfOwner()); setupSPSSODescriptor(ed, representation); ed.setServiceProviderName(representation.getServiceProviderName()); @@ -123,7 +109,7 @@ public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation e } EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); - ed.setGroup(userService.getCurrentUserGroup()); + ed.setIdOfOwner(userService.getCurrentUserGroup().getOwnerId()); return createRepresentationFromDescriptor(entityDescriptorRepository.save(ed)); } @@ -141,7 +127,7 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope representation.setVersion(ed.hashCode()); representation.setCreatedBy(ed.getCreatedBy()); representation.setCurrent(ed.isCurrent()); - representation.setGroupId(ed.getGroup().getResourceId()); + representation.setIdOfOwner(ed.getIdOfOwner()); if (ed.getSPSSODescriptor("") != null && ed.getSPSSODescriptor("").getSupportedProtocols().size() > 0) { ServiceProviderSsoDescriptorRepresentation serviceProviderSsoDescriptorRepresentation = representation.getServiceProviderSsoDescriptor(true); @@ -340,11 +326,11 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope @Override public void delete(String resourceId) throws ForbiddenException, EntityNotFoundException { EntityDescriptor ed = getEntityDescriptorByResourceId(resourceId); + // @TODO - need authorization check for group? The controller probably is only allowing admins to delete if (ed.isServiceEnabled()) { throw new ForbiddenException("Deleting an enabled Metadata Source is not allowed. Disable the source and try again."); } - groupService.removeEntityFromGroup(ed); - ed.setGroup(null); + ownershipRepository.deleteEntriesForOwnedObject(ed); entityDescriptorRepository.delete(ed); } @@ -366,8 +352,7 @@ public List getAllRepresentationsBasedOnUserAcce case GROUP: User user = userService.getCurrentUser(); Group group = user.getGroup(); - return entityDescriptorRepository - .findAllStreamByGroup_resourceId(group.getResourceId()) + return entityDescriptorRepository.findAllStreamByIdOfOwner(group.getOwnerId()) .map(ed -> createRepresentationFromDescriptor(ed)).collect(Collectors.toList()); default: throw new ForbiddenException(); @@ -385,7 +370,7 @@ public EntityDescriptor getEntityDescriptorByResourceId(String resourceId) throw if (ed == null) { throw new EntityNotFoundException(String.format("The entity descriptor with entity id [%s] was not found.", resourceId)); } - if (!userService.isAuthorizedFor(ed.getGroup())) { + if (!userService.isAuthorizedFor(ed)) { throw new ForbiddenException(); } return ed; @@ -405,7 +390,7 @@ public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRe if (edRep.isServiceEnabled() && !userService.currentUserIsAdmin()) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); } - if (!userService.isAuthorizedFor(existingEd.getGroup())) { + if (!userService.isAuthorizedFor(existingEd)) { throw new ForbiddenException(); } // Verify we're the only one attempting to update the EntityDescriptor diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index ba73cb0d4..384cb9ce6 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -6,6 +6,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.CustomEntityAttributeDe import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.DefaultAuditorAware import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.CustomEntityAttributesDefinitionServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService @@ -28,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile import org.springframework.core.io.ClassPathResource import org.springframework.data.domain.AuditorAware import org.springframework.mail.javamail.JavaMailSender @@ -122,9 +124,10 @@ class TestConfiguration { } @Bean - GroupServiceImpl groupService(GroupsRepository repo) { + GroupServiceImpl groupServiceImpl(GroupsRepository repo, OwnershipRepository ownershipRepository) { new GroupServiceImpl().with { - it.repo = repo + it.groupRepository = repo + it.ownershipRepository = ownershipRepository return it } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy index e1fd8b225..a04c9ebba 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy @@ -12,11 +12,15 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService @@ -42,7 +46,9 @@ import org.springframework.beans.factory.support.RootBeanDefinition import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Profile import org.springframework.context.support.StaticApplicationContext import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.security.core.Authentication @@ -51,8 +57,10 @@ import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.annotation.DirtiesContext import org.springframework.test.annotation.Rollback +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional import org.springframework.web.client.RestTemplate import org.springframework.web.servlet.config.annotation.EnableWebMvc import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport @@ -73,9 +81,11 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* @DataJpaTest -@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration, LocalConfig]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@DirtiesContext +@ActiveProfiles(["local"]) class EntityDescriptorControllerTests extends Specification { @Autowired EntityDescriptorRepository entityDescriptorRepository @@ -86,6 +96,12 @@ class EntityDescriptorControllerTests extends Specification { @Autowired EntityService entityService + @Autowired + GroupServiceForTesting groupService + + @Autowired + OwnershipRepository ownershipRepository + @Autowired RoleRepository roleRepository @@ -118,9 +134,10 @@ class EntityDescriptorControllerTests extends Specification { Authentication authentication = Mock() SecurityContext securityContext = Mock() EntityDescriptorVersionService versionService = Mock() - IGroupService groupService = Mock() + @Transactional def setup() { + groupService.ensureAdminGroupExists() generator = new TestObjectGenerator() randomGenerator = new RandomGenerator() mapper = new ObjectMapper() @@ -161,7 +178,6 @@ class EntityDescriptorControllerTests extends Specification { Optional userRole = roleRepository.findByName("ROLE_USER") User user = new User(username: "someUser", roles:[userRole.get()], password: "foo") userService.save(user) - entityManager.flush() EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) EntityDescriptorConversionUtils.setEntityService(entityService) @@ -172,7 +188,6 @@ class EntityDescriptorControllerTests extends Specification { def 'DELETE as admin'() { given: authentication.getName() >> 'admin' - groupService.find("admingroup") >> Group.ADMIN_GROUP def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false) entityDescriptorRepository.save(entityDescriptor) @@ -215,10 +230,9 @@ class EntityDescriptorControllerTests extends Specification { def 'GET /EntityDescriptors with 1 record in repository as admin'() { given: authentication.getName() >> 'admin' - groupService.find("admingroup") >> Group.ADMIN_GROUP - def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) - entityDescriptorRepository.save(entityDescriptor) - entityManager.flush() + + def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") + entityDescriptorRepository.saveAndFlush(entityDescriptor) def expectedResponseContentType = APPLICATION_JSON def expectedHttpResponseStatus = status().isOk() @@ -231,7 +245,7 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(jsonPath("\$.[0].id").value("uuid-1")) .andExpect(jsonPath("\$.[0].entityId").value("eid1")) .andExpect(jsonPath("\$.[0].serviceEnabled").value(true)) - .andExpect(jsonPath("\$.[0].groupId").value("admingroup")) + .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) } @Rollback @@ -239,14 +253,13 @@ class EntityDescriptorControllerTests extends Specification { def 'GET /EntityDescriptors with 2 records in repository as admin'() { given: authentication.getName() >> 'admin' - groupService.find("admingroup") >> Group.ADMIN_GROUP + - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) - def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: "admingroup") - entityDescriptorRepository.save(entityDescriptorOne) - entityDescriptorRepository.save(entityDescriptorTwo) - entityManager.flush() + entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) def expectedResponseContentType = APPLICATION_JSON def expectedHttpResponseStatus = status().isOk() @@ -260,11 +273,11 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(jsonPath("\$.[0].id").value("uuid-1")) .andExpect(jsonPath("\$.[0].entityId").value("eid1")) .andExpect(jsonPath("\$.[0].serviceEnabled").value(true)) - .andExpect(jsonPath("\$.[0].groupId").value("admingroup")) + .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) .andExpect(jsonPath("\$.[1].id").value("uuid-2")) .andExpect(jsonPath("\$.[1].entityId").value("eid2")) .andExpect(jsonPath("\$.[1].serviceEnabled").value(false)) - .andExpect(jsonPath("\$.[1].groupId").value("admingroup")) + .andExpect(jsonPath("\$.[1].idOfOwner").value("admingroup")) } @Rollback @@ -272,8 +285,7 @@ class EntityDescriptorControllerTests extends Specification { def 'POST /EntityDescriptor and successfully create new record'() { given: authentication.getName() >> 'admin' - groupService.find("admingroup") >> Group.ADMIN_GROUP - + def expectedEntityId = 'https://shib' def expectedSpName = 'sp1' def expectedResponseHeader = 'Location' @@ -304,7 +316,7 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) .andExpect(jsonPath("\$.entityId").value("https://shib")) .andExpect(jsonPath("\$.serviceEnabled").value(true)) - .andExpect(jsonPath("\$.groupId").value("admingroup")) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) } @Rollback @@ -409,7 +421,7 @@ class EntityDescriptorControllerTests extends Specification { def 'GET /EntityDescriptor/{resourceId} existing'() { given: authentication.getName() >> 'admin' - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") entityDescriptorRepository.save(entityDescriptorOne) entityManager.flush() @@ -421,7 +433,7 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(jsonPath("\$.entityId").value("eid1")) .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) .andExpect(jsonPath("\$.serviceEnabled").value(true)) - .andExpect(jsonPath("\$.groupId").value("admingroup")) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) } @Rollback @@ -431,12 +443,15 @@ class EntityDescriptorControllerTests extends Specification { authentication.getName() >> 'someUser' Group g = userService.getCurrentUserGroup() - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: g) - def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, group: Group.ADMIN_GROUP) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "someUser") + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) + + entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) + + ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) + ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) - entityDescriptorRepository.save(entityDescriptorOne) - entityDescriptorRepository.save(entityDescriptorTwo) - entityManager.flush() when: def result = mockMvc.perform(get("/api/EntityDescriptor/uuid-1")) @@ -446,7 +461,7 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(jsonPath("\$.entityId").value("eid1")) .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) .andExpect(jsonPath("\$.serviceEnabled").value(true)) - .andExpect(jsonPath("\$.groupId").value("someUser")) + .andExpect(jsonPath("\$.idOfOwner").value("someUser")) } @Rollback @@ -456,12 +471,14 @@ class EntityDescriptorControllerTests extends Specification { authentication.getName() >> 'someUser' Group g = userService.getCurrentUserGroup() - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: g) - def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, group: Group.ADMIN_GROUP) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) - entityDescriptorRepository.save(entityDescriptorOne) - entityDescriptorRepository.save(entityDescriptorTwo) - entityManager.flush() + entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) + + ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) + ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) then: try { @@ -502,12 +519,12 @@ class EntityDescriptorControllerTests extends Specification { authentication.getName() >> 'someUser' Group g = userService.getCurrentUserGroup() - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: g) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) entityDescriptorOne.setElementLocalName("EntityDescriptor") entityDescriptorOne.setNamespacePrefix("md") entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") - entityDescriptorRepository.save(entityDescriptorOne) - entityManager.flush() + entityDescriptorOne = entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + ownershipRepository.saveAndFlush(new Ownership(g,entityDescriptorOne)) def expectedXML = """ > 'someUser' Group g = Group.ADMIN_GROUP - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: g) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) entityDescriptorOne.setElementLocalName("EntityDescriptor") entityDescriptorOne.setNamespacePrefix("md") entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") @@ -547,8 +564,7 @@ class EntityDescriptorControllerTests extends Specification { @WithMockUser(value = "admin", roles = ["ADMIN"]) def "POST /EntityDescriptor handles XML happily"() { given: - authentication.getName() >> 'admin' - groupService.find("admingroup") >> Group.ADMIN_GROUP + authentication.getName() >> 'admin' def postedBody = ''' @@ -581,7 +597,7 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) .andExpect(jsonPath("\$.entityId").value("http://test.scaldingspoon.org/test1")) .andExpect(jsonPath("\$.serviceEnabled").value(false)) - .andExpect(jsonPath("\$.groupId").value("admingroup")) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) .andExpect(jsonPath("\$.serviceProviderSsoDescriptor.protocolSupportEnum").value("SAML 2")) .andExpect(jsonPath("\$.serviceProviderSsoDescriptor.nameIdFormats[0]").value("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified")) .andExpect(jsonPath("\$.assertionConsumerServices[0].binding").value("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) @@ -637,7 +653,7 @@ class EntityDescriptorControllerTests extends Specification { given: authentication.getName() >> 'admin' - def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, group: Group.ADMIN_GROUP) + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) entityDescriptorTwo = entityDescriptorRepository.save(entityDescriptorTwo) entityManager.flush() @@ -654,7 +670,7 @@ class EntityDescriptorControllerTests extends Specification { result.andExpect(status().isOk()) .andExpect(jsonPath("\$.entityId").value("eid2")) .andExpect(jsonPath("\$.serviceEnabled").value(false)) - .andExpect(jsonPath("\$.groupId").value("admingroup")) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) .andExpect(jsonPath("\$.serviceProviderName").value("newName")) } @@ -665,7 +681,7 @@ class EntityDescriptorControllerTests extends Specification { authentication.getName() >> 'someUser' Group g = userService.getCurrentUserGroup() - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, group: g) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: g.getOwnerId()) entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) entityManager.flush() @@ -692,7 +708,7 @@ class EntityDescriptorControllerTests extends Specification { authentication.getName() >> 'someUser' Group g = userService.getCurrentUserGroup() - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: g) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) entityManager.flush() @@ -718,7 +734,7 @@ class EntityDescriptorControllerTests extends Specification { given: authentication.getName() >> 'admin' - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, group: Group.ADMIN_GROUP) + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) entityManager.flush() @@ -737,4 +753,28 @@ class EntityDescriptorControllerTests extends Specification { e instanceof ConcurrentModificationException == true } } + + @org.springframework.boot.test.context.TestConfiguration + @Profile(value = "local") + static class LocalConfig { + @Bean + GroupServiceForTesting groupServiceForTesting(GroupsRepository repo, OwnershipRepository ownershipRepository) { + GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { + it.groupRepository = repo + it.ownershipRepository = ownershipRepository + return it + }) + result.ensureAdminGroupExists() + return result + } + } } + +//when: +//def Set ownerships = ownershipRepository.findOwnableObjectOwners(ed) +// +//then: +//ownerships.size() == 1 +//ownerships.each { +// it.ownerId == groupFromDb.resourceId +//} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptorTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptorTest.groovy index bb0a2c4a8..0747b9d4c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptorTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptorTest.groovy @@ -11,6 +11,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSaml import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlFileBackedHTTPMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator @@ -24,8 +25,10 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import spock.lang.Specification @@ -39,6 +42,7 @@ import java.nio.file.Files @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles(value="local") class EntityDescriptorTest extends Specification { RandomGenerator randomGenerator @@ -86,6 +90,7 @@ class EntityDescriptorTest extends Specification { } @TestConfiguration + @Profile("local") static class MyConfig { @Bean MetadataResolver metadataResolver() { @@ -97,9 +102,10 @@ class EntityDescriptorTest extends Specification { } @Bean - GroupServiceImpl groupService(GroupsRepository repo) { + GroupServiceImpl groupService(GroupsRepository repo, OwnershipRepository ownershipRepository) { new GroupServiceImpl().with { - it.repo = repo + it.groupRepository = repo + it.ownershipRepository = ownershipRepository return it } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy index 3fb7a3f21..8f69e2424 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy @@ -7,6 +7,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSaml import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl @@ -24,8 +25,10 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Profile import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import spock.lang.Specification @@ -41,6 +44,7 @@ import javax.persistence.EntityManager @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) +@ActiveProfiles(value = "local") class EntityDescriptorRepositoryTest extends Specification { @Autowired EntityDescriptorRepository entityDescriptorRepository @@ -99,47 +103,45 @@ class EntityDescriptorRepositoryTest extends Specification { noExceptionThrown() } - def "SHIBUI-1849 - extend data model for group ownership"() { + def "SHIBUI-1849 - extend data model for ownership"() { given: def group = new Group().with { it.name = "group-name" it.description = "some description" it } - groupRepository.save(group) - entityManager.flush() - entityManager.clear() + group = groupRepository.saveAndFlush(group) + def gList = groupRepository.findAll() def groupFromDb = gList.get(0).asType(Group) def ed = openSamlObjects.unmarshalFromXml(this.class.getResource('/metadata/SHIBUI-553.2.xml').bytes) as EntityDescriptor ed.with { - it.group = groupFromDb + it.idOfOwner = groupFromDb.resourceId } - entityDescriptorRepository.save(ed) - entityManager.flush() - entityManager.clear() + entityDescriptorRepository.saveAndFlush(ed) when: - def edStreamFromDb = entityDescriptorRepository.findAllStreamByGroup_resourceId(null); + def edStreamFromDb = entityDescriptorRepository.findAllStreamByIdOfOwner(null); then: ((Stream)edStreamFromDb).count() == 0 when: - def edStreamFromDb2 = entityDescriptorRepository.findAllStreamByGroup_resourceId("random value"); + def edStreamFromDb2 = entityDescriptorRepository.findAllStreamByIdOfOwner("random value"); then: ((Stream)edStreamFromDb2).count() == 0 when: - def edStreamFromDb3 = entityDescriptorRepository.findAllStreamByGroup_resourceId(groupFromDb.resourceId); + def edStreamFromDb3 = entityDescriptorRepository.findAllStreamByIdOfOwner(groupFromDb.resourceId); then: ((Stream)edStreamFromDb3).count() == 1 } @TestConfiguration + @Profile("local") static class LocalConfig { @Bean MetadataResolver metadataResolver() { @@ -156,9 +158,10 @@ class EntityDescriptorRepositoryTest extends Specification { } @Bean - GroupServiceImpl groupService(GroupsRepository repo) { + GroupServiceImpl groupService(GroupsRepository repo, OwnershipRepository ownershipRepository) { new GroupServiceImpl().with { - it.repo = repo + it.groupRepository = repo + it.ownershipRepository = ownershipRepository return it } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy index 3c7a7de2f..0f1305c51 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy @@ -65,6 +65,8 @@ class GroupsControllerIntegrationTests extends Specification { def MockMvc mockMvc def setup() { + groupService.ensureAdminGroupExists() + def GroupController groupController = new GroupController().with ({ it.groupService = this.groupService it diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy index fdf583211..f1bd5a591 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy @@ -1,146 +1,221 @@ package edu.internet2.tier.shibboleth.admin.ui.security.controller -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter -import groovy.json.JsonOutput import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Profile +import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.http.MediaType import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.annotation.DirtiesContext import org.springframework.test.annotation.Rollback import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.result.MockMvcResultHandlers -import spock.lang.Ignore -import spock.lang.Specification +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration +import edu.internet2.tier.shibboleth.admin.ui.controller.support.RestControllersSupport +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User -import edu.internet2.tier.shibboleth.admin.ui.security.model.UserGroup -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserGroupRepository -import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService +import groovy.json.JsonOutput +import spock.lang.Specification -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import static org.springframework.http.MediaType.* +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -/** - * @author Dmitriy Kopylenko - */ -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles(["no-auth", "dev"]) +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, TestConfiguration, InternationalizationConfiguration, SearchConfiguration, LocalConfig]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext +@ActiveProfiles(["no-auth", "local"]) +@ComponentScan(basePackages="{ edu.internet2.tier.shibboleth.admin.ui.configuration }") class UsersControllerIntegrationTests extends Specification { @Autowired - IGroupService groupService - - @Autowired - UserGroupRepository ugRepo + GroupsRepository groupsRepository @Autowired - private MockMvc mockMvc - - static RESOURCE_URI = '/api/admin/users' + GroupServiceForTesting groupService + @Autowired def ObjectMapper mapper + @Autowired + OwnershipRepository ownershipRepository + + @Autowired + RoleRepository roleRepository + + @Autowired + UserRepository userRepository + + @Autowired + UserService userService + + def MockMvc mockMvc + def users + + static RESOURCE_URI = '/api/admin/users' + def setup() { - JavaTimeModule module = new JavaTimeModule(); - LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")); - module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer); - mapper = Jackson2ObjectMapperBuilder.json() - .modules(module) - .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .build() + def controller = new UsersController(userRepository, userService) + mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new RestControllersSupport()).build() + createDevUsersAndGroups() } + @Transactional + void createDevUsersAndGroups() { + userRepository.findAll().forEach { + userService.delete(it.getUsername()) + } + userRepository.flush() + + roleRepository.deleteAll() + roleRepository.flush() + groupService.clearAllForTesting() //leaves us just the admingroup + + def groups = [ + new Group().with { + it.name = "A1" + it.description = "AAA Group" + it.resourceId = "AAA" + it + }, + new Group().with { + it.name = "B1" + it.description = "BBB Group" + it.resourceId = "BBB" + it + }] + groups.each { + try { + groupsRepository.save(it) + } catch (Throwable e) { + // Must already exist (from a unit test) + } + } + groupsRepository.flush() + + if (roleRepository.count() == 0) { + def roles = [new Role().with { + name = 'ROLE_ADMIN' + it + }, new Role().with { + name = 'ROLE_USER' + it + }, new Role().with { + name = 'ROLE_NONE' + it + }] + roles.each { + roleRepository.save(it) + } + } + roleRepository.flush() + if (userRepository.count() == 0) { + users = [new User().with { + username = 'admin' + password = '{noop}adminpass' + firstName = 'Joe' + lastName = 'Doe' + emailAddress = 'joe@institution.edu' + roles.add(roleRepository.findByName('ROLE_ADMIN').get()) + it + }, new User().with { + username = 'nonadmin' + password = '{noop}nonadminpass' + firstName = 'Peter' + lastName = 'Vandelay' + emailAddress = 'peter@institution.edu' + roles.add(roleRepository.findByName('ROLE_USER').get()) + it + }, new User().with { + username = 'none' + password = '{noop}nonepass' + firstName = 'Bad' + lastName = 'robot' + emailAddress = 'badboy@institution.edu' + roles.add(roleRepository.findByName('ROLE_NONE').get()) + it + }, new User().with { // allow us to auto-login as an admin + username = 'anonymousUser' + password = '{noop}anonymous' + firstName = 'Anon' + lastName = 'Ymous' + emailAddress = 'anon@institution.edu' + roles.add(roleRepository.findByName('ROLE_ADMIN').get()) + it + }] + users.each { + it = userService.save(it) + } + } + } + @WithMockUser(value = "admin", roles = ["ADMIN"]) def 'GET ALL users (when there are existing users)'() { - given: - // The list of users created by the "dev" configuration - def expectedJson = """ -[ - { - "modifiedBy" : "anonymousUser", - "firstName" : "Joe", - "emailAddress" : "joe@institution.edu", - "role" : "ROLE_ADMIN", - "username" : "admin", - "createdBy" : anonymousUser, - "lastName" : "Doe" - }, - { - "modifiedBy" : "anonymousUser", - "firstName" : "Peter", - "emailAddress" : "peter@institution.edu", - "role" : "ROLE_USER", - "username" : "nonadmin", - "createdBy" : anonymousUser, - "lastName" : "Vandelay" - }, - { - "modifiedBy" : "anonymousUser", - "firstName" : "Bad", - "emailAddress" : "badboy@institution.edu", - "role" : "ROLE_NONE", - "username" : "none", - "createdBy" : "anonymousUser", - "lastName" : "robot" - }, - { - "modifiedBy" : "anonymousUser", - "firstName" : "Anon", - "emailAddress" : "anon@institution.edu", - "role" : "ROLE_ADMIN", - "username" : "anonymousUser", - "createdBy" : "anonymousUser", - "lastName" : "Ymous" - } -]""" + // given: users created in setup + when: 'GET request is made for ALL users in the system, and system has users in it' def result = mockMvc.perform(get(RESOURCE_URI)) - then: 'Request completed with HTTP 200 and returned a list of users' - result - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedJson, false)) + result.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].username").value("admin")) + .andExpect(jsonPath("\$.[0].emailAddress").value("joe@institution.edu")) + .andExpect(jsonPath("\$.[0].role").value("ROLE_ADMIN")) + .andExpect(jsonPath("\$.[0].groupId").value("admingroup")) + .andExpect(jsonPath("\$.[1].username").value("nonadmin")) + .andExpect(jsonPath("\$.[1].emailAddress").value("peter@institution.edu")) + .andExpect(jsonPath("\$.[1].role").value("ROLE_USER")) + .andExpect(jsonPath("\$.[1].groupId").value("nonadmin")) + .andExpect(jsonPath("\$.[2].username").value("none")) + .andExpect(jsonPath("\$.[2].emailAddress").value("badboy@institution.edu")) + .andExpect(jsonPath("\$.[2].role").value("ROLE_NONE")) + .andExpect(jsonPath("\$.[2].groupId").value("none")) + .andExpect(jsonPath("\$.[3].username").value("anonymousUser")) + .andExpect(jsonPath("\$.[3].emailAddress").value("anon@institution.edu")) + .andExpect(jsonPath("\$.[3].role").value("ROLE_ADMIN")) + .andExpect(jsonPath("\$.[3].groupId").value("admingroup")) } @WithMockUser(value = "admin", roles = ["ADMIN"]) def 'GET ONE existing user'() { - given: - def expectedJson = """ -{ - "modifiedBy" : anonymousUser, - "firstName" : "Joe", - "emailAddress" : "joe@institution.edu", - "role" : "ROLE_ADMIN", - "username" : "admin", - "createdBy" : anonymousUser, - "lastName" : "Doe" -}""" when: 'GET request is made for one existing user' def result = mockMvc.perform(get("$RESOURCE_URI/admin")) then: 'Request completed with HTTP 200 and returned one user' result - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedJson, false)) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("\$.username").value("admin")) + .andExpect(jsonPath("\$.emailAddress").value("joe@institution.edu")) + .andExpect(jsonPath("\$.role").value("ROLE_ADMIN")) + .andExpect(jsonPath("\$.groupId").value("admingroup")) } @WithMockUser(value = "admin", roles = ["ADMIN"]) @@ -167,11 +242,14 @@ class UsersControllerIntegrationTests extends Specification { then: 'DELETE was successful' result.andExpect(status().isNoContent()) - when: 'GET request is made for the deleted user' - result = mockMvc.perform(get("$RESOURCE_URI/nonadmin")) - - then: 'The deleted user is gone' - result.andExpect(status().isNotFound()) + // 'GET request is made for the deleted user' + try { + result = mockMvc.perform(get("$RESOURCE_URI/nonadmin")) + false + } + catch (org.springframework.web.util.NestedServletException expectedResult) { + expectedResult.getCause() instanceof org.springframework.web.client.HttpClientErrorException + } } @Rollback @@ -247,7 +325,7 @@ class UsersControllerIntegrationTests extends Specification { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("\$.groupId").value("AAA")) - def List groups = ugRepo.findAllByUser(user) + def groups = ownershipRepository.findAllGroupsForUser(user.username) groups.size() == 1 when: 'Updating user role to admin puts the user in the admin group' @@ -261,7 +339,7 @@ class UsersControllerIntegrationTests extends Specification { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("\$.groupId").value("admingroup")) - def groupsCheck = ugRepo.findAllByUser(user) + def groupsCheck = ownershipRepository.findAllGroupsForUser(user.username) groupsCheck.size() == 1 } @@ -284,4 +362,28 @@ class UsersControllerIntegrationTests extends Specification { then: result.andExpect(status().isNotFound()) } -} + + @org.springframework.boot.test.context.TestConfiguration + @Profile(value = "local") + static class LocalConfig { + @Bean + GroupServiceForTesting groupServiceForTesting(GroupsRepository repo, OwnershipRepository ownershipRepository) { + GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { + it.groupRepository = repo + it.ownershipRepository = ownershipRepository + return it + }) + result.ensureAdminGroupExists() + return result + } + + @Bean + ObjectMapper objectMapper() { + JavaTimeModule module = new JavaTimeModule() + LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")) + module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer) + + return Jackson2ObjectMapperBuilder.json().modules(module).featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build() + } + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy index 805c8d27c..1a23778da 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy @@ -1,15 +1,21 @@ package edu.internet2.tier.shibboleth.admin.ui.security.repository import javax.persistence.EntityManager +import javax.transaction.Transactional 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.context.annotation.Bean import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.annotation.Rollback +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener import spock.lang.Specification /** @@ -17,16 +23,90 @@ import spock.lang.Specification * @author chasegawa */ @DataJpaTest -@ContextConfiguration(classes=[InternationalizationConfiguration]) +@ContextConfiguration(classes=[InternationalizationConfiguration, LocalConfig]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@ActiveProfiles("test") class GroupsRepositoryTests extends Specification { @Autowired - GroupsRepository repo + GroupsRepository groupsRepo @Autowired - EntityManager entityManager + OwnershipRepository ownershipRepository + @Transactional + def setup() { + groupsRepo.deleteAll() + ownershipRepository.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 { + ownershipRepository.save(it) + } + } + + def "group ownership tests"() { + when: "Simple create test" + def group = new Group().with { + it.name = "group 1" + it.description = "some description" + it.resourceId = "g1" + it + } + def Group savedGroup = groupsRepo.saveAndFlush(group) + def Collection all = ownershipRepository.findAllByOwner(savedGroup) + + then: + all.size() == 3 + savedGroup.ownedItems.size() == 3 + all.each { + savedGroup.ownedItems.contains(it) + } + } + + @Rollback def "simple create test"() { given: def group = new Group().with { @@ -37,27 +117,25 @@ class GroupsRepositoryTests extends Specification { // Confirm empty state when: - def groups = repo.findAll() + def groups = groupsRepo.findAll() then: groups.size() == 0 // save check when: - repo.save(group) - entityManager.flush() - entityManager.clear() + group = groupsRepo.save(group) then: // save check - def gList = repo.findAll() + def gList = groupsRepo.findAll() gList.size() == 1 def groupFromDb = gList.get(0).asType(Group) groupFromDb.equals(group) == true // fetch checks - repo.findByResourceId("not an id") == null - repo.findByResourceId(groupFromDb.resourceId).equals(group) + groupsRepo.findByResourceId("not an id") == null + groupsRepo.findByResourceId(groupFromDb.resourceId).equals(group) } def "expected error"() { @@ -69,22 +147,21 @@ class GroupsRepositoryTests extends Specification { // Confirm empty state when: - def gList = repo.findAll() + def gList = groupsRepo.findAll() then: gList.size() == 0 // save check when: - repo.save(group) - entityManager.flush() - entityManager.clear() + def savedGroup = groupsRepo.save(group) then: // Missing non-nullable field (name) should thrown error final def exception = thrown(org.springframework.dao.DataIntegrityViolationException) } - + + @Rollback def "basic CRUD operations validated"() { given: def group = new Group().with { @@ -95,20 +172,18 @@ class GroupsRepositoryTests extends Specification { // Confirm empty state when: - def groups = repo.findAll() + def groups = groupsRepo.findAll() then: groups.size() == 0 // save check when: - repo.save(group) - entityManager.flush() - entityManager.clear() + groupsRepo.save(group) then: // save check - def gList = repo.findAll() + def gList = groupsRepo.findAll() gList.size() == 1 def groupFromDb = gList.get(0).asType(Group) groupFromDb.equals(group) == true @@ -120,12 +195,10 @@ class GroupsRepositoryTests extends Specification { groupFromDb.equals(group) == false when: - repo.save(groupFromDb) - entityManager.flush() - entityManager.clear() + groupsRepo.save(groupFromDb) then: - def gList2 = repo.findAll() + def gList2 = groupsRepo.findAll() gList2.size() == 1 def groupFromDb2 = gList2.get(0).asType(Group) groupFromDb2.equals(group) == false @@ -133,17 +206,25 @@ class GroupsRepositoryTests extends Specification { // delete tests when: - repo.delete(groupFromDb2) - entityManager.flush() - entityManager.clear() - + groupsRepo.delete(groupFromDb2) + then: - repo.findAll().size() == 0 + groupsRepo.findAll().size() == 0 when: - def nothingThere = repo.findByResourceId(null); + def nothingThere = groupsRepo.findByResourceId(null); then: nothingThere == null } + + @org.springframework.boot.test.context.TestConfiguration + static class LocalConfig { + @Bean + GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo) { + GroupUpdatedEntityListener result = new GroupUpdatedEntityListener() + result.init(repo) + return result + } + } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy new file mode 100644 index 000000000..9916ae084 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy @@ -0,0 +1,21 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.service + +import org.springframework.context.annotation.Profile +import org.springframework.transaction.annotation.Transactional + +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl + +@Profile('test') +class GroupServiceForTesting extends GroupServiceImpl { + public GroupServiceForTesting(GroupServiceImpl impl) { + this.groupRepository = impl.groupRepository + this.ownershipRepository = impl.ownershipRepository + } + + @Transactional + public void clearAllForTesting() { + groupRepository.deleteAll(); + ownershipRepository.clearAllOwnedByGroup() + ensureAdminGroupExists() + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy index 91c329056..b6431f79f 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy @@ -1,61 +1,91 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + import javax.persistence.EntityManager 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.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Profile import org.springframework.context.annotation.PropertySource +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.annotation.DirtiesContext import org.springframework.test.annotation.Rollback +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.springframework.transaction.annotation.Transactional +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer + import edu.internet2.tier.shibboleth.admin.ui.ShibbolethUiApplication import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User -import edu.internet2.tier.shibboleth.admin.ui.security.model.UserGroup +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserGroupRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import spock.lang.Specification -@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration]) -@SpringBootTest(classes = ShibbolethUiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) -@PropertySource("classpath:application.yml") +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration, LocalConfig]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext +@ActiveProfiles(["test", "local"]) +@ComponentScan(basePackages="{ edu.internet2.tier.shibboleth.admin.ui.configuration }") class UserServiceTests extends Specification { @Autowired EntityManager entityManager @Autowired - IGroupService groupService + GroupServiceForTesting groupService + @Autowired + OwnershipRepository ownershipRepository + @Autowired RoleRepository roleRepository @Autowired UserRepository userRepository - - @Autowired - UserGroupRepository userGroupRepository @Autowired UserService userService @Transactional def setup() { - // ensure we start fresh with only expected users and roles and groups - userRepository.deleteAll() + userRepository.findAll().forEach { + userService.delete(it.getUsername()) + } + userRepository.flush() + roleRepository.deleteAll() + roleRepository.flush() groupService.clearAllForTesting() //leaves us just the admingroup def roles = [new Role().with { @@ -86,19 +116,21 @@ class UserServiceTests extends Specification { user.setGroup(gb) when: - def result = userService.save(user) + def User result = userService.save(user) then: result.groupId == "testingGroupBBB" result.username == "someUser" result.userGroups.size() == 1 - result.getUserGroups().getAt(0).id.resourceId != null - result.getUserGroups().getAt(0).id.userId != 0 + // Raw check that the DB is correct for ownership + def Set users = ownershipRepository.findUsersByOwner(gb) + users.size() == 1 + users.getAt(0).ownedId == "someUser" + + // Validate that loading the group has the correct list as well Group g = groupService.find("testingGroupBBB"); - g.userGroups.size() == 1 - g.getUserGroups().getAt(0).id.resourceId != null - g.getUserGroups().getAt(0).id.userId != 0 + g.ownedItems.size() == 1 } @Rollback @@ -120,26 +152,58 @@ class UserServiceTests extends Specification { def User userInB = userService.save(user) when: - userInB.setGroupId("testingGroup") // changing groups will happen by updating the user's groupid + userInB.setGroupId("testingGroup") // changing groups will happen by updating the user's groupid (from the ui) def User result = userService.save(userInB) - def List usersGroups = userGroupRepository.findAllByUser(result) - + then: - usersGroups.size() == 1 - usersGroups.get(0).group.getResourceId() == "testingGroup" - result.groupId == "testingGroup" result.username == "someUser" result.userGroups.size() == 1 - result.getUserGroups().getAt(0).id.resourceId != null - result.getUserGroups().getAt(0).id.userId != 0 + // Raw check that the DB is correct for ownership + def Set users = ownershipRepository.findUsersByOwner(ga) + users.size() == 1 + users.getAt(0).ownedId == "someUser" + + // check db is correct for the previous group as well + def Set users2 = ownershipRepository.findUsersByOwner(gb) + users2.size() == 0 + + // Validate that loading the group has the correct list as well Group g = groupService.find("testingGroup"); - g.userGroups.size() == 1 - g.getUserGroups().getAt(0).id.resourceId != null - g.getUserGroups().getAt(0).id.userId != 0 + g.ownedItems.size() == 1 + + Group g2 = groupService.find("testingGroupBBB"); + g2.ownedItems.size() == 0 } + @Rollback + def "logically try to match user controller test causing headaches"() { + given: + Group ga = new Group() + ga.setResourceId("testingGroup") + ga.setName("Group A") + ga = groupService.createGroup(ga) + + Optional userRole = roleRepository.findByName("ROLE_USER") + def User user = new User(username: "someUser", firstName: "Fred", lastName: "Flintstone", roles:[userRole.get()], password: "foo") + user.setGroup(ga) + userService.save(user) + + when: + def User flintstoneUser = userRepository.findByUsername("someUser").get() + flintstoneUser.setFirstName("Wilma") + flintstoneUser.setGroupId("testingGroup") + + def User result = userService.save(flintstoneUser) + + then: + result.groupId == "testingGroup" + result.username == "someUser" + result.userGroups.size() == 1 + result.firstName == "Wilma" + } + @Rollback def "When creating user, user with multiple groups is saved correctly"() { given: @@ -172,6 +236,15 @@ class UserServiceTests extends Specification { then: result.userGroups.size() == 2 + // Raw check that the DB is correct for ownership + def Set users = ownershipRepository.findUsersByOwner(ga) + users.size() == 1 + users.getAt(0).ownedId == "someUser" + + def Set users2 = ownershipRepository.findUsersByOwner(gb) + users2.size() == 1 + users2.getAt(0).ownedId == "someUser" + when: def userFromDb = userRepository.findById(result.id).get(); @@ -182,6 +255,29 @@ class UserServiceTests extends Specification { Group gbUpdated = groupService.find("testingGroupBBB") then: - gbUpdated.userGroups.size() == 1 + gbUpdated.ownedItems.size() == 1 + } + + @org.springframework.boot.test.context.TestConfiguration + @Profile("local") + static class LocalConfig { + @Bean + GroupServiceForTesting groupServiceForTesting(GroupsRepository repo, OwnershipRepository ownershipRepository) { + GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { + it.groupRepository = repo + it.ownershipRepository = ownershipRepository + return it + }) + result.ensureAdminGroupExists() + return result + } + + @Bean + ObjectMapper objectMapper() { + JavaTimeModule module = new JavaTimeModule() + LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")) + module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer) + return Jackson2ObjectMapperBuilder.json().modules(module).featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build() + } } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy index eeae13328..26c7eb5f1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy @@ -40,7 +40,7 @@ class AuxiliaryIntegrationTests extends Specification { it } def entityDescriptor = openSamlObjects.unmarshalFromXml(this.class.getResource('/metadata/SHIBUI-1723-1.xml').bytes) as EntityDescriptor - entityDescriptor.group = group + entityDescriptor.idOfOwner = "foo" def entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor).with { it.serviceProviderName = 'testme' diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy index 4ee9db41e..0cde7c38b 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy @@ -12,6 +12,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidation import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility @@ -24,7 +25,9 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import spock.lang.Specification @@ -35,6 +38,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.util.TestHelpers.* @ContextConfiguration(classes = [CoreShibUiConfiguration, SearchConfiguration, InternationalizationConfiguration, edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration ,LocalConfig]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@ActiveProfiles(value = "local") class IncommonJPAMetadataResolverServiceImplTests extends Specification { @Autowired MetadataResolverService metadataResolverService @@ -106,8 +110,8 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { } } - //TODO: check that this configuration is sufficient @TestConfiguration + @Profile("local") static class LocalConfig { @Autowired OpenSamlObjects openSamlObjects @@ -151,9 +155,10 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { } @Bean - GroupServiceImpl groupService(GroupsRepository repo) { + GroupServiceImpl groupService(GroupsRepository repo, OwnershipRepository ownershipRepository) { new GroupServiceImpl().with { - it.repo = repo + it.groupRepository = repo + it.ownershipRepository = ownershipRepository return it } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy index 371b07409..7ce67fea1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy @@ -2,10 +2,14 @@ package edu.internet2.tier.shibboleth.admin.ui.service import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.context.annotation.PropertySource import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.annotation.DirtiesContext import org.springframework.test.annotation.Rollback +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.springframework.transaction.annotation.Transactional @@ -17,26 +21,31 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRe import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting +import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import spock.lang.Specification -@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration, LocalConfig]) @SpringBootTest(classes = ShibbolethUiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) @PropertySource("classpath:application.yml") @DirtiesContext +@ActiveProfiles(value="local") class JPAEntityDescriptorServiceImplTests2 extends Specification { @Autowired - IGroupService groupService + GroupServiceForTesting groupService @Autowired RoleRepository roleRepository @Autowired - JPAEntityDescriptorServiceImpl service + JPAEntityDescriptorServiceImpl entityDescriptorService @Autowired UserRepository userRepository @@ -100,9 +109,23 @@ class JPAEntityDescriptorServiceImplTests2 extends Specification { def entityDescriptor = new EntityDescriptor(resourceId: expectedUUID, entityID: expectedEntityId, serviceProviderName: expectedSpName, serviceEnabled: false) when: - def result = service.createNew(entityDescriptor) + def result = entityDescriptorService.createNew(entityDescriptor) then: ((EntityDescriptorRepresentation)result).getGroupId() == "testingGroupBBB" } + + @TestConfiguration + @Profile("local") + static class LocalConfig { + @Bean + GroupServiceForTesting groupServiceForTesting(GroupsRepository repo, OwnershipRepository ownershipRepository) { + GroupServiceForTesting result = new GroupServiceForTesting(new GroupServiceImpl().with { + it.groupRepository = repo + it.ownershipRepository = ownershipRepository + return it + }) + return result + } + } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index 713e905b8..e91f3fbab 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -22,6 +22,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSaml import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility @@ -40,9 +41,11 @@ import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.core.io.ClassPathResource import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.test.annotation.DirtiesContext +import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input @@ -57,6 +60,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.util.TestHelpers.generatedX @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@ActiveProfiles(value = "local") class JPAMetadataResolverServiceImplTests extends Specification { @Autowired MetadataResolverRepository metadataResolverRepository @@ -481,6 +485,7 @@ class JPAMetadataResolverServiceImplTests extends Specification { } @TestConfiguration + @Profile("local") static class Config { @Autowired OpenSamlObjects openSamlObjects @@ -513,9 +518,10 @@ class JPAMetadataResolverServiceImplTests extends Specification { } @Bean - GroupServiceImpl groupService(GroupsRepository repo) { + GroupServiceImpl groupService(GroupsRepository repo, OwnershipRepository ownershipRepository) { new GroupServiceImpl().with { - it.repo = repo + it.groupRepository = repo + it.ownershipRepository = ownershipRepository return it } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy index 93857449e..4b4761625 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy @@ -7,6 +7,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan @@ -37,9 +38,13 @@ class UserBootstrapTests extends Specification { @Autowired UserService userService + + @Autowired + IGroupService groupService def setup() { - roleRepository.deleteAll(); + groupService.ensureAdminGroupExists() + roleRepository.deleteAll() } def "simple test"() { From 13dd5293fd22020a0895d8f9a032ab2a2f39378d Mon Sep 17 00:00:00 2001 From: chasegawa Date: Sun, 8 Aug 2021 23:11:55 -0700 Subject: [PATCH 4/8] SHIBUI-2003 fix for launch --- .../tier/shibboleth/admin/ui/service/UserBootstrap.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy index 2635f908c..e007c62c3 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrap.groovy @@ -6,6 +6,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import groovy.util.logging.Slf4j import org.springframework.boot.context.event.ApplicationStartedEvent @@ -22,11 +23,12 @@ class UserBootstrap { private final RoleRepository roleRepository private final UserService userService - UserBootstrap(ShibUIConfiguration shibUIConfiguration, UserRepository userRepository, RoleRepository roleRepository, UserService userService) { + UserBootstrap(ShibUIConfiguration shibUIConfiguration, UserRepository userRepository, RoleRepository roleRepository, UserService userService, IGroupService groupService) { this.shibUIConfiguration = shibUIConfiguration this.userRepository = userRepository this.roleRepository = roleRepository this.userService = userService + groupService.ensureAdminGroupExists() } @Transactional From b5467688d4c73a2d8563becb7e20d17e31262219 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 9 Aug 2021 09:22:14 -0700 Subject: [PATCH 5/8] NOJIRA removed comment --- .../admin/ui/service/JPAEntityDescriptorServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index bc739ea1b..9e0f7117e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -326,7 +326,6 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope @Override public void delete(String resourceId) throws ForbiddenException, EntityNotFoundException { EntityDescriptor ed = getEntityDescriptorByResourceId(resourceId); - // @TODO - need authorization check for group? The controller probably is only allowing admins to delete if (ed.isServiceEnabled()) { throw new ForbiddenException("Deleting an enabled Metadata Source is not allowed. Disable the source and try again."); } From 1d2ec051d78f61706a0e8e99646cdfecbbe0a413 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 9 Aug 2021 10:30:04 -0700 Subject: [PATCH 6/8] SHIBUI-2003 adding missing annotation to listeners --- .../ui/security/model/listener/GroupUpdatedEntityListener.java | 3 +++ .../ui/security/model/listener/UserUpdatedEntityListener.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java index b9dc9c5d0..319624fe9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java @@ -6,6 +6,8 @@ import javax.persistence.PostPersist; import javax.persistence.PostUpdate; +import org.springframework.beans.factory.annotation.Autowired; + import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; @@ -16,6 +18,7 @@ public class GroupUpdatedEntityListener implements ILazyLoaderHelper { /** * @see https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener */ + @Autowired public void init(OwnershipRepository repo) { GroupUpdatedEntityListener.ownershipRepository = repo; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java index 96826f254..dc2291db9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/UserUpdatedEntityListener.java @@ -7,6 +7,7 @@ import javax.persistence.PostPersist; import javax.persistence.PostUpdate; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -23,6 +24,7 @@ public class UserUpdatedEntityListener implements ILazyLoaderHelper { /** * @see https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener */ + @Autowired public void init(OwnershipRepository repo, GroupsRepository groupRepo) { UserUpdatedEntityListener.ownershipRepository = repo; UserUpdatedEntityListener.groupRepository = groupRepo; From 7d05a78e7fae717fbb4b88daefb2db3017b0a762 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 9 Aug 2021 13:51:18 -0700 Subject: [PATCH 7/8] Updated metadata sources group id --- ui/src/app/dashboard/view/SourcesTab.js | 2 +- ui/src/app/metadata/component/MetadataHeader.js | 4 ++-- ui/src/app/metadata/domain/source/component/SourceList.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/app/dashboard/view/SourcesTab.js b/ui/src/app/dashboard/view/SourcesTab.js index dd0925876..d60218f08 100644 --- a/ui/src/app/dashboard/view/SourcesTab.js +++ b/ui/src/app/dashboard/view/SourcesTab.js @@ -34,7 +34,7 @@ export function SourcesTab () { async function changeSourceGroup(source, group) { await updater.put(`/${source.id}`, { ...source, - groupId: group + idOfOwner: group }); if (updater.response.ok) { loadSources(); diff --git a/ui/src/app/metadata/component/MetadataHeader.js b/ui/src/app/metadata/component/MetadataHeader.js index 83ef7cee1..4c9a04326 100644 --- a/ui/src/app/metadata/component/MetadataHeader.js +++ b/ui/src/app/metadata/component/MetadataHeader.js @@ -24,7 +24,7 @@ export function MetadataHeader ({ showGroup, model, current = true, enabled = tr let toast; const resp = await put(`/${s.id}`, { ...s, - groupId: group + idOfOwner: group }); if (response.ok) { toast = createNotificationAction(`Updated group successfully.`, NotificationTypes.SUCCESS); @@ -64,7 +64,7 @@ export function MetadataHeader ({ showGroup, model, current = true, enabled = tr name={`group-${model.id}`} className="form-control form-control-sm" onChange={(event) => changeSourceGroup(model, event.target.value)} - value={model.groupId} + value={model.idOfOwner} disabled={loadingGroups} disablevalidation="true"> diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js index 9888e0cea..bead9da9b 100644 --- a/ui/src/app/metadata/domain/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -79,7 +79,7 @@ export default function SourceList({ entities, onDelete, onEnable, onChangeGroup name={`group-${source.id}`} className="form-control" onChange={(event) => onChangeGroup(source, event.target.value)} - value={source.groupId ? source.groupId : ''} + value={source.idOfOwner ? source.idOfOwner : ''} disabled={loadingGroups} disablevalidation="true"> From 26a9cbc79894ed57c6b74189836aa7da69bdf73c Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 9 Aug 2021 15:30:07 -0700 Subject: [PATCH 8/8] SHIBUI-2003 Cleaning up groupid from entity descriptor representation after UI adjustments --- .../tier/shibboleth/admin/ui/domain/EntityDescriptor.java | 1 - .../ui/domain/frontend/EntityDescriptorRepresentation.java | 5 ----- .../ui/service/JPAEntityDescriptorServiceImplTests2.groovy | 2 +- .../shibboleth/admin/ui/service/UserBootstrapTests.groovy | 4 ++-- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java index c090db618..a3acff06b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java @@ -4,7 +4,6 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index 7062c9ec7..821d9817d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import lombok.Getter; import lombok.Setter; @@ -109,10 +108,6 @@ public String getEntityId() { public String getId() { return id; } - - public String getGroupId() { - return idOfOwner == null ? Group.ADMIN_GROUP.getResourceId() : idOfOwner; - } public List getLogoutEndpoints() { return this.getLogoutEndpoints(false); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy index 7ce67fea1..13847472c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests2.groovy @@ -112,7 +112,7 @@ class JPAEntityDescriptorServiceImplTests2 extends Specification { def result = entityDescriptorService.createNew(entityDescriptor) then: - ((EntityDescriptorRepresentation)result).getGroupId() == "testingGroupBBB" + ((EntityDescriptorRepresentation)result).getIdOfOwner() == "testingGroupBBB" } @TestConfiguration diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy index 4b4761625..95eb8d09f 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/UserBootstrapTests.groovy @@ -51,7 +51,7 @@ class UserBootstrapTests extends Specification { setup: shibUIConfiguration.roles = [] shibUIConfiguration.userBootstrapResource = new ClassPathResource('/conf/1044.csv') - def userBootstrap = new UserBootstrap(shibUIConfiguration, userRepository, roleRepository, userService) + def userBootstrap = new UserBootstrap(shibUIConfiguration, userRepository, roleRepository, userService, groupService) when: userBootstrap.bootstrapUsersAndRoles(null) @@ -65,7 +65,7 @@ class UserBootstrapTests extends Specification { def "bootstrap roles"() { setup: shibUIConfiguration.roles = ['ROLE_ADMIN', 'ROLE_USER'] - def userbootstrap = new UserBootstrap(shibUIConfiguration, userRepository, roleRepository, userService) + def userbootstrap = new UserBootstrap(shibUIConfiguration, userRepository, roleRepository, userService, groupService) when: userbootstrap.bootstrapUsersAndRoles(null)