client dynamic registration now protected by access token, addresses #199

pull/263/head
Justin Richer 2012-12-06 17:48:23 -05:00
parent 7342da6a51
commit 7561ac9e8c
3 changed files with 74 additions and 15 deletions

View File

@ -11,6 +11,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.AbstractView;
@ -38,11 +39,19 @@ public class ClientAssociateView extends AbstractView {
Gson gson = new GsonBuilder().create(); Gson gson = new GsonBuilder().create();
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client"); ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) model.get("token");
JsonObject obj = new JsonObject(); JsonObject obj = new JsonObject();
obj.addProperty("client_id", client.getClientId()); obj.addProperty("client_id", client.getClientId());
obj.addProperty("client_secret", client.getClientSecret()); if (client.isSecretRequired()) {
obj.addProperty("expires_at", 0); // TODO: configure expiring client secrets. For now, they don't expire 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(); Writer out = response.getWriter();
gson.toJson(obj, out); gson.toJson(obj, out);

View File

@ -9,9 +9,20 @@ import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AppType; import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthType; import org.mitre.oauth2.model.ClientDetailsEntity.AuthType;
import org.mitre.oauth2.model.ClientDetailsEntity.UserIdType; import org.mitre.oauth2.model.ClientDetailsEntity.UserIdType;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.service.OAuth2TokenEntityService;
import org.springframework.beans.factory.annotation.Autowired; 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.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.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -32,6 +43,9 @@ public class ClientDynamicRegistrationEndpoint {
@Autowired @Autowired
private ClientDetailsEntityService clientService; private ClientDetailsEntityService clientService;
@Autowired
private OAuth2TokenEntityService tokenService;
/** /**
* Bind utility data types to their classes * Bind utility data types to their classes
* @param binder * @param binder
@ -243,38 +257,74 @@ public class ClientDynamicRegistrationEndpoint {
ClientDetailsEntity saved = clientService.saveNewClient(client); ClientDetailsEntity saved = clientService.saveNewClient(client);
OAuth2AccessTokenEntity registrationAccessToken = createRegistrationAccessToken(client);
model.put("client", saved); model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientAssociate"; 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") @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) {
String clientId = auth.getAuthorizationRequest().getClientId();
ClientDetailsEntity client = clientService.loadClientByClientId(clientId); ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
if (client == null) { if (client == null) {
throw new ClientNotFoundException("Could not find client: " + clientId); throw new ClientNotFoundException("Could not find client: " + clientId);
} }
if (!Objects.equal(client.getClientSecret(), clientSecret)) { // rotate the secret, if available
throw new UnauthorizedClientException("Client secret did not match"); if (client.isSecretRequired()) {
client = clientService.generateClientSecret(client);
} }
// rotate the secret // mint a new access token
client = clientService.generateClientSecret(client); 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); ClientDetailsEntity saved = clientService.updateClient(client, client);
model.put("client", saved); model.put("client", saved);
model.put("token", registrationAccessToken);
return "clientAssociate"; return "clientAssociate";
} }
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('registration-token')")
@RequestMapping(params = "type=client_update", produces = "application/json") @RequestMapping(params = "type=client_update", produces = "application/json")
public String clientUpdate( public String clientUpdate(
@RequestParam("client_id") String clientId,
@RequestParam("client_secret") String clientSecret,
@RequestParam(value = "contacts", required = false) Set<String> contacts, @RequestParam(value = "contacts", required = false) Set<String> contacts,
@RequestParam(value = "application_type", required = false) AppType applicationType, @RequestParam(value = "application_type", required = false) AppType applicationType,
@RequestParam(value = "application_name", required = false) String applicationName, @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 = "default_max_age", required = false) Integer defaultMaxAge,
@RequestParam(value = "require_auth_time", required = false) Boolean requireAuthTime, @RequestParam(value = "require_auth_time", required = false) Boolean requireAuthTime,
@RequestParam(value = "default_acr", required = false) String defaultAcr, @RequestParam(value = "default_acr", required = false) String defaultAcr,
OAuth2Authentication auth,
ModelMap model ModelMap model
) { ) {
String clientId = auth.getAuthorizationRequest().getClientId();
ClientDetailsEntity client = clientService.loadClientByClientId(clientId); ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
if (client == null) { if (client == null) {
throw new ClientNotFoundException("Could not find client: " + clientId); 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.setContacts(contacts);
client.setApplicationType(applicationType); client.setApplicationType(applicationType);
client.setApplicationName(applicationName); client.setApplicationName(applicationName);

View File

@ -73,6 +73,8 @@
<!-- OAuth-protect API and other endpoints --> <!-- OAuth-protect API and other endpoints -->
<security:http pattern="/register**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint"> <security:http pattern="/register**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint">
<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<security:expression-handler ref="oauthWebExpressionHandler" />
<security:intercept-url pattern="/register**" access="permitAll"/> <security:intercept-url pattern="/register**" access="permitAll"/>
</security:http> </security:http>