/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.client;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.approval.ApprovalStore;
import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode;
import org.cloudfoundry.identity.uaa.error.UaaException;
import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification;
import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest;
import org.cloudfoundry.identity.uaa.resources.ActionResult;
import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper;
import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager;
import org.cloudfoundry.identity.uaa.resources.ResourceMonitor;
import org.cloudfoundry.identity.uaa.resources.SearchResults;
import org.cloudfoundry.identity.uaa.resources.SearchResultsFactory;
import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper;
import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor;
import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor;
import org.cloudfoundry.identity.uaa.util.UaaPagingUtils;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.support.MetricType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Controller for listing and manipulating OAuth2 clients.
*/
@Controller
@ManagedResource
public class ClientAdminEndpoints implements InitializingBean, ApplicationEventPublisherAware {
private static final String SCIM_CLIENTS_SCHEMA_URI = "http://cloudfoundry.org/schema/scim/oauth-clients-1.0";
private final Log logger = LogFactory.getLog(getClass());
private ClientServicesExtension clientRegistrationService;
private QueryableResourceManager<ClientDetails> clientDetailsService;
private ResourceMonitor<ClientDetails> clientDetailsResourceMonitor;
private AttributeNameMapper attributeNameMapper = new SimpleAttributeNameMapper(
Collections.<String, String> emptyMap());
private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor();
private final Map<String, AtomicInteger> errorCounts = new ConcurrentHashMap<>();
private AtomicInteger clientUpdates = new AtomicInteger();
private AtomicInteger clientDeletes = new AtomicInteger();
private AtomicInteger clientSecretChanges = new AtomicInteger();
private ClientDetailsValidator clientDetailsValidator;
private ClientDetailsValidator restrictedScopesValidator;
private ApprovalStore approvalStore;
private AuthenticationManager authenticationManager;
private ApplicationEventPublisher publisher;
public ClientDetailsValidator getRestrictedScopesValidator() {
return restrictedScopesValidator;
}
public void setRestrictedScopesValidator(ClientDetailsValidator restrictedScopesValidator) {
this.restrictedScopesValidator = restrictedScopesValidator;
}
public ApprovalStore getApprovalStore() {
return approvalStore;
}
public void setApprovalStore(ApprovalStore approvalStore) {
this.approvalStore = approvalStore;
}
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setAttributeNameMapper(AttributeNameMapper attributeNameMapper) {
this.attributeNameMapper = attributeNameMapper;
}
/**
* @param clientRegistrationService the clientRegistrationService to set
*/
public void setClientRegistrationService(ClientServicesExtension clientRegistrationService) {
this.clientRegistrationService = clientRegistrationService;
}
/**
* @param clientDetailsService the clientDetailsService to set
*/
public void setClientDetailsService(QueryableResourceManager<ClientDetails> clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) {
this.securityContextAccessor = securityContextAccessor;
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Client Registration Count")
public int getTotalClients() {
return clientDetailsResourceMonitor.getTotalCount();
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Client Update Count (Since Startup)")
public int getClientUpdates() {
return clientUpdates.get();
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Client Delete Count (Since Startup)")
public int getClientDeletes() {
return clientDeletes.get();
}
@ManagedMetric(metricType = MetricType.COUNTER, displayName = "Client Secret Change Count (Since Startup)")
public int getClientSecretChanges() {
return clientSecretChanges.get();
}
@ManagedMetric(displayName = "Errors Since Startup")
public Map<String, AtomicInteger> getErrorCounts() {
return errorCounts;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(clientRegistrationService != null, "A ClientRegistrationService must be provided");
Assert.state(clientDetailsService != null, "A ClientDetailsService must be provided");
Assert.state(clientDetailsValidator != null, "A ClientDetailsValidator must be provided");
}
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.GET)
@ResponseBody
public ClientDetails getClientDetails(@PathVariable String client) throws Exception {
try {
return removeSecret(clientDetailsService.retrieve(client));
} catch (InvalidClientException e) {
throw new NoSuchClientException("No such client: " + client);
} catch (BadClientCredentialsException e) {
// Defensive check, in case the clientDetailsService starts throwing
// these instead
throw new NoSuchClientException("No such client: " + client);
}
}
@RequestMapping(value = "/oauth/clients", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public ClientDetails createClientDetails(@RequestBody BaseClientDetails client) throws Exception {
ClientDetails details = clientDetailsValidator.validate(client, Mode.CREATE);
ClientDetails ret = removeSecret(clientDetailsService.create(details));
return ret;
}
@RequestMapping(value = "/oauth/clients/restricted", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public List<String> getRestrictedClientScopes() throws Exception {
if (restrictedScopesValidator instanceof RestrictUaaScopesClientValidator) {
return ((RestrictUaaScopesClientValidator)restrictedScopesValidator).getUaaScopes().getUaaScopes();
} else {
return null;
}
}
@RequestMapping(value = "/oauth/clients/restricted", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public ClientDetails createRestrictedClientDetails(@RequestBody BaseClientDetails client) throws Exception {
getRestrictedScopesValidator().validate(client, Mode.CREATE);
return createClientDetails(client);
}
@RequestMapping(value = "/oauth/clients/tx", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
@Transactional
public ClientDetails[] createClientDetailsTx(@RequestBody BaseClientDetails[] clients) throws Exception {
if (clients==null || clients.length==0) {
throw new NoSuchClientException("Message body does not contain any clients.");
}
ClientDetails[] results = new ClientDetails[clients.length];
for (int i=0; i<clients.length; i++) {
results[i] = clientDetailsValidator.validate(clients[i], Mode.CREATE);
}
return doInsertClientDetails(results);
}
protected ClientDetails[] doInsertClientDetails(ClientDetails[] details) {
for (int i=0; i<details.length; i++) {
details[i] = clientDetailsService.create(details[i]);
details[i] = removeSecret(details[i]);
}
return details;
}
@RequestMapping(value = "/oauth/clients/tx", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
@Transactional
@ResponseBody
public ClientDetails[] updateClientDetailsTx(@RequestBody BaseClientDetails[] clients) throws Exception {
if (clients==null || clients.length==0) {
throw new InvalidClientDetailsException("No clients specified for update.");
}
ClientDetails[] details = new ClientDetails[clients.length];
for (int i=0; i<clients.length; i++) {
ClientDetails client = clients[i];
ClientDetails existing = getClientDetails(client.getClientId());
if (existing==null) {
throw new NoSuchClientException("Client "+client.getClientId()+" does not exist");
} else {
details[i] = syncWithExisting(existing, client);
}
details[i] = clientDetailsValidator.validate(details[i], Mode.MODIFY);
}
return doProcessUpdates(details);
}
protected ClientDetails[] doProcessUpdates(ClientDetails[] details) {
ClientDetails[] result = new ClientDetails[details.length];
for (int i=0; i<result.length; i++) {
clientRegistrationService.updateClientDetails(details[i]);
clientUpdates.incrementAndGet();
result[i] = removeSecret(details[i]);
}
return result;
}
@RequestMapping(value = "/oauth/clients/restricted/{client}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ClientDetails updateRestrictedClientDetails(@RequestBody BaseClientDetails client,
@PathVariable("client") String clientId) throws Exception {
getRestrictedScopesValidator().validate(client, Mode.MODIFY);
return updateClientDetails(client, clientId);
}
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ClientDetails updateClientDetails(@RequestBody BaseClientDetails client,
@PathVariable("client") String clientId) throws Exception {
Assert.state(clientId.equals(client.getClientId()),
String.format("The client id (%s) does not match the URL (%s)", client.getClientId(), clientId));
ClientDetails details = client;
try {
ClientDetails existing = getClientDetails(clientId);
if (existing==null) {
//TODO - should we proceed? Previous code did by throwing a NPE and logging a warning
logger.warn("Couldn't fetch client config, null, for client_id: " + clientId);
} else {
details = syncWithExisting(existing, client);
}
} catch (Exception e) {
logger.warn("Couldn't fetch client config for client_id: " + clientId, e);
}
details = clientDetailsValidator.validate(details, Mode.MODIFY);
clientRegistrationService.updateClientDetails(details);
clientUpdates.incrementAndGet();
return removeSecret(clientDetailsService.retrieve(clientId));
}
@RequestMapping(value = "/oauth/clients/{client}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ClientDetails removeClientDetails(@PathVariable String client) throws Exception {
ClientDetails details = clientDetailsService.retrieve(client);
doProcessDeletes(new ClientDetails[]{details});
return removeSecret(details);
}
@RequestMapping(value = "/oauth/clients/tx/delete", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
@Transactional
@ResponseBody
public ClientDetails[] removeClientDetailsTx(@RequestBody BaseClientDetails[] details) throws Exception {
ClientDetails[] result = new ClientDetails[details.length];
for (int i=0; i<result.length; i++) {
result[i] = clientDetailsService.retrieve(details[i].getClientId());
}
return doProcessDeletes(result);
}
@RequestMapping(value = "/oauth/clients/tx/modify", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
@Transactional
@ResponseBody
public ClientDetailsModification[] modifyClientDetailsTx(@RequestBody ClientDetailsModification[] details) throws Exception {
ClientDetailsModification[] result = new ClientDetailsModification[details.length];
for (int i=0; i<result.length; i++) {
if (ClientDetailsModification.ADD.equals(details[i].getAction())) {
ClientDetails client = clientDetailsValidator.validate(details[i], Mode.CREATE);
clientRegistrationService.addClientDetails(client);
clientUpdates.incrementAndGet();
result[i] = new ClientDetailsModification(clientDetailsService.retrieve(details[i].getClientId()));
} else if (ClientDetailsModification.DELETE.equals(details[i].getAction())) {
result[i] = new ClientDetailsModification(clientDetailsService.retrieve(details[i].getClientId()));
doProcessDeletes(new ClientDetails[]{result[i]});
result[i].setApprovalsDeleted(true);
} else if (ClientDetailsModification.UPDATE.equals(details[i].getAction())) {
result[i] = updateClientNotSecret(details[i]);
} else if (ClientDetailsModification.UPDATE_SECRET.equals(details[i].getAction())) {
boolean approvalsDeleted = updateClientSecret(details[i]);
result[i] = updateClientNotSecret(details[i]);
result[i].setApprovalsDeleted(approvalsDeleted);
} else if (ClientDetailsModification.SECRET.equals(details[i].getAction())) {
boolean approvalsDeleted = updateClientSecret(details[i]);
result[i] = details[i];
result[i].setApprovalsDeleted(approvalsDeleted);
} else {
throw new InvalidClientDetailsException("Invalid action.");
}
result[i].setAction(details[i].getAction());
result[i].setClientSecret(null);
}
return result;
}
private ClientDetailsModification updateClientNotSecret(ClientDetailsModification c) {
ClientDetailsModification result = new ClientDetailsModification(clientDetailsService.retrieve(c.getClientId()));
ClientDetails client = clientDetailsValidator.validate(c, Mode.MODIFY);
clientRegistrationService.updateClientDetails(client);
clientUpdates.incrementAndGet();
return result;
}
private boolean updateClientSecret(ClientDetailsModification detail) {
boolean deleteApprovals = !(authenticateClient(detail.getClientId(), detail.getClientSecret()));
if (deleteApprovals) {
clientRegistrationService.updateClientSecret(detail.getClientId(), detail.getClientSecret());
deleteApprovals(detail.getClientId());
detail.setApprovalsDeleted(true);
}
return deleteApprovals;
}
@RequestMapping(value = "/oauth/clients/tx/secret", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
@Transactional
@ResponseBody
public ClientDetailsModification[] changeSecretTx(@RequestBody SecretChangeRequest[] change) {
ClientDetailsModification[] clientDetails = new ClientDetailsModification[change.length];
String clientId=null;
try {
for (int i=0; i<change.length; i++) {
clientId = change[i].getClientId();
clientDetails[i] = new ClientDetailsModification(clientDetailsService.retrieve(clientId));
boolean oldPasswordOk = authenticateClient(clientId, change[i].getOldSecret());
clientRegistrationService.updateClientSecret(clientId, change[i].getSecret());
if (!oldPasswordOk) {
deleteApprovals(clientId);
clientDetails[i].setApprovalsDeleted(true);
}
clientDetails[i] = removeSecret(clientDetails[i]);
}
} catch (InvalidClientException e) {
throw new NoSuchClientException("No such client: " + clientId);
}
clientSecretChanges.getAndAdd(change.length);
return clientDetails;
}
protected ClientDetails[] doProcessDeletes(ClientDetails[] details) {
ClientDetailsModification[] result = new ClientDetailsModification[details.length];
for (int i=0; i<details.length; i++) {
publish(new EntityDeletedEvent<>(details[i], SecurityContextHolder.getContext().getAuthentication()));
clientDeletes.incrementAndGet();
result[i] = removeSecret(details[i]);
result[i].setApprovalsDeleted(true);
}
return result;
}
protected void deleteApprovals(String clientId) {
if (approvalStore!=null) {
approvalStore.revokeApprovalsForClient(clientId);
} else {
throw new UnsupportedOperationException("No approval store configured on "+getClass().getName());
}
}
@RequestMapping(value = "/oauth/clients", method = RequestMethod.GET)
@ResponseBody
public SearchResults<?> listClientDetails(
@RequestParam(value = "attributes", required = false) String attributesCommaSeparated,
@RequestParam(required = false, defaultValue = "client_id pr") String filter,
@RequestParam(required = false, defaultValue = "client_id") String sortBy,
@RequestParam(required = false, defaultValue = "ascending") String sortOrder,
@RequestParam(required = false, defaultValue = "1") int startIndex,
@RequestParam(required = false, defaultValue = "100") int count) throws Exception {
List<ClientDetails> result = new ArrayList<ClientDetails>();
List<ClientDetails> clients;
try {
clients = clientDetailsService.query(filter, sortBy, "ascending".equalsIgnoreCase(sortOrder));
if (count > clients.size()) {
count = clients.size();
}
} catch (IllegalArgumentException e) {
String msg = "Invalid filter expression: [" + filter + "]";
if (StringUtils.hasText(sortBy)) {
msg += " [" +sortBy+"]";
}
throw new UaaException(msg, HttpStatus.BAD_REQUEST.value());
}
for (ClientDetails client : UaaPagingUtils.subList(clients, startIndex, count)) {
result.add(removeSecret(client));
}
if (!StringUtils.hasLength(attributesCommaSeparated)) {
return new SearchResults<ClientDetails>(Arrays.asList(SCIM_CLIENTS_SCHEMA_URI), result, startIndex, count,
clients.size());
}
String[] attributes = attributesCommaSeparated.split(",");
try {
return SearchResultsFactory.buildSearchResultFrom(result, startIndex, count, clients.size(), attributes,
attributeNameMapper, Arrays.asList(SCIM_CLIENTS_SCHEMA_URI));
} catch (SpelParseException e) {
throw new UaaException("Invalid attributes: [" + attributesCommaSeparated + "]",
HttpStatus.BAD_REQUEST.value());
} catch (SpelEvaluationException e) {
throw new UaaException("Invalid attributes: [" + attributesCommaSeparated + "]",
HttpStatus.BAD_REQUEST.value());
}
}
@RequestMapping(value = "/oauth/clients/{client_id}/secret", method = RequestMethod.PUT)
@ResponseBody
public ActionResult changeSecret(@PathVariable String client_id, @RequestBody SecretChangeRequest change) {
ClientDetails clientDetails;
try {
clientDetails = clientDetailsService.retrieve(client_id);
} catch (InvalidClientException e) {
throw new NoSuchClientException("No such client: " + client_id);
}
try {
checkPasswordChangeIsAllowed(clientDetails, change.getOldSecret());
} catch (IllegalStateException e) {
throw new InvalidClientDetailsException(e.getMessage());
}
ActionResult result;
switch (change.getChangeMode()){
case ADD :
if(!validateCurrentClientSecretAdd(clientDetails.getClientSecret())) {
throw new InvalidClientDetailsException("client secret is either empty or client already has two secrets.");
}
clientRegistrationService.addClientSecret(client_id, change.getSecret());
result = new ActionResult("ok", "Secret is added");
break;
case DELETE :
if(!validateCurrentClientSecretDelete(clientDetails.getClientSecret())) {
throw new InvalidClientDetailsException("client secret is either empty or client has only one secret.");
}
clientRegistrationService.deleteClientSecret(client_id);
result = new ActionResult("ok", "Secret is deleted");
break;
default:
clientRegistrationService.updateClientSecret(client_id, change.getSecret());
result = new ActionResult("ok", "secret updated");
}
clientSecretChanges.incrementAndGet();
return result;
}
private boolean validateCurrentClientSecretAdd(String clientSecret) {
if(clientSecret != null && clientSecret.split(" ").length != 1){
return false;
}
return true;
}
private boolean validateCurrentClientSecretDelete(String clientSecret) {
if(clientSecret != null && clientSecret.split(" ").length == 2){
return true;
}
return false;
}
@ExceptionHandler(InvalidClientDetailsException.class)
public ResponseEntity<InvalidClientDetailsException> handleInvalidClientDetails(InvalidClientDetailsException e) {
incrementErrorCounts(e);
return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(NoSuchClientException.class)
public ResponseEntity<Void> handleNoSuchClient(NoSuchClientException e) {
incrementErrorCounts(e);
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ClientAlreadyExistsException.class)
public ResponseEntity<InvalidClientDetailsException> handleClientAlreadyExists(ClientAlreadyExistsException e) {
incrementErrorCounts(e);
return new ResponseEntity<>(new InvalidClientDetailsException(e.getMessage()),
HttpStatus.CONFLICT);
}
private void incrementErrorCounts(Exception e) {
String series = UaaStringUtils.getErrorName(e);
errorCounts.computeIfAbsent(series, k -> new AtomicInteger()).incrementAndGet();
}
private void checkPasswordChangeIsAllowed(ClientDetails clientDetails, String oldSecret) {
if (!securityContextAccessor.isClient()) {
// Trusted client (not acting on behalf of user)
throw new IllegalStateException("Only a client can change client secret");
}
String clientId = clientDetails.getClientId();
// Call is by client
String currentClientId = securityContextAccessor.getClientId();
if (!securityContextAccessor.isAdmin() && !securityContextAccessor.getScopes().contains("clients.admin")) {
if (!clientId.equals(currentClientId)) {
logger.warn("Client with id " + currentClientId + " attempting to change password for client "
+ clientId);
// TODO: This should be audited when we have non-authentication
// events in the log
throw new IllegalStateException("Bad request. Not permitted to change another client's secret");
}
// Client is changing their own secret, old password is required
if (!authenticateClient(clientId, oldSecret)) {
throw new IllegalStateException("Previous secret is required and must be valid");
}
}
}
private boolean authenticateClient(String clientId, String clientSecret) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(clientId,clientSecret);
try {
HttpServletRequest curRequest =
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
if (curRequest != null) {
authentication.setDetails(new UaaAuthenticationDetails(curRequest, clientId));
}
}catch (IllegalStateException x) {
//ignore - means no thread bound request found
}
try {
Authentication auth = authenticationManager.authenticate(authentication);
return auth.isAuthenticated();
} catch (AuthenticationException e) {
return false;
} catch (Exception e) {
logger.debug("Unable to authenticate/validate "+clientId, e);
return false;
}
}
private ClientDetailsModification removeSecret(ClientDetails client) {
if (client == null) {
return null;
}
ClientDetailsModification details = new ClientDetailsModification(client);
details.setClientSecret(null);
return details;
}
private ClientDetails syncWithExisting(ClientDetails existing, ClientDetails input) {
BaseClientDetails details = new BaseClientDetails(input);
if (input instanceof BaseClientDetails) {
BaseClientDetails baseInput = (BaseClientDetails)input;
if (baseInput.getAutoApproveScopes()!=null) {
details.setAutoApproveScopes(baseInput.getAutoApproveScopes());
} else {
details.setAutoApproveScopes(new HashSet<String>());
if (existing instanceof BaseClientDetails) {
BaseClientDetails existingDetails = (BaseClientDetails)existing;
if (existingDetails.getAutoApproveScopes()!=null) {
for (String scope : existingDetails.getAutoApproveScopes()) {
details.getAutoApproveScopes().add(scope);
}
}
}
}
}
if (details.getAccessTokenValiditySeconds() == null) {
details.setAccessTokenValiditySeconds(existing.getAccessTokenValiditySeconds());
}
if (details.getRefreshTokenValiditySeconds() == null) {
details.setRefreshTokenValiditySeconds(existing.getRefreshTokenValiditySeconds());
}
if (details.getAuthorities() == null || details.getAuthorities().isEmpty()) {
details.setAuthorities(existing.getAuthorities());
}
if (details.getAuthorizedGrantTypes() == null || details.getAuthorizedGrantTypes().isEmpty()) {
details.setAuthorizedGrantTypes(existing.getAuthorizedGrantTypes());
}
if (details.getRegisteredRedirectUri() == null || details.getRegisteredRedirectUri().isEmpty()) {
details.setRegisteredRedirectUri(existing.getRegisteredRedirectUri());
}
if (details.getResourceIds() == null || details.getResourceIds().isEmpty()) {
details.setResourceIds(existing.getResourceIds());
}
if (details.getScope() == null || details.getScope().isEmpty()) {
details.setScope(existing.getScope());
}
Map<String, Object> additionalInformation = new HashMap<String, Object>(existing.getAdditionalInformation());
additionalInformation.putAll(input.getAdditionalInformation());
for (String key : Collections.unmodifiableSet(additionalInformation.keySet())) {
if (additionalInformation.get(key) == null) {
additionalInformation.remove(key);
}
}
details.setAdditionalInformation(additionalInformation);
return details;
}
public void setClientDetailsValidator(ClientAdminEndpointsValidator clientDetailsValidator) {
this.clientDetailsValidator = clientDetailsValidator;
}
public ClientDetailsValidator getClientDetailsValidator() {
return clientDetailsValidator;
}
public void setClientDetailsResourceMonitor(ResourceMonitor<ClientDetails> clientDetailsResourceMonitor) {
this.clientDetailsResourceMonitor = clientDetailsResourceMonitor;
}
public void publish(ApplicationEvent event) {
if (publisher!=null) {
publisher.publishEvent(event);
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
publisher = applicationEventPublisher;
}
}