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 607615adc..aa0c80d55 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 @@ -12,6 +12,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener; import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; 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; @@ -21,12 +22,14 @@ import edu.internet2.tier.shibboleth.admin.ui.service.DefaultMetadataResolversPositionOrderContainerService; import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryService; import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryServiceImpl; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.EntityService; import edu.internet2.tier.shibboleth.admin.ui.service.FileCheckingFileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterTargetService; +import edu.internet2.tier.shibboleth.admin.ui.service.JPADynamicRegistrationServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.JPAFilterTargetServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; @@ -234,8 +237,13 @@ public UserUpdatedEntityListener userUpdatedEntityListener(OwnershipRepository r } @Bean - public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService, DynamicRegistrationInfoRepository driRepo) { // TODO: @jj define type to return for Grouper integration - return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + return new ShibUiPermissionDelegate(driRepo, entityDescriptorRepository, userService); + } + + @Bean + public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService) { + return new JPADynamicRegistrationServiceImpl(driRepo, ownershipRepo, permissionEvaluator, userService); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java new file mode 100644 index 000000000..0b0f71c9c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java @@ -0,0 +1,51 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.InvalidPatternMatchException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; + +@RestController +@RequestMapping("/api") +@Tags(value = {@Tag(name = "oidc")}) +public class DynamicRegistrationController { + @Autowired + DynamicRegistrationService dynamicRegistrationService; + + @PostMapping("/DynamicRegistration") + @Transactional + public ResponseEntity create(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation) throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { + DynamicRegistrationRepresentation persisted = dynamicRegistrationService.createNew(dynRegRepresentation); + return ResponseEntity.created(getResourceUriFor(persisted.getResourceId())).body(persisted); + } + + @GetMapping(value = "/DynamicRegistrations", produces = "application/json") + @Transactional(readOnly = true) + public ResponseEntity getAll() throws ForbiddenException { + return ResponseEntity.ok(dynamicRegistrationService.getAllDynamicRegistrationsBasedOnUserAccess()); + } + + private static URI getResourceUriFor(String resourceId) { + return ServletUriComponentsBuilder + .fromCurrentServletMapping().path("/api/DynamicRegistration") + .pathSegment(resourceId) + .build() + .toUri(); + } + + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java index 8042d56d4..e3a9a7387 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; public enum ActivatableType { - ENTITY_DESCRIPTOR, METADATA_RESOLVER, FILTER + ENTITY_DESCRIPTOR, METADATA_RESOLVER, FILTER, DYNAMIC_REGISTRATION } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java index f2e163b0b..541d86fa5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java @@ -2,4 +2,6 @@ public interface IApprovable { String getIdOfOwner(); + + void removeLastApproval(); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java new file mode 100644 index 000000000..ab52bb975 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java @@ -0,0 +1,90 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.frontend; + +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@Getter +@Setter +public class DynamicRegistrationRepresentation { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); + + private String applicationType; + private boolean approved; + private String contacts; + private LocalDateTime createdDate; + private boolean enabled; + private GrantType grantType; + private String idOfOwner; + private String jwks; + private String logoUri; + private LocalDateTime modifiedDate; + private String policyUri; + private String redirectUris; + private String resourceId; + private String responseTypes; + private String scope; + private String subjectType; + private String tokenEndpointAuthMethod; + private String tosUri; + private int version; + + public DynamicRegistrationRepresentation(DynamicRegistrationInfo dri) { + applicationType = dri.getApplicationType(); + approved = dri.isApproved(); + contacts = dri.getContacts(); + createdDate = dri.getCreatedDate(); + enabled = dri.isEnabled(); + grantType = dri.getGrantType(); + idOfOwner = dri.getIdOfOwner(); + jwks = dri.getJwks(); + logoUri = dri.getLogoUri(); + modifiedDate = dri.getModifiedDate(); + policyUri = dri.getPolicyUri(); + redirectUris = dri.getRedirectUris(); + resourceId = dri.getResourceId(); + responseTypes = dri.getResponseTypes(); + scope = dri.getScope(); + subjectType = dri.getSubjectType(); + tokenEndpointAuthMethod = dri.getTokenEndpointAuthMethod(); + tosUri = dri.getTosUri(); + version = dri.hashCode(); + } + + public DynamicRegistrationInfo buildDynamicRegistrationInfo() { + // Approved and enabled shouldn't be handled from here, and owner shouldn't come from the UI, so we ignore all those + + DynamicRegistrationInfo dri = new DynamicRegistrationInfo(); + dri.setApplicationType(applicationType); +// dri.setApproved(approved); + dri.setContacts(contacts); +// dri.setEnabled(enabled); + dri.setGrantType(grantType); +// dri.setIdOfOwner(idOfOwner); + dri.setJwks(jwks); + dri.setLogoUri(logoUri); + dri.setPolicyUri(policyUri); + dri.setRedirectUris(redirectUris); + dri.setResourceId(resourceId); + dri.setResponseTypes(responseTypes); + dri.setScope(scope); + dri.setSubjectType(subjectType); + dri.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); + dri.setTosUri(tosUri); + return dri; + } + + public String getCreatedDate() { + return createdDate != null ? DATE_TIME_FORMATTER.format(createdDate) : null; + } + + public String getModifiedDate() { + return modifiedDate != null ? DATE_TIME_FORMATTER.format(modifiedDate) : null; + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java new file mode 100644 index 000000000..87b10e90c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java @@ -0,0 +1,78 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.oidc; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType; +import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; +import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Data +@Audited +public class DynamicRegistrationInfo extends AbstractAuditable implements Ownable, IActivatable, IApprovable { + private String applicationType; + private boolean approved; + private String contacts; + private boolean enabled; + private GrantType grantType; + private String idOfOwner; + private String jwks; + private String logoUri; + private String policyUri; + private String redirectUris; + private String resourceId; + private String responseTypes; + private String scope; + private String subjectType; + private String tokenEndpointAuthMethod; + private String tosUri; + + @ElementCollection(fetch = FetchType.EAGER) + @EqualsAndHashCode.Exclude + private List approvedBy = new ArrayList<>(); + + @Override + public ActivatableType getActivatableType() { + return ActivatableType.DYNAMIC_REGISTRATION; + } + + @Override + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getObjectId() { + return getResourceId(); + } + + public String getResourceId() { + if (resourceId == null) { + resourceId = UUID.randomUUID().toString(); + } + return resourceId; + } + + @Override + public OwnableType getOwnableType() { + return OwnableType.DYNAMIC_REGISTRATION; + } + + @Override + public void removeLastApproval() { + if (!approvedBy.isEmpty()) { + approvedBy.remove(approvedBy.size() - 1); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java new file mode 100644 index 000000000..65e28c7fe --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.oidc; + +public enum GrantType { + authorization_code, implicit, refresh_token +} \ 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 index 0cf82714c..ed1fdb8bf 100644 --- 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 @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; public enum OwnableType { - USER, ENTITY_DESCRIPTOR, METADATA_PROVIDER -} + USER, ENTITY_DESCRIPTOR, METADATA_PROVIDER, DYNAMIC_REGISTRATION +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java index 5db069c5c..0cd22d7a1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; public enum ShibUiPermissibleType { - entityDescriptorProjection // represents EntityDescriptorProjections + entityDescriptorProjection, // represents EntityDescriptorProjections + dynamicRegistrationInfo } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java index 0560b569b..8fd1efaaf 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java @@ -1,13 +1,16 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; +import edu.internet2.tier.shibboleth.admin.ui.controller.DynamicRegistrationController; import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserAccess; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import lombok.AllArgsConstructor; @@ -27,6 +30,8 @@ */ @AllArgsConstructor public class ShibUiPermissionDelegate implements IShibUiPermissionEvaluator { + private DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository; + private EntityDescriptorRepository entityDescriptorRepository; private UserService userService; @@ -46,14 +51,30 @@ public Collection getPersistentEntities(Authentication ignored, ShibUiPermissibl return entityDescriptorRepository.getEntityDescriptorsNeedingEnabling(); case fetch: if (!hasPermission(ignored, null, PermissionType.fetch)) { - throw new ForbiddenException("User has no access rights to get a list of Metadata Sources"); + throw new ForbiddenException("User has no access rights to get a list of : " + shibUiType); } return getAllEntityDescriptorProjectionsBasedOnUserAccess(); } + case dynamicRegistrationInfo: + switch (permissionType) { + case fetch: + if (!hasPermission(ignored, null, PermissionType.fetch)) { + throw new ForbiddenException("User has no access rights to get a list of : " + shibUiType); + } + return getAllDynamicRegistrationInfoObjectsBasedOnUserAccess(); + } } return null; } + private List getAllDynamicRegistrationInfoObjectsBasedOnUserAccess() { + if (userService.currentUserIsAdmin()) { + return dynamicRegistrationInfoRepository.findAll(); + } else { + return dynamicRegistrationInfoRepository.findAllByIdOfOwner(userService.getCurrentUser().getGroup().getOwnerId()); + } + } + private List getAllEntityDescriptorProjectionsBasedOnUserAccess() { if (userService.currentUserIsAdmin()) { return entityDescriptorRepository.findAllReturnProjections(); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java new file mode 100644 index 000000000..fcef188a9 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java @@ -0,0 +1,12 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface DynamicRegistrationInfoRepository extends JpaRepository { + List findAllByIdOfOwner(String idOfOwner); + + DynamicRegistrationInfo findByResourceId(String id); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java new file mode 100644 index 000000000..4eb86e16f --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java @@ -0,0 +1,11 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; + +public interface DynamicRegistrationService { + Object getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException; + + DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException; +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java new file mode 100644 index 000000000..d36d92647 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java @@ -0,0 +1,71 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.PermissionType; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissibleType; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@AllArgsConstructor +@NoArgsConstructor +public class JPADynamicRegistrationServiceImpl implements DynamicRegistrationService { + @Autowired + DynamicRegistrationInfoRepository repository; + + @Autowired + OwnershipRepository ownershipRepository; + + @Autowired + private IShibUiPermissionEvaluator shibUiAuthorizationDelegate; + + @Autowired + UserService userService; + + @Override + public DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException { + if (entityExists(dynRegRepresentation.getResourceId())) { + throw new ObjectIdExistsException(dynRegRepresentation.getResourceId()); + } + + DynamicRegistrationInfo dri = dynRegRepresentation.buildDynamicRegistrationInfo(); + dri.setEnabled(false); // cannot create as enabled + + // "Create new" will use the current user's group as the owner + String ownerId = userService.getCurrentUserGroup().getOwnerId(); + dri.setIdOfOwner(ownerId); + + if (shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin) || + userService.getCurrentUserGroup().getApproversList().isEmpty()) { + dri.setApproved(true); + } + + ownershipRepository.deleteEntriesForOwnedObject(dri); + ownershipRepository.save(new Ownership(userService.getCurrentUserGroup(), dri)); + + return new DynamicRegistrationRepresentation(repository.save(dri)); + } + + private boolean entityExists(String id) { + return repository.findByResourceId(id) != null ; + } + + @Override + public List getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException { + return (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.dynamicRegistrationInfo, PermissionType.fetch); + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy index 9c5b88df1..97e826003 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy @@ -14,6 +14,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdat import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository 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.GroupServiceForTesting @@ -110,7 +111,7 @@ class BaseDataJpaTestConfiguration { } @Bean - public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { - return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(DynamicRegistrationInfoRepository driRepo, EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + return new ShibUiPermissionDelegate(driRepo, entityDescriptorRepository, userService); } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy new file mode 100644 index 000000000..2409da087 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy @@ -0,0 +1,773 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +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.service.DynamicRegistrationService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityService +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import spock.lang.Subject + +import javax.persistence.EntityManager + +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class DynamicRegistrationControllerTests extends AbstractBaseDataJpaTest { + @Autowired + DynamicRegistrationService dynamicRegistrationService; + + @Autowired + EntityManager entityManager + + @Autowired + EntityService entityService + + @Autowired + ObjectMapper mapper + + def mockMvc + + @Subject + def controller + + @Transactional + def setup() { + Group gb = new Group() + gb.setResourceId("testingGroupBBB") + gb.setName("Group BBB") + gb.setValidationRegex("^(?:https?:\\/\\/)?(?:[^.]+\\.)?shib\\.org(\\/.*)?\$") + gb = groupService.createGroup(gb) + + controller = new DynamicRegistrationController() + controller.dynamicRegistrationService = dynamicRegistrationService + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + Optional userRole = roleRepository.findByName("ROLE_USER") + User user = new User(username: "someUser", roles:[userRole.get()], password: "foo") + user.setGroup(gb) + userService.save(user) + } + +// @WithMockAdmin +// def 'DELETE as admin'() { +// given: +// def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false) +// entityDescriptorRepository.save(entityDescriptor) +// +// when: 'pre-check' +// entityManager.flush() +// +// then: +// entityDescriptorRepository.findAll().size() == 1 +// +// when: +// def result = mockMvc.perform(delete("/api/EntityDescriptor/uuid-1")) +// +// then: +// result.andExpect(status().isNoContent()) +// entityDescriptorRepository.findByResourceId("uuid-1") == null +// entityDescriptorRepository.findAll().size() == 0 +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'DELETE as non-admin'() { +// given: +// def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false) +// entityDescriptorRepository.save(entityDescriptor) +// +// when: 'pre-check' +// entityManager.flush() +// +// then: +// entityDescriptorRepository.findAll().size() == 1 +// try { +// result = mockMvc.perform(delete("/api/EntityDescriptor/uuid-1")) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// + @WithMockAdmin + def 'GET /DynamicRegistrations with empty repository as admin'() { + when: + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)).andExpect(content().json('[]')) + } +// +// @WithMockAdmin +// def 'GET /EntityDescriptors with 1 record in repository as admin'() { +// given: +// 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() +// +// when: +// def result = mockMvc.perform(get('/api/EntityDescriptors')) +// +// then: +// result.andDo(MockMvcResultHandlers.print()) +// .andExpect(expectedHttpResponseStatus).andExpect(content().contentType(expectedResponseContentType)) +// .andExpect(jsonPath("\$.[0].id").value("uuid-1")) +// .andExpect(jsonPath("\$.[0].entityId").value("eid1")) +// .andExpect(jsonPath("\$.[0].serviceEnabled").value(true)) +// .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.[0].protocol").value("SAML")) +// } +// +// @WithMockAdmin +// def 'GET /EntityDescriptors with 2 records in repository as admin'() { +// given: +// 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.saveAndFlush(entityDescriptorOne) +// entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) +// +// def expectedResponseContentType = APPLICATION_JSON +// def expectedHttpResponseStatus = status().isOk() +// +// when: +// def result = mockMvc.perform(get('/api/EntityDescriptors')) +// +// then: +// result.andExpect(expectedHttpResponseStatus) +// .andExpect(content().contentType(expectedResponseContentType)) +// .andExpect(jsonPath("\$.[0].id").value("uuid-1")) +// .andExpect(jsonPath("\$.[0].entityId").value("eid1")) +// .andExpect(jsonPath("\$.[0].serviceEnabled").value(true)) +// .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.[0].protocol").value("SAML")) +// .andExpect(jsonPath("\$.[1].id").value("uuid-2")) +// .andExpect(jsonPath("\$.[1].entityId").value("eid2")) +// .andExpect(jsonPath("\$.[1].serviceEnabled").value(false)) +// .andExpect(jsonPath("\$.[1].idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.[1].protocol").value("SAML")) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'POST create new - entity id does not match pattern'() { +// when: +// def expectedEntityId = 'https://google.com/blah/blah' +// EntityDescriptorRepresentation edRep = new EntityDescriptorRepresentation() +// edRep.setEntityId(expectedEntityId) +// edRep.setServiceProviderName("spName") +// +// def edRepJson = mapper.writeValueAsString(edRep) +// +// then: +// try { +// mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(edRepJson)) +// false +// } catch (NestedServletException expected) { +// expected.getCause() instanceof InvalidPatternMatchException +// } +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'POST create new - verifying validation on entityID and ACS locations'() { +// given: +// def expectedEntityId = 'https://shib.org/blah/blah' +// EntityDescriptorRepresentation edRep = new EntityDescriptorRepresentation() +// edRep.setEntityId(expectedEntityId) +// edRep.setServiceProviderName("spName") +// +// def acsList = new ArrayList() +// AssertionConsumerServiceRepresentation acsRep = new AssertionConsumerServiceRepresentation() +// acsRep.setIndex(0) +// acsRep.setLocationUrl("http://logout.shib.org/dologout") +// acsList.add(acsRep) +// edRep.setAssertionConsumerServices(acsList) +// +// def edRepJson = mapper.writeValueAsString(edRep) +// +// when: +// def result = mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(edRepJson)) +// +// then: +// result.andExpect(status().isCreated()) +// .andExpect(content().contentType(APPLICATION_JSON)) +// .andExpect(jsonPath("\$.entityId").value("https://shib.org/blah/blah")) +// .andExpect(jsonPath("\$.serviceEnabled").value(false)) +// .andExpect(jsonPath("\$.idOfOwner").value("testingGroupBBB")) +// +// when: "ACS url is bad" +// expectedEntityId = 'https://shib.org/blah/blah/again' +// edRep = new EntityDescriptorRepresentation() +// edRep.setEntityId(expectedEntityId) +// edRep.setServiceProviderName("spName") +// +// acsList = new ArrayList() +// acsRep = new AssertionConsumerServiceRepresentation() +// acsRep.setIndex(0) +// acsRep.setLocationUrl("http://shib.com/dologout") +// acsList.add(acsRep) +// edRep.setAssertionConsumerServices(acsList) +// edRepJson = mapper.writeValueAsString(edRep) +// +// then: +// try { +// mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(edRepJson)) +// false +// } catch (NestedServletException expected) { +// expected.getCause() instanceof InvalidPatternMatchException +// } +// } +// +// @WithMockAdmin +// def 'POST /EntityDescriptor and successfully create new record'() { +// given: +// def expectedEntityId = 'https://shib' +// def expectedSpName = 'sp1' +// def expectedResponseHeader = 'Location' +// def expectedResponseHeaderValue = "/api/EntityDescriptor/" +// +// def postedJsonBody = """ +// { +// "serviceProviderName": "$expectedSpName", +// "entityId": "$expectedEntityId", +// "organization": {}, +// "serviceEnabled": true, +// "createdDate": null, +// "modifiedDate": null, +// "contacts": null, +// "serviceProviderSsoDescriptor": null, +// "logoutEndpoints": null, +// "securityInfo": null, +// "assertionConsumerServices": null, +// "current": false +// } +// """ +// +// when: +// def result = mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(postedJsonBody)) +// +// then: +// result.andExpect(status().isCreated()) +// .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) +// .andExpect(jsonPath("\$.entityId").value("https://shib")) +// .andExpect(jsonPath("\$.serviceEnabled").value(true)) +// .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'POST /EntityDescriptor as user disallows enabling'() { +// given: +// def expectedEntityId = 'https://shib' +// def expectedSpName = 'sp1' +// +// when: +// def postedJsonBody = """ +// { +// "serviceProviderName": "$expectedSpName", +// "entityId": "$expectedEntityId", +// "organization": null, +// "serviceEnabled": true, +// "createdDate": null, +// "modifiedDate": null, +// "organization": null, +// "contacts": null, +// "mdui": null, +// "serviceProviderSsoDescriptor": null, +// "logoutEndpoints": null, +// "securityInfo": null, +// "assertionConsumerServices": null, +// "relyingPartyOverrides": null, +// "attributeRelease": null +// } +// """ +// +// then: +// try { +// mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(postedJsonBody)) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// +// @WithMockAdmin +// def 'POST /EntityDescriptor record already exists'() { +// given: +// def postedJsonBody = """ +// { +// "serviceProviderName": "sp1", +// "entityId": "eid1", +// "organization": null, +// "serviceEnabled": true, +// "createdDate": null, +// "modifiedDate": null, +// "organization": null, +// "contacts": null, +// "mdui": null, +// "serviceProviderSsoDescriptor": null, +// "logoutEndpoints": null, +// "securityInfo": null, +// "assertionConsumerServices": null, +// "relyingPartyOverrides": null, +// "attributeRelease": null +// } +// """ +// +// when: +// 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) +// +// entityDescriptorRepository.save(entityDescriptorOne) +// entityDescriptorRepository.save(entityDescriptorTwo) +// entityManager.flush() +// +// then: +// try { +// mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(postedJsonBody)) +// } +// catch (Exception e) { +// e instanceof ObjectIdExistsException +// } +// } +// +// @WithMockAdmin +// def 'GET /EntityDescriptor/{resourceId} non-existent'() { +// expect: +// try { +// mockMvc.perform(get("/api/EntityDescriptor/uuid-1")) +// } +// catch (Exception e) { +// e instanceof PersistentEntityNotFound +// } +// } +// +// @WithMockAdmin +// def 'GET /EntityDescriptor/{resourceId} existing'() { +// given: +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") +// entityDescriptorRepository.save(entityDescriptorOne) +// entityManager.flush() +// +// when: +// def result = mockMvc.perform(get("/api/EntityDescriptor/uuid-1")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.entityId").value("eid1")) +// .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) +// .andExpect(jsonPath("\$.serviceEnabled").value(true)) +// .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'GET /EntityDescriptor/{resourceId} existing, validate group access'() { +// given: +// Group g = userService.getCurrentUserGroup() +// +// 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)) +// +// when: +// def result = mockMvc.perform(get("/api/EntityDescriptor/uuid-1")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.entityId").value("eid1")) +// .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) +// .andExpect(jsonPath("\$.serviceEnabled").value(true)) +// .andExpect(jsonPath("\$.idOfOwner").value("someUser")) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'GET /EntityDescriptor/{resourceId} existing, owned by some other user'() { +// when: +// Group g = userService.getCurrentUserGroup() +// +// 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.saveAndFlush(entityDescriptorOne) +// entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) +// +// ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) +// ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) +// +// then: +// try { +// mockMvc.perform(get("/api/EntityDescriptor/uuid-2")) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// +// @WithMockAdmin +// def 'GET /EntityDescriptor/{resourceId} existing (xml)'() { +// given: +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) +// entityDescriptorOne.setElementLocalName("EntityDescriptor") +// entityDescriptorOne.setNamespacePrefix("md") +// entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") +// entityDescriptorRepository.save(entityDescriptorOne) +// entityManager.flush() +// +// def expectedXML = """ +//""" +// +// when: +// def result = mockMvc.perform(get("/api/EntityDescriptor/uuid-1").accept(APPLICATION_XML)) +// +// then: +// result.andExpect(status().isOk()).andExpect(content().xml(expectedXML)) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'GET /EntityDescriptor/{resourceId} existing (xml), user-owned'() { +// given: +// Group g = userService.getCurrentUserGroup() +// +// 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") +// entityDescriptorOne = entityDescriptorRepository.saveAndFlush(entityDescriptorOne) +// ownershipRepository.saveAndFlush(new Ownership(g,entityDescriptorOne)) +// +// def expectedXML = """ +//""" +// +// when: +// def result = mockMvc.perform(get("/api/EntityDescriptor/uuid-1").accept(APPLICATION_XML)) +// +// then: +// result.andExpect(status().isOk()).andExpect(content().xml(expectedXML)) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def 'GET /EntityDescriptor/{resourceId} existing (xml), other user-owned'() { +// when: +// Group g = Group.ADMIN_GROUP +// +// 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() +// +// then: +// try { +// mockMvc.perform(get("/api/EntityDescriptor/$providedResourceId").accept(APPLICATION_XML)) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// +// @WithMockAdmin +// def "POST /EntityDescriptor handles XML happily"() { +// given: +// def postedBody = ''' +// +// +// +// +// internal +// +// +// givenName +// employeeNumber +// +// +// +// +// urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +// +// +// Shrink Space +// Shrink Space Authenticator +// +// +// +// +//''' +// def expectedResponseHeader = 'Location' +// def expectedResponseHeaderValue = "/api/EntityDescriptor/" +// +// when: +// def spName = randomGenerator.randomString() +// def result = mockMvc.perform(post("/api/EntityDescriptor").contentType(APPLICATION_XML).content(postedBody).param("spName", spName)) +// +// then: +// result.andExpect(status().isCreated()) +// .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) +// .andExpect(jsonPath("\$.entityId").value("http://test.scaldingspoon.org/test1")) +// .andExpect(jsonPath("\$.serviceEnabled").value(false)) +// .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")) +// .andExpect(jsonPath("\$.assertionConsumerServices[0].makeDefault").value(false)) +// .andExpect(jsonPath("\$.assertionConsumerServices[0].locationUrl").value("https://test.scaldingspoon.org/test1/acs")) +// +// try { +// mockMvc.perform(post("/api/EntityDescriptor").contentType(APPLICATION_XML).content(postedBody).param("spName", spName)) +// } +// catch (Exception e) { +// e instanceof ObjectIdExistsException +// } +// } +// +// @WithMockAdmin +// def "POST /EntityDescriptor returns error for duplicate entity id"() { +// when: +// def postedBody = ''' +// +// +// +// +// internal +// +// +// givenName +// employeeNumber +// +// +// +// +// urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +// +// +// +//''' +// def spName = randomGenerator.randomString() +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) +// def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'http://test.scaldingspoon.org/test1', serviceProviderName: 'sp2', serviceEnabled: false) +// +// entityDescriptorRepository.save(entityDescriptorOne) +// entityDescriptorRepository.save(entityDescriptorTwo) +// entityManager.flush() +// +// then: +// try { +// mockMvc.perform(post("/api/EntityDescriptor").contentType(APPLICATION_XML).content(postedBody).param("spName", spName)) +// } +// catch (Exception e) { +// e instanceof ObjectIdExistsException +// } +// } +// +// @WithMockAdmin +// def "PUT /EntityDescriptor updates entity descriptors properly as admin"() { +// given: +// def entityDescriptorToSave = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) +// +// entityDescriptorRepository.save(entityDescriptorToSave) +// entityManager.flush() +// entityManager.clear() +// +// def entityDescriptorTwo = entityDescriptorRepository.findByResourceId('uuid-2') +// +// def updatedEntityDescriptorRepresentation = jpaEntityDescriptorService.createRepresentationFromDescriptor(entityDescriptorTwo) +// updatedEntityDescriptorRepresentation.setServiceProviderName("newName") +// def postedJsonBody = mapper.writeValueAsString(updatedEntityDescriptorRepresentation) +// +// when: +// def result = mockMvc.perform(put("/api/EntityDescriptor/uuid-2").contentType(APPLICATION_JSON).content(postedJsonBody)) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.entityId").value("eid2")) +// .andExpect(jsonPath("\$.serviceEnabled").value(false)) +// .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.serviceProviderName").value("newName")) +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def "PUT /EntityDescriptor disallows non-admin user from enabling"() { +// given: +// Group g = userService.getCurrentUserGroup() +// +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: g.getOwnerId()) +// entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) +// entityManager.flush() +// +// when: +// entityDescriptorOne.serviceEnabled = true +// entityDescriptorOne.resourceId = 'uuid-1' +// def updatedEntityDescriptorRepresentation = jpaEntityDescriptorService.createRepresentationFromDescriptor(entityDescriptorOne) +// updatedEntityDescriptorRepresentation.version = entityDescriptorOne.hashCode() +// def postedJsonBody = mapper.writeValueAsString(updatedEntityDescriptorRepresentation) +// +// then: +// try { +// mockMvc.perform(put("/api/EntityDescriptor/uuid-1").contentType(APPLICATION_JSON).content(postedJsonBody)) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// +// @WithMockUser(value = "someUser", roles = ["USER"]) +// def "PUT /EntityDescriptor denies the request if the PUTing user is not an ADMIN and not the createdBy user"() { +// given: +// Group g = userService.getCurrentUserGroup() +// +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) +// entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) +// entityManager.flush() +// +// when: +// entityDescriptorOne.serviceProviderName = 'foo' +// entityDescriptorOne.resourceId = 'uuid-1' +// def updatedEntityDescriptorRepresentation = jpaEntityDescriptorService.createRepresentationFromDescriptor(entityDescriptorOne) +// updatedEntityDescriptorRepresentation.version = entityDescriptorOne.hashCode() +// def postedJsonBody = mapper.writeValueAsString(updatedEntityDescriptorRepresentation) +// +// then: +// try { +// mockMvc.perform(put("/api/EntityDescriptor/uuid-1").contentType(APPLICATION_JSON).content(postedJsonBody)) +// } +// catch (Exception e) { +// e instanceof ForbiddenException +// } +// } +// +// @WithMockAdmin +// def "PUT /EntityDescriptor throws a concurrent mod exception if the version numbers don't match"() { +// given: +// def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) +// entityDescriptorOne = entityDescriptorRepository.save(entityDescriptorOne) +// entityManager.flush() +// +// when: +// entityDescriptorOne.serviceProviderName = 'foo' +// entityDescriptorOne.resourceId = 'uuid-1' +// def updatedEntityDescriptorRepresentation = jpaEntityDescriptorService.createRepresentationFromDescriptor(entityDescriptorOne) +// +// def postedJsonBody = mapper.writeValueAsString(updatedEntityDescriptorRepresentation) +// +// then: +// try { +// mockMvc.perform(put("/api/EntityDescriptor/$resourceId").contentType(APPLICATION_JSON).content(postedJsonBody)) +// } +// catch (Exception e) { +// e instanceof ConcurrentModificationException +// } +// } +// +// @WithMockAdmin +// def "POST /EntityDescriptor OIDC descriptor - incoming JSON"() { +// when: +// def result = mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(fromFile("/json/SHIBUI-2380-1.json"))) +// +// then: +// result.andExpect(status().isCreated()) +// .andExpect(content().contentType(APPLICATION_JSON)) +// .andExpect(jsonPath("\$.entityId").value("mockSamlClientId2")) +// .andExpect(jsonPath("\$.serviceEnabled").value(false)) +// .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.serviceProviderSsoDescriptor.protocolSupportEnum").value("http://openid.net/specs/openid-connect-core-1_0.html")) +// .andExpect(jsonPath("\$.securityInfo.keyDescriptors[0].name").value("test1")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.clientUri").value("https://example.org/clientUri")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.responseTypes").value("code id_token")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.sectorIdentifierUri").value("https://example.org/sectorIdentifier")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.idTokenEncryptedResponseEnc").value("A256GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.applicationType").value("web")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.tokenEndpointAuthMethod").value("client_secret_basic")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoEncryptedResponseEnc").value("A192GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoSignedResponseAlg").value("RS384")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoEncryptedResponseAlg").value("A192KW")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.grantTypes").value("authorization_code")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.softwareId").value("mockSoftwareId")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requestObjectEncryptionEnc").value("A128GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.initiateLoginUri").value("https://example.org/initiateLogin")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.tokenEndpointAuthMethod").value("client_secret_basic")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requestObjectSigningAlg").value("RS256")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.scopes").value("openid profile")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.idTokenEncryptedResponseAlg").value("A256KW")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.softwareVersion").value("mockSoftwareVersion")) +// .andExpect(jsonPath(shortNameToOAuth + "postLogoutRedirectUris[0]").value("https://example.org/postLogout")) +// .andExpect(jsonPath(shortNameToOAuth + "requestUris[0]").value("https://example.org/request")) +// .andExpect(jsonPath(shortNameToOAuth + "defaultAcrValues").isArray()) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requireAuthTime").value(Boolean.FALSE)) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.defaultMaxAge").value(Integer.valueOf(0))) +// } +// +// @WithMockAdmin +// def 'GET /EntityDescriptor/{resourceId} existing as oidc xml'() { +// given: +// def representation = new ObjectMapper().readValue(this.class.getResource('/json/SHIBUI-2380.json').bytes, EntityDescriptorRepresentation) +// jpaEntityDescriptorService.createNew(representation) +// def edResourceId = jpaEntityDescriptorService.getAllEntityDescriptorProjectionsBasedOnUserAccess().get(0).getResourceId() +// +// when: +// def result = mockMvc.perform(get("/api/EntityDescriptor/" + edResourceId).accept(APPLICATION_XML)) +// +// then: +// String xmlContent = result.andReturn().getResponse().getContentAsString(); +// result.andExpect(status().isOk()) +// TestHelpers.generatedXmlIsTheSameAsExpectedXml(new String(fromFile("/metadata/SHIBUI-2380.xml"), StandardCharsets.UTF_8), xmlContent) +// } +// +// @WithMockAdmin +// def "POST /EntityDescriptor OIDC descriptor - incoming XML"() { +// when: +// def result = mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_XML).content(fromFile("/metadata/SHIBUI-2380.xml")).param("spName", "testing")) +// +// then: +// result.andExpect(status().isCreated()) +// .andExpect(content().contentType(APPLICATION_JSON)) +// .andExpect(jsonPath("\$.entityId").value("mockSamlClientId")) +// .andExpect(jsonPath("\$.serviceProviderSsoDescriptor.protocolSupportEnum").value("http://openid.net/specs/openid-connect-core-1_0.html")) +// .andExpect(jsonPath("\$.protocol").value("OIDC")) +// .andExpect(jsonPath("\$.serviceEnabled").value(false)) +// .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) +// .andExpect(jsonPath("\$.securityInfo.keyDescriptors[0].name").value("test1")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.clientUri").value("https://example.org/clientUri")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.responseTypes").value("code id_token")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.sectorIdentifierUri").value("https://example.org/sectorIdentifier")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.idTokenEncryptedResponseEnc").value("A256GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.applicationType").value("web")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.tokenEndpointAuthMethod").value("client_secret_basic")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoEncryptedResponseEnc").value("A192GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoSignedResponseAlg").value("RS384")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.userInfoEncryptedResponseAlg").value("A192KW")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.grantTypes").value("authorization_code")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.softwareId").value("mockSoftwareId")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requestObjectEncryptionEnc").value("A128GCM")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.initiateLoginUri").value("https://example.org/initiateLogin")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.tokenEndpointAuthMethod").value("client_secret_basic")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requestObjectSigningAlg").value("RS256")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.scopes").value("openid profile")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.idTokenEncryptedResponseAlg").value("A256KW")) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.softwareVersion").value("mockSoftwareVersion")) +// .andExpect(jsonPath(shortNameToOAuth + "postLogoutRedirectUris[0]").value("https://example.org/postLogout")) +// .andExpect(jsonPath(shortNameToOAuth + "requestUris[0]").value("https://example.org/request")) +// .andExpect(jsonPath(shortNameToOAuth + "audiences[0]").value("http://mypeeps")) +// .andExpect(jsonPath(shortNameToOAuth + "defaultAcrValues").isArray()) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.requireAuthTime").value(Boolean.FALSE)) +// .andExpect(jsonPath(shortNameToOAuth + "attributes.defaultMaxAge").value(Integer.valueOf(0))) +// } +// +// @SneakyThrows +// private byte[] fromFile(String path) { +// return new ClassPathResource(path).getInputStream().readAllBytes() +// } +} \ No newline at end of file