From 13d4c79fcab93a9477b18da87666f233d942cc8a Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 5 Dec 2022 14:14:08 -0700 Subject: [PATCH] SHIBUI-2393 correcting logic for testing enable --- .../CoreShibUiConfiguration.java | 4 +- .../ui/configuration/ShibUIConfiguration.java | 45 +++++++++++++ .../ui/controller/ActivateController.java | 2 +- .../DynamicRegistrationRepresentation.java | 2 + .../domain/oidc/DynamicRegistrationInfo.java | 1 + .../JPADynamicRegistrationServiceImpl.java | 7 +- .../ui/service/ShibRestTemplateDelegate.java | 64 ++++++++++++++----- 7 files changed, 105 insertions(+), 20 deletions(-) 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 0e0c59afa..8a3ef5aba 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 @@ -251,9 +251,7 @@ public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepo public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, IShibUiPermissionEvaluator permissionEvaluator, UserService userService, IGroupService groupService, @Qualifier("shibUIConfiguration") ShibUIConfiguration config, RestTemplateBuilder restTemplateBuilder) { - URL idpUrl = config.getShibIdpServer(); - RestTemplate template = restTemplateBuilder.build(); - ShibRestTemplateDelegate delegate = new ShibRestTemplateDelegate(idpUrl, template); + ShibRestTemplateDelegate delegate = new ShibRestTemplateDelegate(config); 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 ee402e36f..f1a85a4f9 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 @@ -2,11 +2,35 @@ import lombok.Getter; import lombok.Setter; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import java.net.URL; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; @@ -45,4 +69,25 @@ public class ShibUIConfiguration { * The URL of the shib idp server ala - https://idp.someschool.edu/idp */ private URL shibIdpServer; + + private RestTemplate restTemplate; + + @Profile("very-dangerous") + @Bean + public RestTemplate dangerousRestTemplate() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + + TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true; + SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + restTemplate = new RestTemplate(requestFactory); + return restTemplate; + } } \ 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 7136292f8..c3d69d88b 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 @@ -49,7 +49,7 @@ public ResponseEntity enableDynamicRegistration(@PathVariable String resource HttpStatus status = dynamicRegistrationService.enableDynamicRegistration(resourceId); switch (status) { case OK: - case ACCEPTED: return ResponseEntity.ok("Service enabled"); + case CREATED: 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"); } 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 index 971ac1124..1b4f1dfc8 100644 --- 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 @@ -36,6 +36,7 @@ public class DynamicRegistrationRepresentation { private String tokenEndpointAuthMethod; private String tosUri; private int version; + private String clientId; public DynamicRegistrationRepresentation(DynamicRegistrationInfo dri) { applicationType = dri.getApplicationType(); @@ -59,6 +60,7 @@ public DynamicRegistrationRepresentation(DynamicRegistrationInfo dri) { tokenEndpointAuthMethod = dri.getTokenEndpointAuthMethod(); tosUri = dri.getTosUri(); version = dri.hashCode(); + clientId = dri.getClientId(); } public DynamicRegistrationInfo buildDynamicRegistrationInfo() { 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 index efa37ed02..77ad95d29 100644 --- 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 @@ -43,6 +43,7 @@ public class DynamicRegistrationInfo extends AbstractAuditable implements Ownabl private String subjectType; private String tokenEndpointAuthMethod; private String tosUri; + private String clientId; @ElementCollection(fetch = FetchType.EAGER) @EqualsAndHashCode.Exclude 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 d89c28d6b..3ac846b39 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 @@ -143,7 +143,12 @@ public HttpStatus enableDynamicRegistration(String resourceId) throws Persistent if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this service"); } - return shibRestTemplateDelegate.sendRequest(existingDri); + HttpStatus status = shibRestTemplateDelegate.sendRequest(existingDri); + if (status == HttpStatus.CREATED || status == HttpStatus.OK) { + existingDri.setEnabled(true); + repository.save(existingDri); + } + return status; } 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 index 087d581fd..bb8720334 100644 --- 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 @@ -3,10 +3,13 @@ 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.configuration.ShibUIConfiguration; import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -15,34 +18,57 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** - * Requires that the shib server url be non-null + * Requires that the shib server url be non-null. The URL of the shib idp server ala - https://idp.someschool.edu/idp + * The URL is used to both fetch a token from Shib as well as to call the OIDC plugin */ public class ShibRestTemplateDelegate { - private URL shibUrl; + ShibUIConfiguration config; + private URL tokenURL; + private URL registrationURL; private RestTemplate restTemplate; - public ShibRestTemplateDelegate(URL url, RestTemplate template) { - this.restTemplate = template; + public ShibRestTemplateDelegate(ShibUIConfiguration config) { + this.config = config; + URL url = config.getShibIdpServer(); if (url != null) { try { - shibUrl = new URL(url.toExternalForm() + "/profile/oidc/register"); + registrationURL = new URL(url.toExternalForm() + "/profile/oidc/register"); + tokenURL = new URL(url.toExternalForm() + "/profile/admin/oidc/issue-registration-access-token?policyId=shibboleth.DefaultRelyingParty"); } catch (MalformedURLException e) { - shibUrl = null; + tokenURL = null; + registrationURL = null; } } } + /** + * Handles sending the Dynamic Registration request to Shibboleth (assuming the URL for Shib was configured for this) + * @throws UnsupportedShibUiOperationException + */ public HttpStatus sendRequest(DynamicRegistrationInfo dri) throws UnsupportedShibUiOperationException { - if (shibUrl == null) { + if (config.getRestTemplate() != null) { + this.restTemplate = config.getRestTemplate(); + } + + if (tokenURL == 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); + // Fetch an access token from SHIBBOLETH + ResponseEntity tokenResponse = restTemplate.postForEntity(tokenURL.toURI(), "", Map.class); + String token = ((Map)tokenResponse.getBody()).get("access_token").toString(); + + HttpEntity entity = convertDynamicReg(dri, token); + ResponseEntity response = restTemplate.exchange(registrationURL.toURI(), HttpMethod.POST, entity, Map.class); + if (response.getStatusCode() == HttpStatus.CREATED) { + dri.setClientId(((Map)response.getBody()).get("client_id").toString()); + } return response.getStatusCode(); } catch (URISyntaxException e) { @@ -50,20 +76,21 @@ public HttpStatus sendRequest(DynamicRegistrationInfo dri) throws UnsupportedShi } } - private Object convertDynamicReg(DynamicRegistrationInfo dri) { + private HttpEntity convertDynamicReg(DynamicRegistrationInfo dri, String token) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + token); 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()); + Map valuesMap = new HashMap<>(); + valuesMap.put("redirect_uris", arrayOrNull(dri.getRedirectUris())); + valuesMap.put("response_types", arrayOrNull(dri.getResponseTypes())); + valuesMap.put("grant_types", dri.getGrantType()); valuesMap.put("application_type", dri.getApplicationType()); - valuesMap.put("contacts", dri.getContacts()); + valuesMap.put("contacts", arrayOrNull(dri.getContacts())); valuesMap.put("subject_type", dri.getSubjectType()); - valuesMap.put("jwks", dri.getJwks()); + valuesMap.put("jwks", StringUtils.defaultIfEmpty(dri.getJwks(), null)); valuesMap.put("token_endpoint_auth_method", dri.getTokenEndpointAuthMethod()); valuesMap.put("logo_uri", dri.getLogoUri()); valuesMap.put("policy_uri", dri.getPolicyUri()); @@ -79,4 +106,11 @@ private Object convertDynamicReg(DynamicRegistrationInfo dri) { } return new HttpEntity(json, headers); } + + private String[] arrayOrNull(String values) { + if (values == null) { + return null; + } + return values.split(" "); + } } \ No newline at end of file