Skip to content

Commit

Permalink
SHIBUI-2393
Browse files Browse the repository at this point in the history
All CRUD operations and unit tests for DynamicRegistrations
  • Loading branch information
chasegawa committed Nov 14, 2022
1 parent c4dd0a7 commit 8320b88
Show file tree
Hide file tree
Showing 9 changed files with 592 additions and 722 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepo
}

@Bean
public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService) {
return new JPADynamicRegistrationServiceImpl(driRepo, ownershipRepo, permissionEvaluator, userService);
public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService, IGroupService groupService) {
return new JPADynamicRegistrationServiceImpl(groupService, driRepo, ownershipRepo, permissionEvaluator, userService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,45 @@

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.exception.PersistentEntityNotFound;
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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;
import java.util.ConcurrentModificationException;

@RestController
@RequestMapping("/api")
@Tags(value = {@Tag(name = "oidc")})
public class DynamicRegistrationController {
private static URI getResourceUriFor(String resourceId) {
return ServletUriComponentsBuilder
.fromCurrentServletMapping().path("/api/DynamicRegistration")
.pathSegment(resourceId)
.build()
.toUri();
}

@Autowired
DynamicRegistrationService dynamicRegistrationService;

@PostMapping("/DynamicRegistration")
@Transactional
public ResponseEntity<?> create(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation) throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException {
public ResponseEntity<?> create(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException {
DynamicRegistrationRepresentation persisted = dynamicRegistrationService.createNew(dynRegRepresentation);
return ResponseEntity.created(getResourceUriFor(persisted.getResourceId())).body(persisted);
}
Expand All @@ -39,13 +51,26 @@ 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();
@DeleteMapping(value = "/DynamicRegistration/{resourceId}")
@Transactional
public ResponseEntity<?> deleteOne(@PathVariable String resourceId) throws ForbiddenException, PersistentEntityNotFound {
dynamicRegistrationService.delete(resourceId);
return ResponseEntity.noContent().build();
}

@PutMapping("/DynamicRegistration/{resourceId}")
@Transactional
public ResponseEntity<?> update(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation, @PathVariable String resourceId) throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound {
dynRegRepresentation.setResourceId(resourceId); // This should be the same already, but just to be safe...
DynamicRegistrationRepresentation result = dynamicRegistrationService.update(dynRegRepresentation);
return ResponseEntity.ok().body(result);
}

@PutMapping("/DynamicRegistration/{resourceId}/changeGroup/{groupId}")
@Transactional
public ResponseEntity<?> updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId) throws ForbiddenException, PersistentEntityNotFound {
DynamicRegistrationRepresentation result = dynamicRegistrationService.updateGroupForDynamicRegistration(resourceId, groupId);
return ResponseEntity.ok().body(result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ public ResponseEntity<?> update(@RequestBody EntityDescriptorRepresentation edRe

@PutMapping("/EntityDescriptor/{resourceId}/changeGroup/{groupId}")
@Transactional
public ResponseEntity<?> updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId)
throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound, InvalidPatternMatchException {
public ResponseEntity<?> updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId) throws ConcurrentModificationException {
EntityDescriptorRepresentation result = entityDescriptorService.updateGroupForEntityDescriptor(resourceId, groupId);
return ResponseEntity.ok().body(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

import java.util.ConcurrentModificationException;

@ControllerAdvice(assignableTypes = {EntityDescriptorController.class})
public class EntityDescriptorControllerExceptionHandler extends ResponseEntityExceptionHandler {
@ControllerAdvice(assignableTypes = {EntityDescriptorController.class, DynamicRegistrationController.class})
public class PersistentEntityControllerExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler({ ConcurrentModificationException.class })
public ResponseEntity<?> handleConcurrentModificationException(ConcurrentModificationException e, WebRequest request) {
Expand Down Expand Up @@ -43,7 +43,7 @@ public ResponseEntity<?> handleObjectIdExistsException(ObjectIdExistsException e
headers.setLocation(EntityDescriptorController.getResourceUriFor(e.getMessage()));
return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers).body(new ErrorResponse(
String.valueOf(HttpStatus.CONFLICT.value()),
String.format("The entity descriptor with entity id [%s] already exists.", e.getMessage())));
String.format("The persistent entity with id [%s] already exists.", e.getMessage())));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,25 @@ public String getCreatedDate() {
public String getModifiedDate() {
return modifiedDate != null ? DATE_TIME_FORMATTER.format(modifiedDate) : null;
}

/**
* Do not update approved or change the group here
*/
public DynamicRegistrationInfo updateExistingWithRepValues(DynamicRegistrationInfo dri) {
dri.setApplicationType(applicationType);
dri.setContacts(contacts);
dri.setEnabled(enabled);
dri.setGrantType(grantType);
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ public void removeLastApproval() {
approvedBy.remove(approvedBy.size() - 1);
}
}

public int approvedCount() {
return approvedBy.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
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;
import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound;

public interface DynamicRegistrationService {
Object getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException;

DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException;

void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound;

DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation)
throws PersistentEntityNotFound, ForbiddenException;

DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId)
throws ForbiddenException, PersistentEntityNotFound;
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
package edu.internet2.tier.shibboleth.admin.ui.service;

import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
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.exception.PersistentEntityNotFound;
import edu.internet2.tier.shibboleth.admin.ui.security.model.Group;
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 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.IGroupService;
import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ConcurrentModificationException;
import java.util.List;

@Slf4j
@Service
@AllArgsConstructor
@NoArgsConstructor
public class JPADynamicRegistrationServiceImpl implements DynamicRegistrationService {
@Autowired
IGroupService groupService;

@Autowired
DynamicRegistrationInfoRepository repository;

Expand Down Expand Up @@ -60,6 +71,23 @@ public DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresenta
return new DynamicRegistrationRepresentation(repository.save(dri));
}

@Override
public void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound {
if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) {
throw new ForbiddenException("Deleting a Dynamic Registration Source is only allowed by an admin.");
}

DynamicRegistrationInfo ed = repository.findByResourceId(resourceId);
if (ed==null) {
throw new PersistentEntityNotFound("Dynamic Registration not found for resource id: " + resourceId);
}
if (ed.isEnabled()) {
throw new ForbiddenException("Deleting an enabled Dynamic Registration Source is not allowed.");
}
ownershipRepository.deleteEntriesForOwnedObject(ed);
repository.delete(ed);
}

private boolean entityExists(String id) {
return repository.findByResourceId(id) != null ;
}
Expand All @@ -68,4 +96,59 @@ private boolean entityExists(String id) {
public List<DynamicRegistrationInfo> getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException {
return (List<DynamicRegistrationInfo>) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.dynamicRegistrationInfo, PermissionType.fetch);
}

@Override
public DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation)
throws PersistentEntityNotFound, ForbiddenException, ConcurrentModificationException {
DynamicRegistrationInfo existingDri = repository.findByResourceId(dynRegRepresentation.getResourceId());
if (existingDri == null) {
throw new PersistentEntityNotFound(String.format("The dynamic registration with entity id [%s] was not found for update.", existingDri.getResourceId()));
}
if (dynRegRepresentation.isEnabled() && !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) {
throw new ForbiddenException("You do not have the permissions necessary to enable this service.");
}
if (StringUtils.isEmpty(dynRegRepresentation.getIdOfOwner())) {
dynRegRepresentation.setIdOfOwner(StringUtils.isNotEmpty(existingDri.getIdOfOwner()) ? existingDri.getIdOfOwner() : userService.getCurrentUserGroup().getOwnerId());
}
if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.viewOrEdit)) {
throw new ForbiddenException();
}
// Verify we're the only one attempting to update the EntityDescriptor
if (dynRegRepresentation.getVersion() != existingDri.hashCode()) {
throw new ConcurrentModificationException(String.format("A concurrent modification has occured on entity descriptor with entity id [%s]. Please refresh and try again", dynRegRepresentation.getResourceId()));
}
existingDri = dynRegRepresentation.updateExistingWithRepValues(existingDri);

existingDri = repository.save(existingDri);
ownershipRepository.deleteEntriesForOwnedObject(existingDri);
ownershipRepository.save(new Ownership(new Owner() {
public String getOwnerId() { return dynRegRepresentation.getIdOfOwner(); }
public OwnerType getOwnerType() { return OwnerType.GROUP; }
}, existingDri));
return new DynamicRegistrationRepresentation(existingDri);
}

@Override
public DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) throws ForbiddenException, PersistentEntityNotFound {
DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId);
if (existingDri == null) {
throw new PersistentEntityNotFound(String.format("The dynamic registration with entity id [%s] was not found for update.", existingDri.getResourceId()));
}
if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.admin)) {
throw new ForbiddenException("You do not have the permissions necessary to change the group for this service.");
}
existingDri.setIdOfOwner(groupId);

Group group = groupService.find(groupId);
ownershipRepository.deleteEntriesForOwnedObject(existingDri);
ownershipRepository.save(new Ownership(group, existingDri));
// check and see if we need to update the approved status
if (!existingDri.isEnabled()) {
int numApprovers = group.getApproversList().size();
existingDri.setApproved(!(numApprovers > 0 && existingDri.approvedCount() < numApprovers));
}

DynamicRegistrationInfo savedEntity = repository.save(existingDri);
return new DynamicRegistrationRepresentation(savedEntity);
}
}
Loading

0 comments on commit 8320b88

Please sign in to comment.