org.cloudfoundry.identity.client.UaaContextFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cloudfoundry-identity-client-lib Show documentation
Show all versions of cloudfoundry-identity-client-lib Show documentation
Cloud Foundry User Account and Authentication
/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] 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.client;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.cloudfoundry.identity.client.token.TokenRequest;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpMessageConverterExtractor;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import javax.net.ssl.SSLContext;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static java.lang.String.format;
import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE;
import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD_WITH_PASSCODE;
import static org.springframework.security.oauth2.common.AuthenticationScheme.header;
public class UaaContextFactory {
/**
* UAA Base URI
*/
private final URI uaaURI;
/**
* Instantiates a context factory to authenticate against the UAA
* @param uaaURI the UAA base URI
*/
private UaaContextFactory(URI uaaURI) {
this.uaaURI = uaaURI;
}
private String tokenPath = "/oauth/token";
private String authorizePath = "/oauth/authorize";
/**
* Instantiates a context factory to authenticate against the UAA
* The default token path, /oauth/token, and authorize path, /oauth/authorize are set.
* @param uaaURI the UAA base URI
*/
public static UaaContextFactory factory(URI uaaURI) {
return new UaaContextFactory(uaaURI);
}
/**
* Sets the token endpoint path. If not invoked, the default is /oauth/token
* @param path the path for the token endpoint.
* @return this mutable object
*/
public UaaContextFactory tokenPath(String path) {
this.tokenPath = path;
return this;
}
/**
* Sets the authorize endpoint path. If not invoked, the default is /oauth/authorize
* @param path the path for the authorize endpoint.
* @return this mutable object
*/
public UaaContextFactory authorizePath(String path) {
this.authorizePath = path;
return this;
}
/**
* Returns the authorize URI
* @return the UAA authorization URI
*/
public URI getAuthorizeUri() {
UriComponentsBuilder authorizationURI = UriComponentsBuilder.newInstance();
authorizationURI.uri(uaaURI);
authorizationURI.path(authorizePath);
return authorizationURI.build().toUri();
}
/**
* Returns the URI
* @return
*/
public URI getTokenUri() {
UriComponentsBuilder tokenURI = UriComponentsBuilder.newInstance();
tokenURI.uri(uaaURI);
tokenURI.path(tokenPath);
return tokenURI.build().toUri();
}
/**
* Creates a new {@link TokenRequest} object.
* The object will have the token an authorize endpoints already configured.
* @return the new token request that can be used for an access token request.
*/
public TokenRequest tokenRequest() {
return new TokenRequest(getTokenUri(), getAuthorizeUri());
}
/**
* Authenticates the client and optionally the user and retrieves an access token
* Token request must be valid, see {@link TokenRequest#isValid()}
* @param request - a fully configured token request
* @return an authenticated UAA context with
* @throws NullPointerException if the request object is null
* @throws IllegalArgumentException if the token request is invalid
*/
public UaaContext authenticate(TokenRequest request) {
if (request == null) {
throw new NullPointerException(TokenRequest.class.getName() + " cannot be null.");
}
if (!request.isValid()) {
throw new IllegalArgumentException("Invalid token request.");
}
switch (request.getGrantType()) {
case CLIENT_CREDENTIALS: return authenticateClientCredentials(request);
case PASSWORD:
case PASSWORD_WITH_PASSCODE: return authenticatePassword(request);
case AUTHORIZATION_CODE: return authenticateAuthCode(request);
case AUTHORIZATION_CODE_WITH_TOKEN: return authenticateAuthCodeWithToken(request);
case FETCH_TOKEN_FROM_CODE: return fetchTokenFromCode(request);
case SAML2_BEARER: return authenticateSaml2BearerAssertion(request);
default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType());
}
}
protected UaaContext fetchTokenFromCode(final TokenRequest request) {
String clientBasicAuth = getClientBasicAuthHeader(request);
RestTemplate template = new RestTemplate();
if (request.isSkipSslValidation()) {
template.setRequestFactory(getNoValidatingClientHttpRequestFactory());
}
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, clientBasicAuth);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap form = new LinkedMultiValueMap<>();
form.add(OAuth2Utils.GRANT_TYPE, "authorization_code");
form.add(OAuth2Utils.REDIRECT_URI, request.getRedirectUri().toString());
String responseType = "token";
if (request.wantsIdToken()) {
responseType += " id_token";
}
form.add(OAuth2Utils.RESPONSE_TYPE, responseType);
form.add("code", request.getAuthorizationCode());
ResponseEntity token = template.exchange(request.getTokenEndpoint(), HttpMethod.POST, new HttpEntity<>(form, headers), CompositeAccessToken.class);
return new UaaContextImpl(request, null, token.getBody());
}
/**
* Not yet implemented
* @param tokenRequest - a configured TokenRequest
* @return an authenticated {@link UaaContext}
*/
protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUri().toString());
details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString());
configureResourceDetails(tokenRequest, details);
setClientCredentials(tokenRequest, details);
setRequestScopes(tokenRequest, details);
//begin - work around for not having UI for now
DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext();
oAuth2ClientContext.getAccessTokenRequest().setStateKey(tokenRequest.getState());
oAuth2ClientContext.setPreservedState(tokenRequest.getState(), details.getPreEstablishedRedirectUri());
oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri());
//end - work around for not having UI for now
OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext);
skipSslValidation(tokenRequest, template, null);
template.getAccessToken();
throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented");
}
/**
* Performs and authorization_code grant, but uses a token to assert the user's identity.
* @param tokenRequest - a configured TokenRequest
* @return an authenticated {@link UaaContext}
*/
protected UaaContext authenticateAuthCodeWithToken(final TokenRequest tokenRequest) {
List providers = Collections.singletonList(
new AuthorizationCodeAccessTokenProvider() {
@Override
protected ResponseExtractor getResponseExtractor() {
getRestTemplate(); // force initialization
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter));
}
}
);
enhanceRequestParameters(tokenRequest, providers.get(0));
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUri().toString());
configureResourceDetails(tokenRequest, details);
setClientCredentials(tokenRequest, details);
setRequestScopes(tokenRequest, details);
details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString());
DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext();
oAuth2ClientContext.getAccessTokenRequest().setStateKey(tokenRequest.getState());
oAuth2ClientContext.setPreservedState(tokenRequest.getState(), details.getPreEstablishedRedirectUri());
oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri());
Map> headers = (Map>) oAuth2ClientContext.getAccessTokenRequest().getHeaders();
headers.put("Authorization", Arrays.asList("bearer " + tokenRequest.getAuthCodeAPIToken()));
OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext);
skipSslValidation(tokenRequest, template, providers);
OAuth2AccessToken token = template.getAccessToken();
return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token);
}
protected UaaContext authenticateSaml2BearerAssertion(final TokenRequest request) {
RestTemplate template = new RestTemplate();
if (request.isSkipSslValidation()) {
template.setRequestFactory(getNoValidatingClientHttpRequestFactory());
}
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap form = new LinkedMultiValueMap<>();
form.add(OAuth2Utils.CLIENT_ID, request.getClientId());
form.add("client_secret", request.getClientSecret());
form.add(OAuth2Utils.GRANT_TYPE, "urn:ietf:params:oauth:grant-type:saml2-bearer");
form.add("assertion", request.getAuthCodeAPIToken());
ResponseEntity token = template.exchange(request.getTokenEndpoint(), HttpMethod.POST, new HttpEntity<>(form, headers), CompositeAccessToken.class);
return new UaaContextImpl(request, null, token.getBody());
}
protected String getClientBasicAuthHeader(TokenRequest request) {
try {
byte[] autbytes = Base64.encode(format("%s:%s", request.getClientId(), request.getClientSecret()).getBytes("UTF-8"));
String base64 = new String(autbytes);
return format("Basic %s", base64);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Performs a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD authentication}
* @param tokenRequest - a configured TokenRequest
* @return an authenticated {@link UaaContext}
*/
protected UaaContext authenticatePassword(final TokenRequest tokenRequest) {
List providers = Collections.singletonList(
new ResourceOwnerPasswordAccessTokenProvider() {
@Override
protected ResponseExtractor getResponseExtractor() {
getRestTemplate(); // force initialization
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter));
}
}
);
enhanceRequestParameters(tokenRequest, providers.get(0));
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
configureResourceDetails(tokenRequest, details);
setUserCredentials(tokenRequest, details);
setClientCredentials(tokenRequest, details);
setRequestScopes(tokenRequest, details);
OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext());
skipSslValidation(tokenRequest, template, providers);
OAuth2AccessToken token = template.getAccessToken();
return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token);
}
/**
* Adds a request enhancer to the provider.
* Currently only two request parameters are being enhanced
* 1. If the {@link TokenRequest} wants an id_token the id_token token
values are added as a response_type parameter
* 2. If the {@link TokenRequest} is a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD_WITH_PASSCODE}
* the passcode
parameter will be added to the request
* @param tokenRequest the token request, expected to be a password grant
* @param provider the provider to enhance
*/
protected void enhanceRequestParameters(TokenRequest tokenRequest, OAuth2AccessTokenSupport provider) {
provider.setTokenRequestEnhancer( //add id_token to the response type if requested.
(AccessTokenRequest request,
OAuth2ProtectedResourceDetails resource,
MultiValueMap form,
HttpHeaders headers) -> {
if (tokenRequest.wantsIdToken()) {
form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token"));
}
if (tokenRequest.getGrantType()==PASSWORD_WITH_PASSCODE) {
form.put("passcode", Arrays.asList(tokenRequest.getPasscode()));
}
}
);
}
/**
* Performs a {@link org.cloudfoundry.identity.client.token.GrantType#CLIENT_CREDENTIALS authentication}
* @param request - a configured TokenRequest
* @return an authenticated {@link UaaContext}
*/
protected UaaContext authenticateClientCredentials(TokenRequest request) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
configureResourceDetails(request, details);
setClientCredentials(request, details);
setRequestScopes(request, details);
OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext());
skipSslValidation(request, template, null);
OAuth2AccessToken token = template.getAccessToken();
CompositeAccessToken result = new CompositeAccessToken(token);
return new UaaContextImpl(request, template, result);
}
/**
* Sets the token endpoint on the resource details
* Sets the authentication scheme to be {@link org.springframework.security.oauth2.common.AuthenticationScheme#header}
* @param tokenRequest the token request containing the token endpoint
* @param details the details object that will be configured
*/
protected void configureResourceDetails(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) {
details.setAuthenticationScheme(header);
details.setAccessTokenUri(tokenRequest.getTokenEndpoint().toString());
}
/**
* Sets the requested scopes on the resource details, if and only if the requested scopes are not null
* @param tokenRequest the token request containing the requested scopes, if any
* @param details the details object that will be configured
*/
protected void setRequestScopes(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) {
if (!Objects.isNull(tokenRequest.getScopes())) {
details.setScope(new LinkedList(tokenRequest.getScopes()));
}
}
/**
* Sets the client_id and client_secret on the resource details object
* @param tokenRequest the token request containing the client_id and client_secret
* @param details the details object that. will be configured
*/
protected void setClientCredentials(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) {
details.setClientId(tokenRequest.getClientId());
details.setClientSecret(tokenRequest.getClientSecret());
}
/**
* Sets the username and password on the resource details object
* @param tokenRequest the token request containing the client_id and client_secret
* @param details the details object that. will be configured
*/
protected void setUserCredentials(TokenRequest tokenRequest, ResourceOwnerPasswordResourceDetails details) {
details.setUsername(tokenRequest.getUsername());
details.setPassword(tokenRequest.getPassword());
}
/**
* If the {@link TokenRequest#isSkipSslValidation()} returns true, the rest template
* will be configured
* @param tokenRequest
* @param template
*/
protected void skipSslValidation(TokenRequest tokenRequest, OAuth2RestTemplate template, List existingProviders) {
ClientHttpRequestFactory requestFactory = null;
if (tokenRequest.isSkipSslValidation()) {
requestFactory = getNoValidatingClientHttpRequestFactory();
}
List accessTokenProviders =
existingProviders!=null ? existingProviders :
Arrays.asList(
new AuthorizationCodeAccessTokenProvider(),
new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(),
new ClientCredentialsAccessTokenProvider()
);
List providers = new ArrayList<>();
for (OAuth2AccessTokenSupport provider : accessTokenProviders) {
if (requestFactory!=null) {
provider.setRequestFactory(requestFactory);
}
providers.add((AccessTokenProvider) provider);
}
AccessTokenProviderChain chain = new AccessTokenProviderChain(providers);
template.setAccessTokenProvider(chain);
}
public static ClientHttpRequestFactory getNoValidatingClientHttpRequestFactory() {
return getNoValidatingClientHttpRequestFactory(true);
}
public static ClientHttpRequestFactory getNoValidatingClientHttpRequestFactory(boolean followRedirects) {
ClientHttpRequestFactory requestFactory;
SSLContext sslContext;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyManagementException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
//
CloseableHttpClient httpClient =
HttpClients.custom()
.setSslcontext(sslContext)
.setRedirectStrategy(
followRedirects ? new DefaultRedirectStrategy() : new RedirectStrategy() {
@Override
public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
return false;
}
@Override
public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
return null;
}
}
).build();
requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return requestFactory;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy