diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientAssociateView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientAssociateView.java index 04f5c1457..7d215d192 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientAssociateView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientAssociateView.java @@ -11,6 +11,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.stereotype.Component; import org.springframework.web.servlet.view.AbstractView; @@ -38,11 +39,19 @@ public class ClientAssociateView extends AbstractView { Gson gson = new GsonBuilder().create(); ClientDetailsEntity client = (ClientDetailsEntity) model.get("client"); + OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token"); JsonObject obj = new JsonObject(); obj.addProperty("client_id", client.getClientId()); - obj.addProperty("client_secret", client.getClientSecret()); - obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire + if (client.isSecretRequired()) { + obj.addProperty("client_secret", client.getClientSecret()); + } + obj.addProperty("registration_access_token", token.getValue()); + if (token.getExpiration() != null) { + obj.addProperty("expires_at", token.getExpiration().getTime()); // TODO: make sure this makes sense? + } else { + obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire + } Writer out = response.getWriter(); gson.toJson(obj, out); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java index 1552b6ca7..970765a1d 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java @@ -9,9 +9,20 @@ import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity.AppType; import org.mitre.oauth2.model.ClientDetailsEntity.AuthType; import org.mitre.oauth2.model.ClientDetailsEntity.UserIdType; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.DefaultAuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.WebDataBinder; @@ -32,6 +43,9 @@ public class ClientDynamicRegistrationEndpoint { @Autowired private ClientDetailsEntityService clientService; + @Autowired + private OAuth2TokenEntityService tokenService; + /** * Bind utility data types to their classes * @param binder @@ -243,38 +257,74 @@ public class ClientDynamicRegistrationEndpoint { ClientDetailsEntity saved = clientService.saveNewClient(client); + OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client); + model.put("client", saved); + model.put("token", registrationAccessToken); return "clientAssociate"; } + + /** + * @param client + * @return + * @throws AuthenticationException + */ + private OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) throws AuthenticationException { + // create a registration access token, treat it like a client credentials flow + // I can't use the auth request interface here because it has no setters and bad constructors -- THIS IS BAD API DESIGN + DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(client.getClientId(), Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE)); + authorizationRequest.setApproved(true); + authorizationRequest.setAuthorities(Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT"))); + OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest, null); + OAuth2AccessTokenEntity registrationAccessToken = (OAuth2AccessTokenEntity) tokenService.createAccessToken(authentication); + return registrationAccessToken; + } + @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')") @RequestMapping(params = "type=rotate_secret", produces = "application/json") - public String rotateSecret(@RequestParam("client_id") String clientId, @RequestParam("client_secret") String clientSecret, ModelMap model) { + public String rotateSecret(OAuth2Authentication auth, ModelMap model) { - ClientDetailsEntity client = clientService.loadClientByClientId(clientId); + String clientId = auth.getAuthorizationRequest().getClientId(); + ClientDetailsEntity client = clientService.loadClientByClientId(clientId); + if (client == null) { throw new ClientNotFoundException("Could not find client: " + clientId); } - if (!Objects.equal(client.getClientSecret(), clientSecret)) { - throw new UnauthorizedClientException("Client secret did not match"); + // rotate the secret, if available + if (client.isSecretRequired()) { + client = clientService.generateClientSecret(client); } - // rotate the secret - client = clientService.generateClientSecret(client); + // mint a new access token + OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client); + + // revoke the old one + OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); + if (details != null) { + OAuth2AccessTokenEntity oldAccessToken = tokenService.readAccessToken(details.getTokenValue()); + if (oldAccessToken != null) { + tokenService.revokeAccessToken(oldAccessToken); + } else { + // serious error here -- how'd we get this far without a valid token?! + throw new OAuth2Exception("SEVERE: token not found, something is fishy"); + } + } + // save the client ClientDetailsEntity saved = clientService.updateClient(client, client); model.put("client", saved); - + model.put("token", registrationAccessToken); + return "clientAssociate"; } + @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')") @RequestMapping(params = "type=client_update", produces = "application/json") public String clientUpdate( - @RequestParam("client_id") String clientId, - @RequestParam("client_secret") String clientSecret, @RequestParam(value = "contacts", required = false) Set contacts, @RequestParam(value = "application_type", required = false) AppType applicationType, @RequestParam(value = "application_name", required = false) String applicationName, @@ -305,20 +355,18 @@ public class ClientDynamicRegistrationEndpoint { @RequestParam(value = "default_max_age", required = false) Integer defaultMaxAge, @RequestParam(value = "require_auth_time", required = false) Boolean requireAuthTime, @RequestParam(value = "default_acr", required = false) String defaultAcr, + OAuth2Authentication auth, ModelMap model ) { + String clientId = auth.getAuthorizationRequest().getClientId(); ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client == null) { throw new ClientNotFoundException("Could not find client: " + clientId); } - if (!Objects.equal(client.getClientSecret(), clientSecret)) { - throw new UnauthorizedClientException("Client secret did not match"); - } - client.setContacts(contacts); client.setApplicationType(applicationType); client.setApplicationName(applicationName); diff --git a/openid-connect-server/src/main/webapp/WEB-INF/application-context.xml b/openid-connect-server/src/main/webapp/WEB-INF/application-context.xml index 60012472c..4a645a83b 100644 --- a/openid-connect-server/src/main/webapp/WEB-INF/application-context.xml +++ b/openid-connect-server/src/main/webapp/WEB-INF/application-context.xml @@ -73,6 +73,8 @@ + +