added resource registration endpoint with basic functionality and specialized tokens
parent
0775785ce3
commit
c34357a433
|
@ -32,6 +32,7 @@ public interface SystemScopeService {
|
||||||
public static final String OFFLINE_ACCESS = "offline_access";
|
public static final String OFFLINE_ACCESS = "offline_access";
|
||||||
public static final String ID_TOKEN_SCOPE = "id-token";
|
public static final String ID_TOKEN_SCOPE = "id-token";
|
||||||
public static final String REGISTRATION_TOKEN_SCOPE = "registration-token";
|
public static final String REGISTRATION_TOKEN_SCOPE = "registration-token";
|
||||||
|
public static final String RESOURCE_TOKEN_SCOPE = "resource-token";
|
||||||
|
|
||||||
public Set<SystemScope> getAll();
|
public Set<SystemScope> getAll();
|
||||||
|
|
||||||
|
|
|
@ -55,4 +55,12 @@ public interface OIDCTokenService {
|
||||||
*/
|
*/
|
||||||
public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client);
|
public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resource access token for the given client (protected resource).
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client);
|
||||||
|
|
||||||
}
|
}
|
|
@ -105,7 +105,7 @@ var ResRegRootView = Backbone.View.extend({
|
||||||
var clientId = $('#clientId').val();
|
var clientId = $('#clientId').val();
|
||||||
var token = $('#regtoken').val();
|
var token = $('#regtoken').val();
|
||||||
|
|
||||||
var client = new DynRegClient({
|
var client = new ResRegClient({
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
registration_access_token: token
|
registration_access_token: token
|
||||||
});
|
});
|
||||||
|
@ -185,7 +185,7 @@ var ResRegEditView = Backbone.View.extend({
|
||||||
this.model.destroy({
|
this.model.destroy({
|
||||||
success:function () {
|
success:function () {
|
||||||
self.remove();
|
self.remove();
|
||||||
app.navigate('dev/dynreg', {trigger: true});
|
app.navigate('dev/resource', {trigger: true});
|
||||||
},
|
},
|
||||||
error:function (error, response) {
|
error:function (error, response) {
|
||||||
console.log("An error occurred when deleting a client");
|
console.log("An error occurred when deleting a client");
|
||||||
|
@ -299,10 +299,8 @@ var ResRegEditView = Backbone.View.extend({
|
||||||
|
|
||||||
var attrs = {
|
var attrs = {
|
||||||
client_name:$('#clientName input').val(),
|
client_name:$('#clientName input').val(),
|
||||||
redirect_uris: this.redirectUrisCollection.pluck("item"),
|
|
||||||
client_description:$('#clientDescription textarea').val(),
|
client_description:$('#clientDescription textarea').val(),
|
||||||
logo_uri:$('#logoUri input').val(),
|
logo_uri:$('#logoUri input').val(),
|
||||||
grant_types: grantTypes,
|
|
||||||
scope: scopes,
|
scope: scopes,
|
||||||
|
|
||||||
tos_uri: $('#tosUri input').val(),
|
tos_uri: $('#tosUri input').val(),
|
||||||
|
@ -310,25 +308,8 @@ var ResRegEditView = Backbone.View.extend({
|
||||||
client_uri: $('#clientUri input').val(),
|
client_uri: $('#clientUri input').val(),
|
||||||
application_type: $('#applicationType input').filter(':checked').val(),
|
application_type: $('#applicationType input').filter(':checked').val(),
|
||||||
jwks_uri: $('#jwksUri input').val(),
|
jwks_uri: $('#jwksUri input').val(),
|
||||||
subject_type: $('#subjectType input').filter(':checked').val(),
|
|
||||||
token_endpoint_auth_method: $('#tokenEndpointAuthMethod input').filter(':checked').val(),
|
token_endpoint_auth_method: $('#tokenEndpointAuthMethod input').filter(':checked').val(),
|
||||||
response_types: responseTypes,
|
|
||||||
sector_identifier_uri: $('#sectorIdentifierUri input').val(),
|
|
||||||
initiate_login_uri: $('#initiateLoginUri input').val(),
|
|
||||||
post_logout_redirect_uri: $('#postLogoutRedirectUri input').val(),
|
|
||||||
reuse_refresh_token: $('#reuseRefreshToken').is(':checked'),
|
|
||||||
require_auth_time: $('#requireAuthTime input').is(':checked'),
|
|
||||||
default_max_age: parseInt($('#defaultMaxAge input').val()),
|
|
||||||
contacts: contacts,
|
contacts: contacts,
|
||||||
request_uris: this.requestUrisCollection.pluck('item'),
|
|
||||||
default_acr_values: this.defaultAcrValuesCollection.pluck('item'),
|
|
||||||
request_object_signing_alg: this.defaultToNull($('#requestObjectSigningAlg select').val()),
|
|
||||||
userinfo_signed_response_alg: this.defaultToNull($('#userInfoSignedResponseAlg select').val()),
|
|
||||||
userinfo_encrypted_response_alg: this.defaultToNull($('#userInfoEncryptedResponseAlg select').val()),
|
|
||||||
userinfo_encrypted_response_enc: this.defaultToNull($('#userInfoEncryptedResponseEnc select').val()),
|
|
||||||
id_token_signed_response_alg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()),
|
|
||||||
id_token_encrypted_response_alg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()),
|
|
||||||
id_token_encrypted_response_enc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()),
|
|
||||||
token_endpoint_auth_signing_alg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val())
|
token_endpoint_auth_signing_alg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -343,9 +324,9 @@ var ResRegEditView = Backbone.View.extend({
|
||||||
this.model.save(attrs, {
|
this.model.save(attrs, {
|
||||||
success:function () {
|
success:function () {
|
||||||
// switch to an "edit" view
|
// switch to an "edit" view
|
||||||
app.navigate('dev/dynreg/edit', {trigger: true});
|
app.navigate('dev/resource/edit', {trigger: true});
|
||||||
_self.remove();
|
_self.remove();
|
||||||
var view = new DynRegEditView({model: _self.model, systemScopeList: _self.options.systemScopeList});
|
var view = new ResRegEditView({model: _self.model, systemScopeList: _self.options.systemScopeList});
|
||||||
|
|
||||||
view.load(function() {
|
view.load(function() {
|
||||||
// reload
|
// reload
|
||||||
|
|
|
@ -212,7 +212,6 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
||||||
claims.setExpirationTime(token.getExpiration());
|
claims.setExpirationTime(token.getExpiration());
|
||||||
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||||
|
|
||||||
// TODO: use client's default signing algorithm
|
|
||||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||||
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||||
|
|
||||||
|
@ -221,7 +220,47 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
||||||
token.setJwt(signed);
|
token.setJwt(signed);
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param client
|
||||||
|
* @return
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client) {
|
||||||
|
|
||||||
|
Map<String, String> authorizationParameters = Maps.newHashMap();
|
||||||
|
OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(),
|
||||||
|
Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")), true,
|
||||||
|
Sets.newHashSet(SystemScopeService.RESOURCE_TOKEN_SCOPE), null, null, null, null);
|
||||||
|
OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null);
|
||||||
|
|
||||||
|
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
|
||||||
|
token.setClient(client);
|
||||||
|
token.setScope(Sets.newHashSet(SystemScopeService.RESOURCE_TOKEN_SCOPE));
|
||||||
|
|
||||||
|
AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity();
|
||||||
|
authHolder.setAuthentication(authentication);
|
||||||
|
authHolder = authenticationHolderRepository.save(authHolder);
|
||||||
|
token.setAuthenticationHolder(authHolder);
|
||||||
|
|
||||||
|
JWTClaimsSet claims = new JWTClaimsSet();
|
||||||
|
|
||||||
|
claims.setAudience(Lists.newArrayList(client.getClientId()));
|
||||||
|
claims.setIssuer(configBean.getIssuer());
|
||||||
|
claims.setIssueTime(new Date());
|
||||||
|
claims.setExpirationTime(token.getExpiration());
|
||||||
|
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||||
|
|
||||||
|
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||||
|
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||||
|
|
||||||
|
jwtService.signJwt(signed);
|
||||||
|
|
||||||
|
token.setJwt(signed);
|
||||||
|
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -177,6 +177,9 @@ public class ClientDynamicRegistrationEndpoint {
|
||||||
|
|
||||||
// this client has been dynamically registered (obviously)
|
// this client has been dynamically registered (obviously)
|
||||||
newClient.setDynamicallyRegistered(true);
|
newClient.setDynamicallyRegistered(true);
|
||||||
|
|
||||||
|
// this client can't do token introspection
|
||||||
|
newClient.setAllowIntrospection(false);
|
||||||
|
|
||||||
// now save it
|
// now save it
|
||||||
try {
|
try {
|
||||||
|
@ -285,7 +288,7 @@ public class ClientDynamicRegistrationEndpoint {
|
||||||
newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds());
|
newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds());
|
||||||
newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds());
|
newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds());
|
||||||
newClient.setDynamicallyRegistered(true); // it's still dynamically registered
|
newClient.setDynamicallyRegistered(true); // it's still dynamically registered
|
||||||
newClient.setAllowIntrospection(oldClient.isAllowIntrospection());
|
newClient.setAllowIntrospection(false); // dynamically registered clients can't do introspection -- use the resource registration instead
|
||||||
newClient.setAuthorities(oldClient.getAuthorities());
|
newClient.setAuthorities(oldClient.getAuthorities());
|
||||||
newClient.setClientDescription(oldClient.getClientDescription());
|
newClient.setClientDescription(oldClient.getClientDescription());
|
||||||
newClient.setCreatedAt(oldClient.getCreatedAt());
|
newClient.setCreatedAt(oldClient.getCreatedAt());
|
||||||
|
|
|
@ -0,0 +1,359 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright 2014 The MITRE Corporation
|
||||||
|
* and the MIT Kerberos and Internet Trust Consortium
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
******************************************************************************/
|
||||||
|
package org.mitre.openid.connect.web;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||||
|
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
|
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||||
|
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||||
|
import org.mitre.oauth2.model.RegisteredClient;
|
||||||
|
import org.mitre.oauth2.model.SystemScope;
|
||||||
|
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||||
|
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
||||||
|
import org.mitre.oauth2.service.SystemScopeService;
|
||||||
|
import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
|
||||||
|
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||||
|
import org.mitre.openid.connect.service.BlacklistedSiteService;
|
||||||
|
import org.mitre.openid.connect.service.OIDCTokenService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||||
|
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.util.UriUtils;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping(value = "resource")
|
||||||
|
public class ProtectedResourceRegistrationEndpoint {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientDetailsEntityService clientService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OAuth2TokenEntityService tokenService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtSigningAndValidationService jwtService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemScopeService scopeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BlacklistedSiteService blacklistService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationPropertiesBean config;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OIDCTokenService connectTokenService;
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Client, issue a client ID, and create a registration access token.
|
||||||
|
* @param jsonString
|
||||||
|
* @param m
|
||||||
|
* @param p
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
|
||||||
|
public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {
|
||||||
|
|
||||||
|
ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||||
|
|
||||||
|
if (newClient != null) {
|
||||||
|
// it parsed!
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now do some post-processing consistency checks on it
|
||||||
|
//
|
||||||
|
|
||||||
|
// clear out any spurious id/secret (clients don't get to pick)
|
||||||
|
newClient.setClientId(null);
|
||||||
|
newClient.setClientSecret(null);
|
||||||
|
|
||||||
|
// set of scopes that are OK for clients to dynamically register for
|
||||||
|
Set<SystemScope> dynScopes = scopeService.getDynReg();
|
||||||
|
|
||||||
|
// scopes that the client is asking for
|
||||||
|
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
|
||||||
|
|
||||||
|
// the scopes that the client can have must be a subset of the dynamically allowed scopes
|
||||||
|
Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes);
|
||||||
|
|
||||||
|
// if the client didn't ask for any, give them the defaults
|
||||||
|
if (allowedScopes == null || allowedScopes.isEmpty()) {
|
||||||
|
allowedScopes = scopeService.getDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
newClient.setScope(scopeService.toStrings(allowedScopes));
|
||||||
|
|
||||||
|
|
||||||
|
// no grant types are allowed
|
||||||
|
newClient.setGrantTypes(new HashSet<String>());
|
||||||
|
newClient.setResponseTypes(new HashSet<String>());
|
||||||
|
newClient.setRedirectUris(new HashSet<String>());
|
||||||
|
|
||||||
|
if (newClient.getTokenEndpointAuthMethod() == null) {
|
||||||
|
newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC ||
|
||||||
|
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT ||
|
||||||
|
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) {
|
||||||
|
|
||||||
|
// we need to generate a secret
|
||||||
|
newClient = clientService.generateClientSecret(newClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't issue tokens to this client
|
||||||
|
newClient.setAccessTokenValiditySeconds(0);
|
||||||
|
newClient.setIdTokenValiditySeconds(0);
|
||||||
|
newClient.setRefreshTokenValiditySeconds(0);
|
||||||
|
|
||||||
|
// clear out unused fields
|
||||||
|
newClient.setDefaultACRvalues(new HashSet<String>());
|
||||||
|
newClient.setDefaultMaxAge(null);
|
||||||
|
newClient.setIdTokenEncryptedResponseAlg(null);
|
||||||
|
newClient.setIdTokenEncryptedResponseEnc(null);
|
||||||
|
newClient.setIdTokenSignedResponseAlg(null);
|
||||||
|
newClient.setInitiateLoginUri(null);
|
||||||
|
newClient.setPostLogoutRedirectUri(null);
|
||||||
|
newClient.setRequestObjectSigningAlg(null);
|
||||||
|
newClient.setRequireAuthTime(null);
|
||||||
|
newClient.setReuseRefreshToken(false);
|
||||||
|
newClient.setSectorIdentifierUri(null);
|
||||||
|
newClient.setSubjectType(null);
|
||||||
|
newClient.setUserInfoEncryptedResponseAlg(null);
|
||||||
|
newClient.setUserInfoEncryptedResponseEnc(null);
|
||||||
|
newClient.setUserInfoSignedResponseAlg(null);
|
||||||
|
|
||||||
|
// this client has been dynamically registered (obviously)
|
||||||
|
newClient.setDynamicallyRegistered(true);
|
||||||
|
|
||||||
|
// this client has access to the introspection endpoint
|
||||||
|
newClient.setAllowIntrospection(true);
|
||||||
|
|
||||||
|
// now save it
|
||||||
|
try {
|
||||||
|
ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
|
||||||
|
|
||||||
|
// generate the registration access token
|
||||||
|
OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient);
|
||||||
|
tokenService.saveAccessToken(token);
|
||||||
|
|
||||||
|
// send it all out to the view
|
||||||
|
|
||||||
|
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||||
|
m.addAttribute("client", registered);
|
||||||
|
m.addAttribute("code", HttpStatus.CREATED); // http 201
|
||||||
|
|
||||||
|
return "clientInformationResponseView";
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
logger.error("Unsupported encoding", e);
|
||||||
|
m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
return "httpCodeView";
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("Couldn't save client", e);
|
||||||
|
m.addAttribute("code", HttpStatus.BAD_REQUEST);
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// didn't parse, this is a bad request
|
||||||
|
logger.error("registerNewClient failed; submitted JSON is malformed");
|
||||||
|
m.addAttribute("code", HttpStatus.BAD_REQUEST); // http 400
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the meta information for a client.
|
||||||
|
* @param clientId
|
||||||
|
* @param m
|
||||||
|
* @param auth
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||||
|
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json")
|
||||||
|
public String readResourceConfiguration(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
|
||||||
|
|
||||||
|
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||||
|
|
||||||
|
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||||
|
|
||||||
|
|
||||||
|
// we return the token that we got in
|
||||||
|
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||||
|
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
|
||||||
|
|
||||||
|
try {
|
||||||
|
RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||||
|
|
||||||
|
// send it all out to the view
|
||||||
|
m.addAttribute("client", registered);
|
||||||
|
m.addAttribute("code", HttpStatus.OK); // http 200
|
||||||
|
|
||||||
|
return "clientInformationResponseView";
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
logger.error("Unsupported encoding", e);
|
||||||
|
m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// client mismatch
|
||||||
|
logger.error("readResourceConfiguration failed, client ID mismatch: "
|
||||||
|
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||||
|
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the metainformation for a given client.
|
||||||
|
* @param clientId
|
||||||
|
* @param jsonString
|
||||||
|
* @param m
|
||||||
|
* @param auth
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||||
|
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, produces = "application/json", consumes = "application/json")
|
||||||
|
public String updateProtectedResource(@PathVariable("id") String clientId, @RequestBody String jsonString, Model m, OAuth2Authentication auth) {
|
||||||
|
|
||||||
|
|
||||||
|
ClientDetailsEntity newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||||
|
ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
|
||||||
|
|
||||||
|
if (newClient != null && oldClient != null // we have an existing client and the new one parsed
|
||||||
|
&& oldClient.getClientId().equals(auth.getOAuth2Request().getClientId()) // the client passed in the URI matches the one in the auth
|
||||||
|
&& oldClient.getClientId().equals(newClient.getClientId()) // the client passed in the body matches the one in the URI
|
||||||
|
) {
|
||||||
|
|
||||||
|
// a client can't ask to update its own client secret to any particular value
|
||||||
|
newClient.setClientSecret(oldClient.getClientSecret());
|
||||||
|
|
||||||
|
// we need to copy over all of the local and SECOAUTH fields
|
||||||
|
newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds());
|
||||||
|
newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds());
|
||||||
|
newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds());
|
||||||
|
newClient.setDynamicallyRegistered(true); // it's still dynamically registered
|
||||||
|
newClient.setAllowIntrospection(oldClient.isAllowIntrospection());
|
||||||
|
newClient.setAuthorities(oldClient.getAuthorities());
|
||||||
|
newClient.setClientDescription(oldClient.getClientDescription());
|
||||||
|
newClient.setCreatedAt(oldClient.getCreatedAt());
|
||||||
|
newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken());
|
||||||
|
|
||||||
|
// set of scopes that are OK for clients to dynamically register for
|
||||||
|
Set<SystemScope> dynScopes = scopeService.getDynReg();
|
||||||
|
|
||||||
|
// scopes that the client is asking for
|
||||||
|
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
|
||||||
|
|
||||||
|
// the scopes that the client can have must be a subset of the dynamically allowed scopes
|
||||||
|
Set<SystemScope> allowedScopes = Sets.intersection(dynScopes, requestedScopes);
|
||||||
|
|
||||||
|
// make sure that the client doesn't ask for scopes it can't have
|
||||||
|
newClient.setScope(scopeService.toStrings(allowedScopes));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// save the client
|
||||||
|
ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
|
||||||
|
|
||||||
|
// we return the token that we got in
|
||||||
|
// TODO: rotate this after some set amount of time
|
||||||
|
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||||
|
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
|
||||||
|
|
||||||
|
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||||
|
|
||||||
|
// send it all out to the view
|
||||||
|
m.addAttribute("client", registered);
|
||||||
|
m.addAttribute("code", HttpStatus.OK); // http 200
|
||||||
|
|
||||||
|
return "clientInformationResponseView";
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("Couldn't save client", e);
|
||||||
|
m.addAttribute("code", HttpStatus.BAD_REQUEST);
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
logger.error("Unsupported encoding", e);
|
||||||
|
m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// client mismatch
|
||||||
|
logger.error("readClientConfiguration failed, client ID mismatch: "
|
||||||
|
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||||
|
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the indicated client from the system.
|
||||||
|
* @param clientId
|
||||||
|
* @param m
|
||||||
|
* @param auth
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||||
|
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json")
|
||||||
|
public String deleteResource(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
|
||||||
|
|
||||||
|
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||||
|
|
||||||
|
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||||
|
|
||||||
|
clientService.deleteClient(client);
|
||||||
|
|
||||||
|
m.addAttribute("code", HttpStatus.NO_CONTENT); // http 204
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
} else {
|
||||||
|
// client mismatch
|
||||||
|
logger.error("readClientConfiguration failed, client ID mismatch: "
|
||||||
|
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||||
|
m.addAttribute("code", HttpStatus.FORBIDDEN); // http 403
|
||||||
|
|
||||||
|
return "httpCodeView";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue