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 e9865c93b..c57b448b5 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 @@ -34,6 +34,7 @@ import edu.internet2.tier.shibboleth.admin.ui.service.JPAFilterTargetServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService; +import edu.internet2.tier.shibboleth.admin.ui.service.ShibRestTemplateDelegate; import edu.internet2.tier.shibboleth.admin.util.AttributeUtility; import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils; import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; @@ -43,12 +44,14 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.io.Resource; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; @@ -57,6 +60,7 @@ import org.springframework.web.util.UrlPathHelper; import javax.servlet.http.HttpServletRequest; +import java.net.URL; @Configuration @Import(SearchConfiguration.class) @@ -243,7 +247,10 @@ public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepo } @Bean - public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService, IGroupService groupService) { - return new JPADynamicRegistrationServiceImpl(groupService, driRepo, ownershipRepo, permissionEvaluator, userService); + public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService, IGroupService groupService, ShibUIConfiguration config, RestTemplateBuilder restTemplateBuilder) { + URL idpUrl = config.getShibIdpServer(); + RestTemplate template = restTemplateBuilder.build(); + ShibRestTemplateDelegate delegate = new ShibRestTemplateDelegate(idpUrl, template); + return new JPADynamicRegistrationServiceImpl(groupService, driRepo, ownershipRepo, delegate, permissionEvaluator, userService); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java index 50f5a2e75..ee402e36f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; +import java.net.URL; import java.util.List; import java.util.Set; @@ -39,4 +40,9 @@ public class ShibUIConfiguration { * A list of roles to bootstrap into the system. */ private Set roles; + + /** + * The URL of the shib idp server ala - https://idp.someschool.edu/idp + */ + private URL shibIdpServer; } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java index 1fc52d3c4..7136292f8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java @@ -16,6 +16,7 @@ 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.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PatchMapping; @@ -45,8 +46,13 @@ public class ActivateController { @Transactional public ResponseEntity enableDynamicRegistration(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException { if ("enable".equalsIgnoreCase(mode)) { - DynamicRegistrationRepresentation drr = dynamicRegistrationService.enableDynamicRegistration(resourceId); - return ResponseEntity.ok(drr); + HttpStatus status = dynamicRegistrationService.enableDynamicRegistration(resourceId); + switch (status) { + case OK: + case ACCEPTED: return ResponseEntity.ok("Service enabled"); + case NOT_FOUND: throw new UnsupportedShibUiOperationException("Request returned NOT FOUND, please contact a system admin to check configuration"); + case FORBIDDEN: throw new ForbiddenException("Request was denied with FORBIDDEN, please contact a system admin to check configuration"); + } } throw new UnsupportedShibUiOperationException("Disable is not a valid operation for Dynamic Registrations at this time"); } 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 index ef95ce70b..c92097d92 100644 --- 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 @@ -4,6 +4,8 @@ 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.exception.UnsupportedShibUiOperationException; +import org.springframework.http.HttpStatus; import java.util.List; @@ -15,7 +17,8 @@ DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound; - DynamicRegistrationRepresentation enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException; + HttpStatus enableDynamicRegistration(String resourceId) + throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException; List getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException; 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 index 9c804d42c..18a1be0ef 100644 --- 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 @@ -5,6 +5,7 @@ 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.exception.UnsupportedShibUiOperationException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner; @@ -22,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -34,19 +36,21 @@ @NoArgsConstructor public class JPADynamicRegistrationServiceImpl implements DynamicRegistrationService { @Autowired - IGroupService groupService; + private IGroupService groupService; @Autowired - DynamicRegistrationInfoRepository repository; + private DynamicRegistrationInfoRepository repository; @Autowired - OwnershipRepository ownershipRepository; + private OwnershipRepository ownershipRepository; + + private ShibRestTemplateDelegate shibRestTemplateDelegate; @Autowired private IShibUiPermissionEvaluator shibUiAuthorizationDelegate; @Autowired - UserService userService; + private UserService userService; @Override public DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { @@ -125,7 +129,7 @@ public void delete(String resourceId) throws ForbiddenException, PersistentEntit } @Override - public DynamicRegistrationRepresentation enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException { + public HttpStatus enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException { DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); if (existingDri == null) { throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); @@ -133,8 +137,7 @@ public DynamicRegistrationRepresentation enableDynamicRegistration(String resour if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this service"); } - // TODO do something... - return new DynamicRegistrationRepresentation(existingDri); + return shibRestTemplateDelegate.sendRequest(existingDri); } private boolean entityExists(String id) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java new file mode 100644 index 000000000..087d581fd --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java @@ -0,0 +1,82 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * Requires that the shib server url be non-null + */ +public class ShibRestTemplateDelegate { + private URL shibUrl; + private RestTemplate restTemplate; + + public ShibRestTemplateDelegate(URL url, RestTemplate template) { + this.restTemplate = template; + if (url != null) { + try { + shibUrl = new URL(url.toExternalForm() + "/profile/oidc/register"); + } + catch (MalformedURLException e) { + shibUrl = null; + } + } + } + + public HttpStatus sendRequest(DynamicRegistrationInfo dri) throws UnsupportedShibUiOperationException { + if (shibUrl == null) { + throw new UnsupportedShibUiOperationException("Dynamic Registration endpoint not configured properly, please contact your system admin."); + } + try { + ResponseEntity response = restTemplate.postForEntity(shibUrl.toURI(), convertDynamicReg(dri), Map.class); + return response.getStatusCode(); + } + catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private Object convertDynamicReg(DynamicRegistrationInfo dri) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // skip any null values + + Map valuesMap = new HashMap<>(); + valuesMap.put("redirect_uris", dri.getRedirectUris()); + valuesMap.put("response_types", dri.getResponseTypes()); + valuesMap.put("grant_types", dri.getGrantType().name()); + valuesMap.put("application_type", dri.getApplicationType()); + valuesMap.put("contacts", dri.getContacts()); + valuesMap.put("subject_type", dri.getSubjectType()); + valuesMap.put("jwks", dri.getJwks()); + valuesMap.put("token_endpoint_auth_method", dri.getTokenEndpointAuthMethod()); + valuesMap.put("logo_uri", dri.getLogoUri()); + valuesMap.put("policy_uri", dri.getPolicyUri()); + valuesMap.put("tos_uri", dri.getTosUri()); + valuesMap.put("scope", dri.getScope()); + + String json; + try { + json = mapper.writeValueAsString(valuesMap); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return new HttpEntity(json, headers); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index bbe104c23..65675b9ce 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -167,6 +167,17 @@ custom: helpText: tooltip.ignore-request-signatures attributeName: http://shibboleth.net/ns/profiles/ignoreRequestSignatures attributeFriendlyName: ignoreRequestSignatures + - name: postAuthenticationFlows + attributeFriendlyName: postAuthenticationFlows + displayName: label.postAuthenticationFlows + helpText: tooltip.postAuthenticationFlows + displayType: selection_list + defaultValues: + - attribute-release + - expiring-password + - terms-of-use + attributeName: http://shibboleth.net/ns/profiles/postAuthenticationFlows" + protocol: saml,oidc - name: inboundInterceptorFlows attributeFriendlyName: inboundInterceptorFlows displayName: label.inboundInterceptorFlows @@ -189,13 +200,13 @@ custom: defaultValue: client_secret_basic, client_secret_post, client_secret_jwt, private_key_jwt attributeName: http://shibboleth.net/ns/profiles/tokenEndpointAuthMethods protocol: oidc - - name: postAuthenticationFlows - attributeFriendlyName: postAuthenticationFlows - displayName: label.postAuthenticationFlows - helpText: tooltip.postAuthenticationFlows - displayType: string - attributeName: http://shibboleth.net/ns/profiles/postAuthenticationFlows - protocol: oidc +# - name: postAuthenticationFlows +# attributeFriendlyName: postAuthenticationFlows +# displayName: label.postAuthenticationFlows +# helpText: tooltip.postAuthenticationFlows +# displayType: string +# attributeName: http://shibboleth.net/ns/profiles/postAuthenticationFlows +# protocol: oidc - name: proxyCount attributeFriendlyName: proxyCount displayName: label.proxyCount