Add ReactiveCredHubTemplate. Update OAuth2 support to use Spring

Security 5.1.
This commit is contained in:
Scott Frederick
2018-11-05 13:07:32 -06:00
parent 09c64f6cb3
commit 71788b0a18
50 changed files with 2962 additions and 382 deletions

View File

@@ -29,10 +29,11 @@ buildscript {
}
ext {
springVersion = "5.0.10.RELEASE"
springBootVersion = "2.0.6.RELEASE"
springVersion = "5.1.2.RELEASE"
springBootVersion = "2.1.0.RELEASE"
springSecurityVersion = "5.1.1.RELEASE"
springCloudConnectorsVersion = "1.2.5.RELEASE"
reactorVersion = "Bismuth-SR13"
reactorVersion = "Californium-SR2"
junitVersion = "4.12"
mockitoVersion = "2.7.22"
@@ -44,6 +45,7 @@ ext {
'http://docs.spring.io/spring/docs/current/javadoc-api/',
] as String[]
}
ext['spring-security.version'] = springSecurityVersion
allprojects {
apply plugin: 'java'

View File

@@ -31,11 +31,10 @@ dependencies {
compile("com.fasterxml.jackson.core:jackson-databind:2.9.7")
optional("org.springframework:spring-webflux")
optional("io.projectreactor.ipc:reactor-netty")
optional("io.projectreactor.netty:reactor-netty")
optional("org.springframework.security.oauth:spring-security-oauth2:2.0.14.RELEASE") {
exclude(group: 'org.springframework')
}
optional("org.springframework.security:spring-security-config:${springSecurityVersion}")
optional("org.springframework.security:spring-security-oauth2-client:${springSecurityVersion}")
optional("org.apache.httpcomponents:httpclient:4.5.3") {
exclude(group: 'commons-logging', module: 'commons-logging')
@@ -44,6 +43,7 @@ dependencies {
optional("io.netty:netty-all:4.1.30.Final")
testImplementation("org.springframework:spring-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("junit:junit")
testImplementation("org.mockito:mockito-core")
testImplementation("org.assertj:assertj-core:${assertJVersion}")

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2017-2018 the original author or authors.
*
* 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.springframework.credhub.configuration;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import org.springframework.credhub.support.ClientOptions;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.netty.http.client.HttpClient;
import javax.net.ssl.TrustManagerFactory;
/**
* Factory for {@link ClientHttpConnector} that supports {@link ReactorClientHttpConnector}.
*
* @author Mark Paluch
* @author Scott Frederick
*/
public class ClientHttpConnectorFactory {
private static SslCertificateUtils sslCertificateUtils = new SslCertificateUtils();
/**
* Create a {@link ClientHttpConnector} for the given {@link ClientOptions}.
*
* @param options must not be {@literal null}
* @return a new {@link ClientHttpConnector}.
*/
public static ClientHttpConnector create(ClientOptions options) {
HttpClient httpClient = HttpClient.create();
if (usingCustomCerts(options)) {
TrustManagerFactory trustManagerFactory =
sslCertificateUtils.createTrustManagerFactory(options.getCaCertFiles());
httpClient.secure(sslContextSpec -> sslContextSpec
.sslContext(SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)
.trustManager(trustManagerFactory)));
} else {
httpClient.secure(sslContextSpec -> sslContextSpec
.sslContext(SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)));
}
if (options.getConnectionTimeout() != null) {
// httpClient.sslHandshakeTimeout(options.getConnectionTimeout());
// httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
// Math.toIntExact(options.getConnectionTimeout().toMillis()));
}
return new ReactorClientHttpConnector(httpClient);
}
private static boolean usingCustomCerts(ClientOptions options) {
return options.getCaCertFiles() != null;
}
}

View File

@@ -18,8 +18,15 @@ package org.springframework.credhub.configuration;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.core.ReactiveCredHubTemplate;
import org.springframework.credhub.support.ClientOptions;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
/**
* Factory for {@link CredHubTemplate} used to communicate with CredHub.
@@ -29,9 +36,34 @@ import org.springframework.http.client.ClientHttpRequestFactory;
*/
public class CredHubTemplateFactory {
/**
* Create a {@link CredHubTemplate} for interaction with a CredHub server.
*
* @param credHubProperties connection properties
* @param clientHttpRequestFactory a factory for HTTP connections
* @return a {@code CredHubTemplate}
*/
public CredHubTemplate credHubTemplate(CredHubProperties credHubProperties,
ClientHttpRequestFactory clientHttpRequestFactory) {
return new CredHubTemplate(credHubProperties.getUrl(), clientHttpRequestFactory);
return new CredHubTemplate(credHubProperties, clientHttpRequestFactory);
}
/**
* Create a {@link CredHubTemplate} for interaction with a CredHub server
* using OAuth2 for authentication.
*
* @param credHubProperties connection properties
* @param clientHttpRequestFactory a factory for HTTP connections
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientService a repository of authorized OAuth2 clients
* @return a {@code CredHubTemplate}
*/
public CredHubTemplate credHubTemplate(CredHubProperties credHubProperties,
ClientHttpRequestFactory clientHttpRequestFactory,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
return new CredHubTemplate(credHubProperties, clientHttpRequestFactory,
clientRegistrationRepository, authorizedClientService);
}
/**
@@ -43,6 +75,46 @@ public class CredHubTemplateFactory {
return ClientHttpRequestFactoryFactory.create(new ClientOptions());
}
/**
* Create a {@link ReactiveCredHubTemplate} for interaction with a CredHub server.
*
* @param credHubProperties connection properties
* @param clientHttpConnector a factory for HTTP connections
* @return a {@code ReactiveCredHubTemplate}
*/
public ReactiveCredHubTemplate credHubTemplate(CredHubProperties credHubProperties,
ClientHttpConnector clientHttpConnector) {
return new ReactiveCredHubTemplate(credHubProperties, clientHttpConnector);
}
/**
* Create a {@link ReactiveCredHubTemplate} for interaction with a CredHub server
* using OAuth2 for authentication.
*
* @param credHubProperties connection properties
* @param clientHttpConnector a factory for HTTP connections
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientRepository a repository of OAuth2 client authorizations
* @return a {@code ReactiveCredHubTemplate}
*/
public ReactiveCredHubOperations credHubTemplate(CredHubProperties credHubProperties,
ClientHttpConnector clientHttpConnector,
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
return new ReactiveCredHubTemplate(credHubProperties, clientHttpConnector,
clientRegistrationRepository, authorizedClientRepository);
}
/**
* Create a {@link ClientHttpRequestFactory}.
*
* @param clientOptions options for creating the client connection
* @return the {@link ClientHttpRequestFactory} instance.
*/
public ClientHttpConnector clientHttpConnector(ClientOptions clientOptions) {
return ClientHttpConnectorFactory.create(clientOptions);
}
/**
* Create a {@link ClientHttpRequestFactory}.
*

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.configuration;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.OAuth2CredHubTemplate;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
/**
* Factory for {@link OAuth2CredHubTemplate} used to communicate with CredHub.
*
* @author Daniel Lavoie
*/
public class OAuth2CredHubTemplateFactory {
public OAuth2CredHubTemplate credHubTemplate(OAuth2ProtectedResourceDetails resource,
CredHubProperties credHubProperties,
ClientHttpRequestFactory clientHttpRequestFactory) {
return new OAuth2CredHubTemplate(resource, credHubProperties.getUrl(),
clientHttpRequestFactory);
}
}

View File

@@ -1,140 +0,0 @@
/*
*
* * Copyright 2013-2017 the original author or authors.
* *
* * 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.springframework.credhub.core;
import static java.util.Collections.singletonList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.credhub.support.utils.JsonUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
/**
* Factory for creating a {@link RestTemplate} configured for communication with
* a CredHub server.
*
* @author Scott Frederick
* @author Daniel Lavoie
*/
class CredHubClientFactory {
/**
* Create a {@link RestTemplate} configured for communication with a CredHub server.
*
* @param baseUri the base URI for the CredHub server
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* @return a configured {@link RestTemplate}
*/
static RestTemplate createRestTemplate(String baseUri,
ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate();
configureRestTemplate(restTemplate, baseUri, clientHttpRequestFactory);
return restTemplate;
}
/**
* Configure a {@link RestTemplate} for communication with a CredHub server.
*
* @param restTemplate an existing {@link RestTemplate} to configure
* @param baseUri the base URI for the CredHub server
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
*/
static void configureRestTemplate(RestTemplate restTemplate, String baseUri,
ClientHttpRequestFactory clientHttpRequestFactory) {
restTemplate.setRequestFactory(clientHttpRequestFactory);
restTemplate.setUriTemplateHandler(createUriTemplateHandler(baseUri));
restTemplate.setMessageConverters(createMessageConverters());
restTemplate.setInterceptors(createInterceptors());
}
/**
* Create a {@link UriTemplateHandler} that prefixes all {@link RestTemplate} calls
* with the configured {@literal baseUri}.
*
* @param baseUri the base URI for the CredHub server
* @return a configured {@link UriTemplateHandler}
*/
private static DefaultUriTemplateHandler createUriTemplateHandler(String baseUri) {
DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
uriTemplateHandler.setBaseUrl(baseUri);
return uriTemplateHandler;
}
/**
* Create the {@link HttpMessageConverter}s necessary to build request to
* and parse responses from CredHub.
*
* @return the list of {@link HttpMessageConverter}s
*/
private static List<HttpMessageConverter<?>> createMessageConverters() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(3);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter(JsonUtils.buildObjectMapper()));
return messageConverters;
}
/**
* Create the {@link ClientHttpRequestInterceptor} necessary to configure requests and responses.
*
* @return the list of {@link ClientHttpRequestInterceptor}s
*/
private static List<ClientHttpRequestInterceptor> createInterceptors() {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(1);
interceptors.add(new CredHubRequestInterceptor());
return interceptors;
}
/**
* A request interceptor that sets headers common to all CredHub requests.
*/
private static class CredHubRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
HttpHeaders headers = requestWrapper.getHeaders();
headers.setAccept(singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
return execution.execute(requestWrapper, body);
}
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.credhub.core;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpStatusCodeException;
@@ -25,7 +24,7 @@ import org.springframework.web.client.HttpStatusCodeException;
*
* @author Scott Frederick
*/
public class CredHubException extends NestedRuntimeException {
public class CredHubException extends HttpStatusCodeException {
/**
* Create a new exception with the provided root cause.
*
@@ -33,8 +32,7 @@ public class CredHubException extends NestedRuntimeException {
* with CredHub
*/
public CredHubException(HttpStatusCodeException e) {
super("Error calling CredHub: " + e.getStatusCode() + ": "
+ e.getResponseBodyAsString());
super(e.getStatusCode(), e.getStatusText(), e.getResponseHeaders(), e.getResponseBodyAsByteArray(), null);
}
/**
@@ -44,6 +42,6 @@ public class CredHubException extends NestedRuntimeException {
* communicate with CredHub
*/
public CredHubException(HttpStatus statusCode) {
super("Error calling CredHub: " + statusCode);
super(statusCode);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.web.client.RestOperations;
import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
/**
* A request interceptor that sets OAuth2 bearer authentication headers to all CredHub requests.
*
* @author Scott Frederick
*/
class CredHubOAuth2RequestInterceptor implements ClientHttpRequestInterceptor {
private final ClientRegistration clientRegistration;
private final OAuth2AuthorizedClientService authorizedClientService;
private final DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient;
private final Clock clock = Clock.systemUTC();
private final Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
CredHubOAuth2RequestInterceptor(RestOperations tokenServerRestTemplate,
ClientRegistration clientRegistration,
OAuth2AuthorizedClientService authorizedClientService) {
this.clientRegistration = clientRegistration;
this.authorizedClientService = authorizedClientService;
this.clientCredentialsTokenResponseClient = createClientCredentialsTokenResponseClient(tokenServerRestTemplate);
}
/**
* Add an OAuth2 bearer token header to each request.
*
* {@inheritDoc}
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
HttpHeaders headers = requestWrapper.getHeaders();
headers.setBearerAuth(getAccessToken().getTokenValue());
return execution.execute(requestWrapper, body);
}
private OAuth2AccessToken getAccessToken() {
OAuth2AuthorizedClient client = authorizedClientService
.loadAuthorizedClient(clientRegistration.getRegistrationId(), clientRegistration.getClientId());
if (client == null || tokenExpiring(client)) {
client = authorizeClient();
}
return client.getAccessToken();
}
private OAuth2AuthorizedClient authorizeClient() {
OAuth2ClientCredentialsGrantRequest request =
new OAuth2ClientCredentialsGrantRequest(clientRegistration);
OAuth2AccessTokenResponse tokenResponse = clientCredentialsTokenResponseClient.getTokenResponse(request);
OAuth2AccessToken accessToken = tokenResponse.getAccessToken();
OAuth2RefreshToken refreshToken = tokenResponse.getRefreshToken();
OAuth2AuthorizedClient authorizedClient =
new OAuth2AuthorizedClient(clientRegistration,
clientRegistration.getClientId(),
accessToken, refreshToken);
saveAuthorizedClient(clientRegistration, accessToken, authorizedClient);
return authorizedClient;
}
private boolean tokenExpiring(OAuth2AuthorizedClient client) {
Instant now = this.clock.instant();
Instant expiresAt = client.getAccessToken().getExpiresAt();
if (expiresAt != null && now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew))) {
return true;
}
return false;
}
private void saveAuthorizedClient(ClientRegistration clientRegistration,
OAuth2AccessToken accessToken,
OAuth2AuthorizedClient authorizedClient) {
OAuth2ClientCredentialsGrantAuthenticationToken authentication =
new OAuth2ClientCredentialsGrantAuthenticationToken(clientRegistration, accessToken);
authorizedClientService.saveAuthorizedClient(authorizedClient, authentication);
}
private static DefaultClientCredentialsTokenResponseClient createClientCredentialsTokenResponseClient(RestOperations restTemplate) {
DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setRestOperations(restTemplate);
return clientCredentialsTokenResponseClient;
}
private static class OAuth2ClientCredentialsGrantAuthenticationToken extends AbstractAuthenticationToken {
private final ClientRegistration clientRegistration;
private final OAuth2AccessToken accessToken;
OAuth2ClientCredentialsGrantAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AccessToken accessToken) {
super(Collections.emptyList());
this.clientRegistration = clientRegistration;
this.accessToken = accessToken;
}
@Override
public Object getCredentials() {
return accessToken.getTokenValue();
}
@Override
public Object getPrincipal() {
return this.clientRegistration.getClientId();
}
}
}

View File

@@ -26,22 +26,12 @@ package org.springframework.credhub.core;
*/
public class CredHubProperties {
private String url;
private OAuth2 oauth2;
/**
* Create a new instance without initializing properties.
*/
public CredHubProperties() {
}
/**
* Create a new instance with the provided properties. Intended to be used internally
* for testing.
*
* @param url the base URI for the CredHub server
*/
CredHubProperties(String url) {
this.url = url;
}
/**
@@ -63,4 +53,53 @@ public class CredHubProperties {
public void setUrl(String url) {
this.url = url;
}
/**
* Get the OAuth2 properties.
*
* @return the OAuth2 properties.
*/
public OAuth2 getOauth2() {
return oauth2;
}
/**
* Set the OAuth2 properties.
*
* @param oauth2 the OAuth2 properties
*/
public void setOauth2(OAuth2 oauth2) {
this.oauth2 = oauth2;
}
/**
* Properties containing OAuth2 credentials for CredHub connectivity.
*/
public static class OAuth2 {
private String clientId;
/**
* Create a new instance without initializing properties.
*/
public OAuth2() {
}
/**
* Get the OAuth2 client ID used to authenticate with CredHub.
*
* @return the OAuth2 client ID
*/
public String getClientId() {
return clientId;
}
/**
* Set the OAuth2 client ID used to authentiate with CredHub.
*
* @param clientId the OAuth2 client ID
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import static java.util.Collections.singletonList;
import java.io.IOException;
import java.util.Arrays;
import org.springframework.credhub.support.utils.JsonUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
/**
* Factory for creating a {@link RestTemplate} configured for communication with
* a CredHub server.
*
* @author Scott Frederick
* @author Daniel Lavoie
*/
class CredHubRestTemplateFactory {
/**
* Create a {@link RestTemplate} configured for communication with a CredHub server.
*
* @param properties CredHub connection properties
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* @return a configured {@link RestTemplate}
*/
static RestTemplate createRestTemplate(CredHubProperties properties,
ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate();
configureRestTemplate(restTemplate, properties.getUrl(), clientHttpRequestFactory);
return restTemplate;
}
/**
* Create a {@link RestTemplate} configured for communication with a CredHub server.
*
* @param properties CredHub connection properties
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientService a repository of authorized OAuth2 clients
* @return a configured {@link RestTemplate}
*/
static RestTemplate createRestTemplate(CredHubProperties properties,
ClientHttpRequestFactory clientHttpRequestFactory,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
RestTemplate restTemplate = new RestTemplate();
configureRestTemplate(restTemplate, properties.getUrl(), clientHttpRequestFactory);
configureOAuth2(restTemplate, clientHttpRequestFactory,
properties.getOauth2().getClientId(),
clientRegistrationRepository,
authorizedClientService);
return restTemplate;
}
/**
* Configure a {@link RestTemplate} for communication with a CredHub server.
*
* @param restTemplate an existing {@link RestTemplate} to configure
* @param baseUri the base URI for the CredHub server
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
*/
private static void configureRestTemplate(RestTemplate restTemplate, String baseUri,
ClientHttpRequestFactory clientHttpRequestFactory) {
restTemplate.setRequestFactory(clientHttpRequestFactory);
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(baseUri));
restTemplate.getInterceptors().add(new CredHubRequestInterceptor());
restTemplate.setMessageConverters(Arrays.asList(
new ByteArrayHttpMessageConverter(),
new StringHttpMessageConverter(),
new MappingJackson2HttpMessageConverter(JsonUtils.buildObjectMapper())));
}
/**
* Configure OAuth2 features of a {@link RestTemplate}.
*
* @param restTemplate an existing {@link RestTemplate} to configure
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* @param clientId the OAuth2 client ID for authentication
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientService a repository of authorized OAuth2 clients
*/
private static void configureOAuth2(RestTemplate restTemplate,
ClientHttpRequestFactory clientHttpRequestFactory,
String clientId,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(clientId);
RestOperations tokenServerRestTemplate = createTokenServerRestTemplate(clientHttpRequestFactory);
restTemplate.getInterceptors()
.add(new CredHubOAuth2RequestInterceptor(tokenServerRestTemplate,
clientRegistration, authorizedClientService));
}
private static RestTemplate createTokenServerRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restOperations = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()));
restOperations.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
restOperations.setRequestFactory(clientHttpRequestFactory);
return restOperations;
}
/**
* A request interceptor that sets headers common to all CredHub requests.
*/
private static class CredHubRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
HttpHeaders headers = requestWrapper.getHeaders();
headers.setAccept(singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
return execution.execute(requestWrapper, body);
}
}
}

View File

@@ -29,6 +29,8 @@ import org.springframework.credhub.core.permission.CredHubPermissionTemplate;
import org.springframework.credhub.core.permissionV2.CredHubPermissionV2Operations;
import org.springframework.credhub.core.permissionV2.CredHubPermissionV2Template;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
@@ -54,22 +56,43 @@ public class CredHubTemplate implements CredHubOperations {
}
/**
* Create a new {@link CredHubTemplate} using the provided base URI and
* Create a new {@link CredHubTemplate} using the provided connection properties and
* {@link ClientHttpRequestFactory}.
*
* @param apiUriBase the base URI for the CredHub server (scheme, host, and port);
* must not be {@literal null}
* @param properties CredHub connection properties; must not be {@literal null}
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* creating new connections
*/
public CredHubTemplate(String apiUriBase, ClientHttpRequestFactory clientHttpRequestFactory) {
Assert.notNull(apiUriBase, "apiUriBase must not be null");
public CredHubTemplate(CredHubProperties properties, ClientHttpRequestFactory clientHttpRequestFactory) {
Assert.notNull(properties, "properties must not be null");
Assert.notNull(clientHttpRequestFactory, "clientHttpRequestFactory must not be null");
this.restTemplate = CredHubClientFactory.createRestTemplate(apiUriBase,
this.restTemplate = CredHubRestTemplateFactory.createRestTemplate(properties,
clientHttpRequestFactory);
}
/**
* Create a new {@link CredHubTemplate} using the provided connection properties,
* {@link ClientHttpRequestFactory}, and OAuth2 support.
*
* @param properties CredHub connection properties; must not be {@literal null}
* @param clientHttpRequestFactory the {@link ClientHttpRequestFactory} to use when
* creating new connections
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientService a repository of authorized OAuth2 clients
*/
public CredHubTemplate(CredHubProperties properties,
ClientHttpRequestFactory clientHttpRequestFactory,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
Assert.notNull(properties, "properties must not be null");
Assert.notNull(clientHttpRequestFactory, "clientHttpRequestFactory must not be null");
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository must not be null");
this.restTemplate = CredHubRestTemplateFactory.createRestTemplate(properties,
clientHttpRequestFactory, clientRegistrationRepository, authorizedClientService);
}
/**
* Get the operations for saving, retrieving, and deleting credentials.
*

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.credhub.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient;
import org.springframework.credhub.support.utils.JsonUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
/**
* Factory for creating a {@link WebClient} configured for communication with
* a CredHub server.
*
* @author Mark Paluch
* @author Scott Frederick
*/
class CredHubWebClientFactory {
/**
* Create a {@link WebClient} configured for communication with a CredHub server.
*
* @param properties CredHub connection properties
* @param clientHttpConnector the {@link ClientHttpConnector} to use when
* creating new connections
* @return a configured {@link WebClient}
*/
static WebClient createWebClient(CredHubProperties properties,
ClientHttpConnector clientHttpConnector) {
return buildWebClient(properties.getUrl(), clientHttpConnector)
.build();
}
/**
* Create a {@link WebClient} configured for communication with a CredHub server.
*
* @param properties CredHub connection properties
* @param clientHttpConnector the {@link ClientHttpConnector} to use when
* creating new connections
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientRepository a repository of OAuth2 authorized clients
* @return a configured {@link WebClient}
*/
static WebClient createWebClient(CredHubProperties properties, ClientHttpConnector clientHttpConnector,
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
tokenResponseClient.setWebClient(WebClient.builder()
.clientConnector(clientHttpConnector)
.build());
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository,
authorizedClientRepository);
oauth.setClientCredentialsTokenResponseClient(tokenResponseClient);
return buildWebClient(properties.getUrl(), clientHttpConnector)
.filter(oauth)
.defaultRequest(requestHeadersSpec ->
requestHeadersSpec.attributes(clientRegistrationId(properties.getOauth2().getClientId())))
.build();
}
private static WebClient.Builder buildWebClient(String baseUri, ClientHttpConnector clientHttpConnector) {
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
ObjectMapper mapper = JsonUtils.buildObjectMapper();
CodecConfigurer.DefaultCodecs dc = configurer.defaultCodecs();
dc.jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
dc.jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));
}).build();
return WebClient.builder()
.clientConnector(clientHttpConnector)
.baseUrl(baseUri)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.exchangeStrategies(strategies);
}
}

View File

@@ -19,6 +19,9 @@ package org.springframework.credhub.core;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class ExceptionUtils {
/**
@@ -32,4 +35,14 @@ public class ExceptionUtils {
throw new CredHubException(response.getStatusCode());
}
}
/**
* Helper method to return an appropriate error if a request to CredHub
* returns with an error code.
*
* @param response a {@link ClientResponse} returned from {@link WebClient}
*/
public static Mono<Throwable> buildError(ClientResponse response) {
return Mono.error(new CredHubException(response.statusCode()));
}
}

View File

@@ -1,32 +0,0 @@
package org.springframework.credhub.core;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.web.client.RestTemplate;
/**
* Superclass of {@link CredHubTemplate}. Provides a pre configured
* {@link OAuth2RestTemplate} for CredHub.
*
* @author Daniel Lavoie
*
*/
public class OAuth2CredHubTemplate extends CredHubTemplate {
public OAuth2CredHubTemplate(OAuth2ProtectedResourceDetails resource,
String apiUriBase, ClientHttpRequestFactory clientHttpRequestFactory) {
super(buildRestTemplate(resource, apiUriBase, clientHttpRequestFactory));
}
private static RestTemplate buildRestTemplate(OAuth2ProtectedResourceDetails resource,
String apiUriBase, ClientHttpRequestFactory clientHttpRequestFactory) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource);
CredHubClientFactory.configureRestTemplate(restTemplate, apiUriBase,
clientHttpRequestFactory);
return restTemplate;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import org.reactivestreams.Publisher;
import org.springframework.credhub.core.certificate.ReactiveCredHubCertificateOperations;
import org.springframework.credhub.core.credential.ReactiveCredHubCredentialOperations;
import org.springframework.credhub.core.info.ReactiveCredHubInfoOperations;
import org.springframework.credhub.core.interpolation.ReactiveCredHubInterpolationOperations;
import org.springframework.credhub.core.permission.ReactiveCredHubPermissionOperations;
import org.springframework.credhub.core.permissionV2.ReactiveCredHubPermissionV2Operations;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.function.Function;
/**
* Specifies the main interaction with CredHub.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubOperations {
/**
* Get the operations for saving, retrieving, and deleting credentials.
*
* @return the credentials operations
*/
ReactiveCredHubCredentialOperations credentials();
/**
* Get the operations for adding, retrieving, and deleting credential permissions.
*
* @return the permissions operations
*/
ReactiveCredHubPermissionOperations permissions();
/**
* Get the operations for adding, retrieving, and deleting credential permissions.
*
* @return the permissions operations
*/
ReactiveCredHubPermissionV2Operations permissionsV2();
/**
* Get the operations for retrieving, regenerating, and updating certificates.
*
* @return the certificates operations
*/
ReactiveCredHubCertificateOperations certificates();
/**
* Get the operations for interpolating service binding credentials.
*
* @return the interpolation operations
*/
ReactiveCredHubInterpolationOperations interpolation();
/**
* Get the operations for retrieving CredHub server information.
*
* @return the info operations
*/
ReactiveCredHubInfoOperations info();
/**
* Allow interaction with the configured {@link WebClient} not provided
* by other methods.
*
* @param callback wrapper for the callback method
* @param <V> the publisher type
* @param <T> the credential implementation type
* @return the return value from the callback method
*/
<V, T extends Publisher<V>> T doWithWebClient(Function<WebClient, ? extends T> callback);
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import org.reactivestreams.Publisher;
import org.springframework.credhub.core.certificate.ReactiveCredHubCertificateOperations;
import org.springframework.credhub.core.certificate.ReactiveCredHubCertificateTemplate;
import org.springframework.credhub.core.credential.ReactiveCredHubCredentialOperations;
import org.springframework.credhub.core.credential.ReactiveCredHubCredentialTemplate;
import org.springframework.credhub.core.info.ReactiveCredHubInfoOperations;
import org.springframework.credhub.core.info.ReactiveCredHubInfoTemplate;
import org.springframework.credhub.core.interpolation.ReactiveCredHubInterpolationOperations;
import org.springframework.credhub.core.interpolation.ReactiveCredHubInterpolationTemplate;
import org.springframework.credhub.core.permission.ReactiveCredHubPermissionOperations;
import org.springframework.credhub.core.permission.ReactiveCredHubPermissionTemplate;
import org.springframework.credhub.core.permissionV2.ReactiveCredHubPermissionV2Operations;
import org.springframework.credhub.core.permissionV2.ReactiveCredHubPermissionV2Template;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.function.Function;
/**
* Implements the main interaction with CredHub.
*
* @author Scott Frederick
*/
public class ReactiveCredHubTemplate implements ReactiveCredHubOperations {
private final WebClient webClient;
/**
* Create a new {@link ReactiveCredHubTemplate} using the provided {@link WebClient}.
* Intended for internal testing only.
*
* @param webClient the {@link WebClient} to use for interactions with CredHub
*/
public ReactiveCredHubTemplate(WebClient webClient) {
Assert.notNull(webClient, "webClient must not be null");
this.webClient = webClient;
}
/**
* Create a new {@link ReactiveCredHubTemplate} using the provided base URI and
* {@link ClientHttpRequestFactory}.
*
* @param credHubProperties connection properties for the CredHub server
* @param clientHttpConnector the {@link ClientHttpConnector} to use when
* creating new connections
*/
public ReactiveCredHubTemplate(CredHubProperties credHubProperties, ClientHttpConnector clientHttpConnector) {
Assert.notNull(credHubProperties, "credHubProperties must not be null");
Assert.notNull(clientHttpConnector, "clientHttpConnector must not be null");
this.webClient = CredHubWebClientFactory.createWebClient(credHubProperties, clientHttpConnector);
}
/**
* Create a new {@link ReactiveCredHubTemplate} using the provided base URI and
* {@link ClientHttpRequestFactory}.
* @param credHubProperties connection properties for the CredHub server
* @param clientHttpConnector the {@link ClientHttpConnector} to use when
* creating new connections
*/
public ReactiveCredHubTemplate(CredHubProperties credHubProperties, ClientHttpConnector clientHttpConnector,
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(credHubProperties, "credHubProperties must not be null");
Assert.notNull(clientHttpConnector, "clientHttpConnector must not be null");
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository must not be null");
Assert.notNull(authorizedClientRepository, "authorizedClientRepository must not be null");
this.webClient = CredHubWebClientFactory.createWebClient(credHubProperties, clientHttpConnector,
clientRegistrationRepository, authorizedClientRepository);
}
/**
* Get the operations for saving, retrieving, and deleting credentials.
*
* @return the credentials operations
*/
@Override
public ReactiveCredHubCredentialOperations credentials() {
return new ReactiveCredHubCredentialTemplate(this);
}
/**
* Get the operations for adding, retrieving, and deleting permissions from a credential.
*
* @return the permissions operations
*/
@Override
public ReactiveCredHubPermissionOperations permissions() {
return new ReactiveCredHubPermissionTemplate(this);
}
/**
* Get the operations for adding, retrieving, and deleting permissions from a credential.
*
* @return the permissions operations
*/
@Override
public ReactiveCredHubPermissionV2Operations permissionsV2() {
return new ReactiveCredHubPermissionV2Template(this);
}
/**
* Get the operations for retrieving, regenerating, and updating certificates.
*
* @return the certificates operations
*/
@Override
public ReactiveCredHubCertificateOperations certificates() {
return new ReactiveCredHubCertificateTemplate(this);
}
/**
* Get the operations for interpolating service binding credentials.
*
* @return the interpolation operations
*/
@Override
public ReactiveCredHubInterpolationOperations interpolation() {
return new ReactiveCredHubInterpolationTemplate(this);
}
/**
* Get the operations for retrieving CredHub server information.
*
* @return the info operations
*/
@Override
public ReactiveCredHubInfoOperations info() {
return new ReactiveCredHubInfoTemplate(this);
}
/**
* Allow interaction with the configured {@link WebClient} not provided
* by other methods.
*
* @param callback wrapper for the callback method
* @param <T> the credential implementation type
* @return the return value from the callback method
*/
@Override
public <V, T extends Publisher<V>> T doWithWebClient(Function<WebClient, ? extends T> callback) {
Assert.notNull(callback, "callback must not be null");
try {
return callback.apply(webClient);
}
catch (HttpStatusCodeException e) {
throw new CredHubException(e);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.certificate;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import org.springframework.credhub.support.certificate.CertificateSummary;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Specifies the interactions with CredHub to retrieve, regenerate, and update
* certificates.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubCertificateOperations {
/**
* Retrieve all certificates from CredHub.
*
* @return a collection of certificates
*/
Flux<CertificateSummary> getAll();
/**
* Retrieve a certificate using its name.
*
* @param name the name of the certificate credential; must not be {@literal null}
* @return the details of the retrieved certificate credential
*/
Mono<CertificateSummary> getByName(final CredentialName name);
/**
* Regenerate a certificate.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* and must be an ID returned by {@link #getAll()}
* or {@link #getByName(CredentialName)}
* @param setAsTransitional {@code true} to mark the certificate version transitional;
* {@code false} otherwise
* @return the details of the certificate credential
*/
Mono<CertificateCredentialDetails> regenerate(final String id, final boolean setAsTransitional);
/**
* Regenerate all certificates in CredHub that were signed by the specified certificate.
*
* @param certificateName the name of the signing certificate credential; must not be {@literal null}
* @return the names of all regenerated certificate credentials
*/
Flux<CredentialName> regenerate(CredentialName certificateName);
/**
* Make the specified version of a certificate the {@literal transitional} version.
*
* @param id the CredHub-generated ID of the certificate credential; must not be {@literal null}
* and must be an ID returned by {@link #getAll()}
* or {@link #getByName(CredentialName)}
* @param versionId the CredHub-generated ID of the version of the certificate credential that should be
* marked {@literal transitional}, or {@literal null} to indicate that no version
* is {@literal transitional}
* @return the details of the certificate credential, including all versions
*/
Flux<CertificateCredentialDetails> updateTransitionalVersion(final String id, final String versionId);
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.certificate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.certificate.CertificateCredentialDetails;
import org.springframework.credhub.support.certificate.CertificateSummary;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implements the interactions with CredHub to retrieve, regenerate, and update
* certificates.
*
* @author Scott Frederick
*/
public class ReactiveCredHubCertificateTemplate implements ReactiveCredHubCertificateOperations {
private static final String BASE_URL_PATH = "/api/v1/certificates";
private static final String NAME_URL_QUERY = BASE_URL_PATH + "?name={name}";
private static final String REGENERATE_URL_PATH = BASE_URL_PATH + "/{id}/regenerate";
private static final String UPDATE_TRANSITIONAL_URL_PATH = BASE_URL_PATH + "/{id}/update_transitional_version";
private static final String BULK_REGENERATE_URL_PATH = "/api/v1/bulk-regenerate";
private static final String TRANSITIONAL_REQUEST_FIELD = "set_as_transitional";
private static final String VERSION_REQUEST_FIELD = "version";
private static final String SIGNED_BY_REQUEST_FIELD = "signed_by";
private static final String REGENERATED_CREDENTIALS_RESPONSE_FIELD = "regenerated_credentials";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubCertificateTemplate}.
*
* @param credHubOperations the {@link ReactiveCredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubCertificateTemplate(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public Flux<CertificateSummary> getAll() {
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(BASE_URL_PATH)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(CertificateSummary.class));
}
@Override
public Mono<CertificateSummary> getByName(final CredentialName name) {
Assert.notNull(name, "certificate name must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(NAME_URL_QUERY, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(CertificateSummary.class)
.single());
}
@Override
public Mono<CertificateCredentialDetails> regenerate(final String id, final boolean setAsTransitional) {
Assert.notNull(id, "credential ID must not be null");
final ParameterizedTypeReference<CertificateCredentialDetails> ref =
new ParameterizedTypeReference<CertificateCredentialDetails>() {};
Map<String, Boolean> request = new HashMap<>(1);
request.put(TRANSITIONAL_REQUEST_FIELD, setAsTransitional);
return credHubOperations.doWithWebClient(webClient -> webClient
.put()
.uri(REGENERATE_URL_PATH, id)
.syncBody(request)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref));
}
@Override
public Flux<CredentialName> regenerate(final CredentialName certificateName) {
Assert.notNull(certificateName, "certificate name must not be null");
final ParameterizedTypeReference<Map<String, List<CredentialName>>> ref =
new ParameterizedTypeReference<Map<String, List<CredentialName>>>() {};
Map<String, Object> request = new HashMap<>(1);
request.put(SIGNED_BY_REQUEST_FIELD, certificateName.getName());
return credHubOperations.doWithWebClient(webClient -> webClient
.put()
.uri(BULK_REGENERATE_URL_PATH)
.syncBody(request)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(ref)
.flatMap(body -> Flux.fromIterable(body.get(REGENERATED_CREDENTIALS_RESPONSE_FIELD))));
}
public Flux<CertificateCredentialDetails> updateTransitionalVersion(final String id,
final String versionId) {
Assert.notNull(id, "credential ID must not be null");
Map<String, String> request = new HashMap<>(1);
request.put(VERSION_REQUEST_FIELD, versionId);
return credHubOperations.doWithWebClient(webClient -> webClient
.put()
.uri(UPDATE_TRANSITIONAL_URL_PATH, id)
.syncBody(request)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(CertificateCredentialDetails.class));
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.credential;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialRequest;
import org.springframework.credhub.support.CredentialSummary;
import org.springframework.credhub.support.ParametersRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Specifies the interactions with CredHub to save, generate, retrieve,
* and delete credentials.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubCredentialOperations {
/**
* Write a new credential to CredHub, or overwrite an existing credential with a new
* value.
*
* @param credentialRequest the credential to write to CredHub; must not be {@literal null}
* @param <T> the credential implementation type
* @return the details of the written credential
*/
<T> Mono<CredentialDetails<T>> write(final CredentialRequest<T> credentialRequest);
/**
* Generate a new credential in CredHub, or overwrite an existing credential with a new
* generated value.
*
* @param parametersRequest the parameters of the new credential to generate in CredHub;
* must not be {@literal null}
* @param credentialType the type of the credential to be regenerated; must not be {@literal null}
* @param <T> the credential implementation type
* @param <P> the credential parameter implementation type
* @return the details of the generated credential
*/
<T, P> Mono<CredentialDetails<T>> generate(final ParametersRequest<P> parametersRequest,
Class<T> credentialType);
/**
* Regenerate a credential in CredHub. Only credentials that were previously generated can be
* re-generated.
*
* @param name the name of the credential; must not be {@literal null}
* @param credentialType the type of the credential to be regenerated; must not be {@literal null}
* @param <T> the credential implementation type
* @return the details of the regenerated credential
*/
<T> Mono<CredentialDetails<T>> regenerate(final CredentialName name, Class<T> credentialType);
/**
* Retrieve a credential using its ID, as returned in a write request.
*
* @param id the ID of the credential; must not be {@literal null}
* @param credentialType the type of the credential to be retrieved; must not be {@literal null}
* @param <T> the credential implementation type
* @return the details of the retrieved credential
*/
<T> Mono<CredentialDetails<T>> getById(final String id, final Class<T> credentialType);
/**
* Retrieve a credential using its name, as passed to a write request.
* Only the current credential value will be returned.
*
* @param name the name of the credential; must not be {@literal null}
* @param credentialType the type of credential expected to be returned
* @param <T> the credential implementation type
* @return the details of the retrieved credential
*/
<T> Mono<CredentialDetails<T>> getByName(final CredentialName name, final Class<T> credentialType);
/**
* Retrieve a credential using its name, as passed to a write request.
* A collection of all stored values for the named credential will be returned,
* including historical values.
*
* @param name the name of the credential; must not be {@literal null}
* @param credentialType the type of credential expected to be returned
* @param <T> the credential implementation type
* @return the details of the retrieved credential, including history
*/
<T> Flux<CredentialDetails<T>> getByNameWithHistory(final CredentialName name, final Class<T> credentialType);
/**
* Retrieve a credential using its name, as passed to a write request.
* A collection of stored values for the named credential will be returned,
* with the specified number of historical values.
*
* @param name the name of the credential; must not be {@literal null}
* @param versions the number of historical versions to retrieve
* @param credentialType the type of credential expected to be returned
* @param <T> the credential implementation type
* @return the details of the retrieved credential, including history
*/
<T> Flux<CredentialDetails<T>> getByNameWithHistory(final CredentialName name, int versions,
final Class<T> credentialType);
/**
* Find a credential using a full or partial name.
*
* @param name the name of the credential; must not be {@literal null}
* @return a summary of the credential search results
*/
Flux<CredentialSummary> findByName(final CredentialName name);
/**
* Find a credential using a path.
*
* @param path the path to the credential; must not be {@literal null}
* @return a summary of the credential search results
*/
Flux<CredentialSummary> findByPath(final String path);
/**
* Delete a credential by its full name.
*
* @param name the name of the credential; must not be {@literal null}
* @return an empty {@code Mono}
*/
Mono<Void> deleteByName(final CredentialName name);
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.credential;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialDetailsData;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialRequest;
import org.springframework.credhub.support.CredentialSummary;
import org.springframework.credhub.support.CredentialSummaryData;
import org.springframework.credhub.support.ParametersRequest;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* Implements the interactions with CredHub to save, retrieve,
* and delete credentials.
*
* @author Scott Frederick
*/
public class ReactiveCredHubCredentialTemplate implements ReactiveCredHubCredentialOperations {
private static final String BASE_URL_PATH = "/api/v1/data";
private static final String ID_URL_PATH = BASE_URL_PATH + "/{id}";
private static final String NAME_URL_QUERY = BASE_URL_PATH + "?name={name}";
private static final String NAME_URL_QUERY_CURRENT = NAME_URL_QUERY + "&current=true";
private static final String NAME_URL_QUERY_VERSIONS = NAME_URL_QUERY + "&versions={versions}";
private static final String NAME_LIKE_URL_QUERY = BASE_URL_PATH + "?name-like={name}";
private static final String PATH_URL_QUERY = BASE_URL_PATH + "?path={path}";
private static final String REGENERATE_URL_PATH = "/api/v1/regenerate";
private static final String NAME_REQUEST_FIELD = "name";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubCredentialTemplate}.
*
* @param credHubOperations the {@link ReactiveCredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubCredentialTemplate(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public <T> Mono<CredentialDetails<T>> write(final CredentialRequest<T> credentialRequest) {
Assert.notNull(credentialRequest, "credentialRequest must not be null");
final ParameterizedTypeReference<CredentialDetails<T>> ref =
new ParameterizedTypeReference<CredentialDetails<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.put()
.uri(BASE_URL_PATH)
.syncBody(credentialRequest)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref));
}
@Override
public <T, P> Mono<CredentialDetails<T>> generate(final ParametersRequest<P> parametersRequest,
Class<T> credentialType) {
Assert.notNull(parametersRequest, "parametersRequest must not be null");
final ParameterizedTypeReference<CredentialDetails<T>> ref =
new ParameterizedTypeReference<CredentialDetails<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.post()
.uri(BASE_URL_PATH)
.syncBody(parametersRequest)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref));
}
@Override
public <T> Mono<CredentialDetails<T>> regenerate(final CredentialName name, Class<T> credentialType) {
Assert.notNull(name, "credential name must not be null");
Assert.notNull(credentialType, "credential type must not be null");
final ParameterizedTypeReference<CredentialDetails<T>> ref =
new ParameterizedTypeReference<CredentialDetails<T>>() {};
Map<String, Object> request = new HashMap<>(1);
request.put(NAME_REQUEST_FIELD, name.getName());
return credHubOperations.doWithWebClient(webClient -> webClient
.post()
.uri(REGENERATE_URL_PATH)
.syncBody(request)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref));
}
@Override
public <T> Mono<CredentialDetails<T>> getById(final String id, final Class<T> credentialType) {
Assert.notNull(id, "credential id must not be null");
Assert.notNull(credentialType, "credential type must not be null");
final ParameterizedTypeReference<CredentialDetails<T>> ref =
new ParameterizedTypeReference<CredentialDetails<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(ID_URL_PATH, id)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref));
}
@Override
public <T> Mono<CredentialDetails<T>> getByName(final CredentialName name, final Class<T> credentialType) {
Assert.notNull(name, "credential name must not be null");
Assert.notNull(credentialType, "credential type must not be null");
final ParameterizedTypeReference<CredentialDetailsData<T>> ref =
new ParameterizedTypeReference<CredentialDetailsData<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(NAME_URL_QUERY_CURRENT, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ref)
.map(body -> body.getData().get(0)));
}
@Override
public <T> Flux<CredentialDetails<T>> getByNameWithHistory(final CredentialName name, final Class<T> credentialType) {
Assert.notNull(name, "credential name must not be null");
Assert.notNull(credentialType, "credential type must not be null");
final ParameterizedTypeReference<CredentialDetailsData<T>> ref =
new ParameterizedTypeReference<CredentialDetailsData<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(NAME_URL_QUERY, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(ref)
.flatMap(body -> Flux.fromIterable(body.getData())));
}
@Override
public <T> Flux<CredentialDetails<T>> getByNameWithHistory(final CredentialName name, final int versions,
final Class<T> credentialType) {
Assert.notNull(name, "credential name must not be null");
Assert.notNull(credentialType, "credential type must not be null");
final ParameterizedTypeReference<CredentialDetailsData<T>> ref =
new ParameterizedTypeReference<CredentialDetailsData<T>>() {};
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(NAME_URL_QUERY_VERSIONS, name.getName(), versions)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(ref)
.flatMap(body -> Flux.fromIterable(body.getData())));
}
@Override
public Flux<CredentialSummary> findByName(final CredentialName name) {
Assert.notNull(name, "credential name must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(NAME_LIKE_URL_QUERY, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(CredentialSummaryData.class)
.flatMapMany(data -> Flux.fromIterable(data.getCredentials())));
}
@Override
public Flux<CredentialSummary> findByPath(final String path) {
Assert.notNull(path, "credential path must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(PATH_URL_QUERY, path)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(CredentialSummaryData.class)
.flatMapMany(data -> Flux.fromIterable(data.getCredentials())));
}
@Override
public Mono<Void> deleteByName(final CredentialName name) {
Assert.notNull(name, "credential name must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.delete()
.uri(NAME_URL_QUERY, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(Void.class));
}
}

View File

@@ -14,7 +14,23 @@
* limitations under the License.
*/
package org.springframework.credhub.core.info;
import org.springframework.credhub.support.info.VersionInfo;
import reactor.core.publisher.Mono;
/**
* Spring auto configuration support for Spring CredHub.
* Specifies the interactions with CredHub for retrieving server information.
*
* @author Scott Frederick
*/
package org.springframework.credhub.autoconfig.security;
public interface ReactiveCredHubInfoOperations {
/**
* Retrieve the version information from the CredHub server.
*
* @return the server version information
*/
Mono<VersionInfo> version();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.info;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.info.VersionInfo;
import org.springframework.http.HttpStatus;
import reactor.core.publisher.Mono;
/**
* Implements the interaction with CredHub retrieve server information.
*
* @author Scott Frederick
*/
public class ReactiveCredHubInfoTemplate implements ReactiveCredHubInfoOperations {
private static final String VERSION_URL_PATH = "/version";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubInfoTemplate}.
*
* @param credHubOperations the {@link ReactiveCredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubInfoTemplate(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
/**
* Retrieve the version information from the CredHub server.
*
* @return the server version information
*/
@Override
public Mono<VersionInfo> version() {
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(VERSION_URL_PATH)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(VersionInfo.class));
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.interpolation;
import org.springframework.credhub.support.ServicesData;
import reactor.core.publisher.Mono;
/**
* Specifies the interactions with CredHub to interpolate service binding credentials.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubInterpolationOperations {
/**
* Search the provided data structure of bound service credentials, looking for
* references to CredHub credentials. Any CredHub credentials found in the data
* structure will be replaced by the credential value stored in CredHub.
*
* Example:
*
* A JSON data structure parsed from a {@literal VCAP_SERVICES} environment
* variable might look like this if the service broker that provided the binding
* is integrated with CredHub:
*
* <pre>
* {@code
* {
* "service-offering": [{
* "credentials": {
* "credhub-ref": "((/c/service-broker/service-offering/1111-2222-3333-4444/credentials))"
* }
* "label": "service-offering",
* "name": "service-instance",
* "plan": "standard",
* "tags": ["
* "cloud-service"
* ]
* }]
* }
* }
* </pre>
*
* Assuming that CredHub has a credential with the name
* {@literal /c/service-broker/service-offering/1111-2222-3333-4444/credentials},
* passing the data structure above to this method would result in the
* {@literal credhub-ref} field being replaced by the credentials stored in CredHub:
*
* <pre>
* {@code
* {
* "service-offering": [{
* "credentials": {
* "url": "https://servicehost.example.com/",
* "username": "someuser",
* "password": "secret"
* }
* "label": "service-offering",
* "name": "service-instance",
* "plan": "standard",
* "tags": ["
* "cloud-service"
* ]
* }]
* }
* }
* </pre>
*
* @param serviceData a data structure of bound service credentials, as would be
* parsed from the {@literal VCAP_SERVICES} environment variable provided to
* applications running on Cloud Foundry
* @return the serviceData structure with CredHub references replaced by stored
* credential values
*/
Mono<ServicesData> interpolateServiceData(final ServicesData serviceData);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.interpolation;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.ServicesData;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* Implements the main interaction with CredHub to interpolate service binding credentials.
*
* @author Scott Frederick
*/
public class ReactiveCredHubInterpolationTemplate implements ReactiveCredHubInterpolationOperations {
private static final String INTERPOLATE_URL_PATH = "/api/v1/interpolate";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubInterpolationTemplate}.
*
* @param credHubOperations the {@link CredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubInterpolationTemplate(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public Mono<ServicesData> interpolateServiceData(final ServicesData serviceData) {
Assert.notNull(serviceData, "serviceData must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.post()
.uri(INTERPOLATE_URL_PATH)
.syncBody(serviceData)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(ServicesData.class));
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.permission;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.permissions.Actor;
import org.springframework.credhub.support.permissions.Permission;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Specifies the interactions with CredHub to add, retrieve, and delete permissions.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubPermissionOperations {
/**
* Get the permissions associated with a credential.
*
* @param name the name of the credential; must not be {@literal null}
* @return the collection of permissions associated with the credential
*/
Flux<Permission> getPermissions(final CredentialName name);
/**
* Add permissions to an existing credential.
*
* @param name the name of the credential; must not be {@literal null}
* @param permissions a collection of permissions to add
* @return an empty {@code Mono}
*/
Mono<Void> addPermissions(final CredentialName name, final Permission... permissions);
/**
* Delete a permission associated with a credential.
*
* @param name the name of the credential; must not be {@literal null}
* @param actor the actor of the permission; must not be {@literal null}
* @return an empty {@code Mono}
*/
Mono<Void> deletePermission(final CredentialName name, final Actor actor);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.permission;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialPermissions;
import org.springframework.credhub.support.permissions.Actor;
import org.springframework.credhub.support.permissions.Permission;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Implements the main interaction with CredHub to add, retrieve,
* and delete permissions.
*
* @author Scott Frederick
*/
public class ReactiveCredHubPermissionTemplate implements ReactiveCredHubPermissionOperations {
private static final String PERMISSIONS_URL_PATH = "/api/v1/permissions";
private static final String PERMISSIONS_URL_QUERY = PERMISSIONS_URL_PATH + "?credential_name={name}";
private static final String PERMISSIONS_ACTOR_URL_QUERY = PERMISSIONS_URL_QUERY + "&actor={actor}";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubPermissionTemplate}.
*
* @param credHubOperations the {@link ReactiveCredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubPermissionTemplate(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public Flux<Permission> getPermissions(final CredentialName name) {
Assert.notNull(name, "credential name must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(PERMISSIONS_URL_QUERY, name.getName())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToFlux(Permission.class));
}
@Override
public Mono<Void> addPermissions(final CredentialName name,
final Permission... permissions) {
Assert.notNull(name, "credential name must not be null");
final CredentialPermissions credentialPermissions = new CredentialPermissions(name, permissions);
return credHubOperations.doWithWebClient(webClient -> webClient
.post()
.uri(PERMISSIONS_URL_PATH)
.syncBody(credentialPermissions)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(Void.class));
}
@Override
public Mono<Void> deletePermission(final CredentialName name, final Actor actor) {
Assert.notNull(name, "credential name must not be null");
Assert.notNull(actor, "actor must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.delete()
.uri(PERMISSIONS_ACTOR_URL_QUERY, name.getName(), actor.getIdentity())
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(Void.class));
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.permissionV2;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialPermission;
import org.springframework.credhub.support.permissions.Permission;
import reactor.core.publisher.Mono;
/**
* Specifies the interactions with CredHub to add, retrieve, and delete permissions.
*
* @author Scott Frederick
*/
public interface ReactiveCredHubPermissionV2Operations {
/**
* Get a permission.
*
* @param id the CredHub-assigned ID of the permission; must not be {@literal null}
* @return the details if the specified permission
*/
Mono<CredentialPermission> getPermissions(final String id);
/**
* Add permissions to an credential path.
*
* @param path the path of the credentials; must not be {@literal null}
* @param permission a permission to add
* @return the details if the added permission
*/
Mono<CredentialPermission> addPermissions(final CredentialName path, final Permission permission);
/**
* Add permissions to an existing credential.
*
* @param id the CredHub-assigned ID of the permission; must not be {@literal null}
* @param path the path of the credentials; must not be {@literal null}
* @param permission a permission to add
* @return the details if the added permission
*/
Mono<CredentialPermission> updatePermissions(final String id, final CredentialName path, final Permission permission);
/**
* Delete a permission.
*
* @param id the CredHub-assigned ID of the permission; must not be {@literal null}
* @return an empty {@code Mono}
*/
Mono<Void> deletePermission(final String id);
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.core.permissionV2;
import org.springframework.credhub.core.ExceptionUtils;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.CredentialPermission;
import org.springframework.credhub.support.permissions.Permission;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* Implements the main interaction with CredHub to add, retrieve,
* and delete permissions.
*
* @author Scott Frederick
*/
public class ReactiveCredHubPermissionV2Template implements ReactiveCredHubPermissionV2Operations {
private static final String PERMISSIONS_URL_PATH = "/api/v2/permissions";
private static final String PERMISSIONS_ID_URL_PATH = PERMISSIONS_URL_PATH + "/{id}";
private ReactiveCredHubOperations credHubOperations;
/**
* Create a new {@link ReactiveCredHubPermissionV2Template}.
*
* @param credHubOperations the {@link ReactiveCredHubOperations} to use for interactions with CredHub
*/
public ReactiveCredHubPermissionV2Template(ReactiveCredHubOperations credHubOperations) {
this.credHubOperations = credHubOperations;
}
@Override
public Mono<CredentialPermission> getPermissions(final String id) {
Assert.notNull(id, "credential ID must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.get()
.uri(PERMISSIONS_ID_URL_PATH, id)
.retrieve()
.bodyToMono(CredentialPermission.class));
}
@Override
public Mono<CredentialPermission> addPermissions(final CredentialName path,
final Permission permission) {
Assert.notNull(path, "credential path must not be null");
Assert.notNull(permission, "credential permission must not be null");
final CredentialPermission credentialPermission = new CredentialPermission(path, permission);
return credHubOperations.doWithWebClient(webClient -> webClient
.post()
.uri(PERMISSIONS_URL_PATH)
.syncBody(credentialPermission)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(CredentialPermission.class));
}
@Override
public Mono<CredentialPermission> updatePermissions(final String id, final CredentialName path,
final Permission permission) {
Assert.notNull(id, "credential ID must not be null");
Assert.notNull(path, "credential path must not be null");
Assert.notNull(permission, "credential permission must not be null");
final CredentialPermission credentialPermission = new CredentialPermission(path, permission);
return credHubOperations.doWithWebClient(webClient -> webClient
.put()
.uri(PERMISSIONS_ID_URL_PATH, id)
.syncBody(credentialPermission)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(CredentialPermission.class));
}
@Override
public Mono<Void> deletePermission(final String id) {
Assert.notNull(id, "credential ID must not be null");
return credHubOperations.doWithWebClient(webClient -> webClient
.delete()
.uri(PERMISSIONS_ID_URL_PATH, id)
.retrieve()
.onStatus(HttpStatus::isError, ExceptionUtils::buildError)
.bodyToMono(Void.class));
}
}

View File

@@ -0,0 +1,97 @@
package org.springframework.credhub.security.oauth2.client.endpoint;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.Set;
import java.util.function.Consumer;
import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
/**
* An implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} that &quot;exchanges&quot;
* an authorization code credential for an access token credential
* at the Authorization Server's Token Endpoint.
*
* @author Rob Winch
* @since 5.1
* @see OAuth2AccessTokenResponseClient
* @see OAuth2AuthorizationCodeGrantRequest
* @see OAuth2AccessTokenResponse
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
*/
public class WebClientReactiveClientCredentialsTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private WebClient webClient = WebClient.builder()
.build();
@Override
public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return Mono.defer(() -> {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
BodyInserters.FormInserter<String> body = body(authorizationGrantRequest);
return this.webClient.post()
.uri(tokenUri)
.accept(MediaType.APPLICATION_JSON)
.headers(headers(clientRegistration))
.body(body)
.exchange()
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.map(response -> {
if (response.getAccessToken().getScopes().isEmpty()) {
response = OAuth2AccessTokenResponse.withResponse(response)
.scopes(authorizationGrantRequest.getClientRegistration().getScopes())
.build();
}
return response;
});
});
}
private Consumer<HttpHeaders> headers(ClientRegistration clientRegistration) {
return headers -> {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
};
}
private static BodyInserters.FormInserter<String> body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
BodyInserters.FormInserter<String> body = BodyInserters
.fromFormData(OAuth2ParameterNames.GRANT_TYPE, authorizationGrantRequest.getGrantType().getValue());
Set<String> scopes = clientRegistration.getScopes();
if (!CollectionUtils.isEmpty(scopes)) {
String scope = StringUtils.collectionToDelimitedString(scopes, " ");
body.with(OAuth2ParameterNames.SCOPE, scope);
}
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
return body;
}
public void setWebClient(WebClient webClient) {
this.webClient = webClient;
}
}

View File

@@ -22,7 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import org.springframework.credhub.support.CredentialType;
import java.util.ArrayList;
@@ -42,7 +42,7 @@ public class JsonUtils {
*/
public static ObjectMapper buildObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new ISO8601DateFormat());
objectMapper.setDateFormat(new StdDateFormat());
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@@ -61,7 +61,7 @@ public class JsonUtils {
* @param objectMapper the {@link ObjectMapper} to configure
*/
private static void configureCredentialDetailTypeMapping(ObjectMapper objectMapper) {
List<NamedType> subtypes = new ArrayList<NamedType>();
List<NamedType> subtypes = new ArrayList<>();
for (CredentialType type : CredentialType.values()) {
subtypes.add(new NamedType(type.getModelClass(), type.getValueType()));
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.configuration;
import org.junit.Test;
import org.springframework.credhub.support.ClientOptions;
import org.springframework.http.client.reactive.ClientHttpConnector;
import static org.assertj.core.api.Assertions.assertThat;
public class ClientHttpConnectorFactoryTests {
@Test
public void nettyClientIsCreated() {
ClientHttpConnector clientHttpConnector = ClientHttpConnectorFactory.create(new ClientOptions());
assertThat(clientHttpConnector).isNotNull();
}
}

View File

@@ -25,27 +25,21 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.AbstractUriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class CredHubClientFactoryUnitTests {
private static final String CREDHUB_URI = "https://credhub.cf.example.com:8844";
public class CredHubRestTemplateFactoryUnitTests {
@Mock
private ClientHttpRequestFactory clientHttpRequestFactory;
@Test
public void restTemplateIsCreated() {
RestTemplate restTemplate = CredHubClientFactory.createRestTemplate(CREDHUB_URI,
clientHttpRequestFactory);
CredHubProperties properties = new CredHubProperties();
properties.setUrl("https://credhub.cf.example.com:8844");
RestTemplate restTemplate = CredHubRestTemplateFactory
.createRestTemplate(properties, clientHttpRequestFactory);
assertThat(restTemplate.getUriTemplateHandler())
.isInstanceOf(AbstractUriTemplateHandler.class);
AbstractUriTemplateHandler uriTemplateHandler = (AbstractUriTemplateHandler) restTemplate
.getUriTemplateHandler();
assertThat(uriTemplateHandler.getBaseUrl()).isEqualTo(CREDHUB_URI);
assertThat(restTemplate).isNotNull();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* 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.springframework.credhub.core;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class CredHubWebClientFactoryTests {
@Mock
private ClientHttpConnector clientHttpConnector;
@Mock
private ReactiveClientRegistrationRepository clientRegistrationRepository;
@Mock
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
@Test
public void webClientIsCreated() {
WebClient webClient = CredHubWebClientFactory
.createWebClient(new CredHubProperties(), clientHttpConnector,
clientRegistrationRepository, authorizedClientRepository);
assertThat(webClient).isNotNull();
}
}

View File

@@ -18,7 +18,7 @@ package org.springframework.credhub.support;
import java.util.Date;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import org.junit.Before;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,7 +43,7 @@ public abstract class JsonParsingUnitTestsBase {
@Before
public void setUpJsonParsing() throws Exception {
testDate = new ISO8601DateFormat().parse(TEST_DATE_STRING);
testDate = new StdDateFormat().parse(TEST_DATE_STRING);
}
protected <T> T parseResponse(String json, Class<T> type) {

View File

@@ -18,13 +18,13 @@ description = 'Spring CredHub Demo'
buildscript {
ext {
springBootVersion = "1.5.8.RELEASE"
springBootVersion = "2.0.6.RELEASE"
}
dependencies {
classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
classpath 'io.spring.gradle:spring-io-plugin:0.0.7.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath('io.spring.gradle:propdeps-plugin:0.0.10.RELEASE')
classpath('io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE')
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
repositories {
@@ -41,16 +41,18 @@ apply plugin: 'propdeps'
apply plugin: 'propdeps-maven'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-eclipse'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.springframework.boot'
dependencies {
compile("org.springframework.credhub:spring-credhub-starter:1.1.0.BUILD-SNAPSHOT")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-webflux")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.credhub:spring-credhub-starter:2.0.0.BUILD-SNAPSHOT")
}
repositories {
maven { url "https://repo.spring.io/libs-snapshot" }
mavenCentral()
maven { url "https://repo.spring.io/libs-snapshot-local" }
maven { url "https://repo.spring.io/libs-milestone-local" }
}

View File

@@ -42,8 +42,12 @@ apply plugin: 'org.springframework.boot'
dependencies {
compile project(":spring-credhub-starter")
compile("org.springframework.boot:spring-boot-starter")
compile("org.springframework.security.oauth:spring-security-oauth2")
compile("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.0.RELEASE")
compile("org.springframework.boot:spring-boot-starter-webflux")
compile("io.projectreactor.netty:reactor-netty")
compile("org.springframework.security:spring-security-config")
compile("org.springframework.security:spring-security-oauth2-client")
if (project.hasProperty("useHttpComponents")) {
compile("org.apache.httpcomponents:httpclient:4.5.3")
@@ -56,6 +60,7 @@ dependencies {
}
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("io.projectreactor:reactor-test")
testCompile("org.assertj:assertj-core:${assertJVersion}")
}

View File

@@ -19,9 +19,6 @@ package org.springframework.credhub.integration;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration;
import org.springframework.credhub.core.CredHubException;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.support.CredentialName;
@@ -29,10 +26,7 @@ import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestApplication.class,
CredHubAutoConfiguration.class,
CredHubTemplateAutoConfiguration.class,
CredHubOAuth2TemplateAutoConfiguration.class})
@SpringBootTest(classes = {TestApplication.class})
@ActiveProfiles("test")
public abstract class CredHubIntegrationTests {

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2017-2018 the original author or authors.
*
* 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.springframework.credhub.integration;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration;
import org.springframework.credhub.core.CredHubException;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.support.CredentialName;
import org.springframework.credhub.support.info.VersionInfo;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestApplication.class,
CredHubAutoConfiguration.class,
CredHubOAuth2TemplateAutoConfiguration.class,
CredHubTemplateAutoConfiguration.class})
@ActiveProfiles("test")
public abstract class ReactiveCredHubIntegrationTests {
@Autowired
protected ReactiveCredHubOperations operations;
boolean serverApiIsV1() {
return getVersion().isVersion1();
}
boolean serverApiIsV2() {
return getVersion().isVersion2();
}
private VersionInfo getVersion() {
return operations.info().version().single().block();
}
void deleteCredentialIfExists(CredentialName credentialName) {
try {
operations.credentials().deleteByName(credentialName).block();
} catch (CredHubException e) {
// ignore failing deletes on cleanup
}
}
}

View File

@@ -0,0 +1,339 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.integration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.credhub.core.credential.ReactiveCredHubCredentialOperations;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialType;
import org.springframework.credhub.support.SimpleCredentialName;
import org.springframework.credhub.support.WriteMode;
import org.springframework.credhub.support.password.PasswordParameters;
import org.springframework.credhub.support.user.UserCredential;
import org.springframework.credhub.support.user.UserParametersRequest;
import org.springframework.credhub.support.value.ValueCredential;
import org.springframework.credhub.support.value.ValueCredentialRequest;
import reactor.test.StepVerifier;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
public class ReactiveCredentialIntegrationTests extends ReactiveCredHubIntegrationTests {
private static final SimpleCredentialName CREDENTIAL_NAME =
new SimpleCredentialName("spring-credhub", "integration-test", "test-credential");
private static final String CREDENTIAL_VALUE = "test-value";
private ReactiveCredHubCredentialOperations credentials;
private PasswordParameters.PasswordParametersBuilder passwordParameters;
@Before
public void setUp() {
credentials = operations.credentials();
passwordParameters = PasswordParameters.builder()
.length(12)
.excludeLower(false)
.excludeUpper(false)
.excludeNumber(false)
.includeSpecial(true);
deleteCredentialIfExists(CREDENTIAL_NAME);
}
@After
public void tearDown() {
deleteCredentialIfExists(CREDENTIAL_NAME);
StepVerifier.create(credentials.findByName(CREDENTIAL_NAME))
.expectComplete()
.verify();
}
@Test
public void writeCredential() {
AtomicReference<CredentialDetails<ValueCredential>> written = new AtomicReference<>();
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value(CREDENTIAL_VALUE)
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
assertThat(response.getCredentialType()).isEqualTo(CredentialType.VALUE);
assertThat(response.getId()).isNotNull();
written.set(response);
})
.verifyComplete();
StepVerifier.create(credentials.getById(written.get().getId(), ValueCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
assertThat(response.getCredentialType()).isEqualTo(CredentialType.VALUE);
})
.verifyComplete();
StepVerifier.create(credentials.getByName(CREDENTIAL_NAME, ValueCredential.class))
.assertNext(byName -> {
assertThat(byName.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(byName.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
assertThat(byName.getCredentialType()).isEqualTo(CredentialType.VALUE);
})
.verifyComplete();
StepVerifier.create(credentials.findByName(new SimpleCredentialName("/test")))
.assertNext(response -> assertThat(response)
.extracting("name").extracting("name")
.containsExactly(CREDENTIAL_NAME.getName()))
.verifyComplete();
StepVerifier.create(credentials.findByPath("/spring-credhub/integration-test"))
.assertNext(response ->
assertThat(response)
.extracting("name").extracting("name")
.containsExactly(CREDENTIAL_NAME.getName()))
.verifyComplete();
}
@Test
public void overwriteCredentialV2() {
assumeTrue(serverApiIsV2());
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value(CREDENTIAL_VALUE)
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
assertThat(response.getCredentialType()).isEqualTo(CredentialType.VALUE);
assertThat(response.getId()).isNotNull();
})
.verifyComplete();
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value("new-value")
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo("new-value");
})
.verifyComplete();
}
@Test
public void overwriteCredentialV1() {
assumeTrue(serverApiIsV1());
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value(CREDENTIAL_VALUE)
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
assertThat(response.getCredentialType()).isEqualTo(CredentialType.VALUE);
assertThat(response.getId()).isNotNull();
}).verifyComplete();
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value("new-value")
.mode(WriteMode.NO_OVERWRITE)
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo(CREDENTIAL_VALUE);
})
.verifyComplete();
StepVerifier.create(credentials.write(ValueCredentialRequest.builder()
.name(CREDENTIAL_NAME)
.value("new-value")
.mode(WriteMode.OVERWRITE)
.build()))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getValue()).isEqualTo("new-value");
})
.verifyComplete();
}
@Test
public void generateCredential() {
AtomicReference<CredentialDetails<UserCredential>> generated = new AtomicReference<>();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getCredentialType()).isEqualTo(CredentialType.USER);
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).matches("^[a-zA-Z0-9\\p{Punct}]{12}$");
assertThat(response.getValue().getPasswordHash()).isNotNull();
generated.set(response);
})
.verifyComplete();
StepVerifier.create(credentials.getById(generated.get().getId(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
})
.verifyComplete();
StepVerifier.create(credentials.regenerate(CREDENTIAL_NAME, UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).matches("^[a-zA-Z0-9\\p{Punct}]{12}$");
assertThat(response.getValue().getPassword())
.isNotEqualTo(generated.get().getValue().getPassword());
assertThat(response.getValue().getPasswordHash())
.isNotEqualTo(generated.get().getValue().getPasswordHash());
})
.verifyComplete();
}
@Test
public void generateNoOverwriteCredential() {
AtomicReference<CredentialDetails<UserCredential>> generated = new AtomicReference<>();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).matches("^[a-zA-Z0-9\\p{Punct}]{12}$");
assertThat(response.getValue().getPasswordHash()).isNotNull();
generated.set(response);
})
.verifyComplete();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.mode(WriteMode.NO_OVERWRITE)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).isEqualTo(generated.get().getValue().getPassword());
assertThat(response.getValue().getPasswordHash()).isEqualTo(generated.get().getValue().getPasswordHash());
})
.verifyComplete();
}
@Test
public void generateOverwriteCredential() {
AtomicReference<CredentialDetails<UserCredential>> generated = new AtomicReference<>();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).matches("^[a-zA-Z0-9\\p{Punct}]{12}$");
assertThat(response.getValue().getPasswordHash()).isNotNull();
generated.set(response);
})
.verifyComplete();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.mode(WriteMode.OVERWRITE)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).isNotEqualTo(generated.get().getValue().getPassword());
assertThat(response.getValue().getPasswordHash()).isNotEqualTo(generated.get().getValue().getPasswordHash());
}).verifyComplete();
}
@Test
public void generateConvergeCredential() {
AtomicReference<CredentialDetails<UserCredential>> generated = new AtomicReference<>();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getName().getName()).isEqualTo(CREDENTIAL_NAME.getName());
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword()).matches("^[a-zA-Z0-9\\p{Punct}]{12}$");
assertThat(response.getValue().getPasswordHash()).isNotNull();
generated.set(response);
})
.verifyComplete();
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.mode(WriteMode.CONVERGE)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword())
.isEqualTo(generated.get().getValue().getPassword());
assertThat(response.getValue().getPasswordHash())
.isEqualTo(generated.get().getValue().getPasswordHash());
})
.verifyComplete();
passwordParameters.includeSpecial(false);
StepVerifier.create(credentials.generate(UserParametersRequest.builder()
.name(CREDENTIAL_NAME)
.mode(WriteMode.CONVERGE)
.username("test-user")
.parameters(passwordParameters.build())
.build(), UserCredential.class))
.assertNext(response -> {
assertThat(response.getValue().getUsername()).isEqualTo("test-user");
assertThat(response.getValue().getPassword())
.isNotEqualTo(generated.get().getValue().getPassword());
assertThat(response.getValue().getPasswordHash())
.isNotEqualTo(generated.get().getValue().getPasswordHash());
})
.verifyComplete();
}
}

View File

@@ -26,8 +26,11 @@ dependencies {
compile project(':spring-credhub-core')
compile("org.springframework.boot:spring-boot-autoconfigure")
optional("org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.0.RELEASE")
optional("org.springframework.boot:spring-boot-starter-webflux")
optional("org.springframework.security:spring-security-config")
optional("org.springframework.security:spring-security-oauth2-client")
optional("org.apache.httpcomponents:httpclient") {
exclude(group: 'commons-logging', module: 'commons-logging')
}

View File

@@ -19,6 +19,7 @@ package org.springframework.credhub.autoconfig;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -29,6 +30,8 @@ import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.support.ClientOptions;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link CredHubTemplate}.
@@ -81,6 +84,18 @@ public class CredHubAutoConfiguration {
credHubTemplateFactory.clientHttpRequestFactoryWrapper(clientOptions));
}
/**
* Create a {@link ClientHttpConnector}.
*
* @param clientOptions the populated {@link ClientOptions} bean
* @return the {@link ClientHttpConnector}
*/
@Bean
@ConditionalOnClass(WebClient.class)
public ClientHttpConnector clientHttpConnector(ClientOptions clientOptions) {
return credHubTemplateFactory.clientHttpConnector(clientOptions);
}
/**
* Wrapper for {@link ClientHttpRequestFactory} to not expose the bean globally.
*/

View File

@@ -0,0 +1,62 @@
package org.springframework.credhub.autoconfig;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@AutoConfigureBefore(ReactiveOAuth2ClientAutoConfiguration.class)
@ConditionalOnClass(ClientRegistration.class)
public class CredHubOAuth2AutoConfiguration {
private final OAuth2ClientProperties properties;
CredHubOAuth2AutoConfiguration(OAuth2ClientProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public ClientRegistrationRepository credHubClientRegistrationRepository() {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(this.properties).values());
return new InMemoryClientRegistrationRepository(registrations);
}
@Bean
@ConditionalOnMissingBean
public OAuth2AuthorizedClientService credHubAuthorizedClientService(
ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
/**
* Create a {@code ServerOAuth2AuthorizedClientRepository} bean to override the default
* provided by Spring Boot auto-configuration.
*
* @return the {@code ServerOAuth2AuthorizedClientRepository}
*/
@Bean
@ConditionalOnMissingBean
public ServerOAuth2AuthorizedClientRepository credHubAuthorizedClientRepository() {
return new UnAuthenticatedServerOAuth2AuthorizedClientRepository();
}
}

View File

@@ -16,64 +16,84 @@
package org.springframework.credhub.autoconfig;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration.ClientFactoryWrapper;
import org.springframework.credhub.autoconfig.security.CredHubCredentialsDetails;
import org.springframework.credhub.configuration.OAuth2CredHubTemplateFactory;
import org.springframework.credhub.configuration.CredHubTemplateFactory;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.core.ReactiveCredHubTemplate;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link CredHubTemplate} with
* OAuth2 credentials if spring-security-oauth2 and OAuth2 properties are provided.
*
* OAuth2 credentials.
*
* @author Daniel Lavoie
* @author Scott Frederick
*/
@Configuration
@AutoConfigureBefore(CredHubTemplateAutoConfiguration.class)
@AutoConfigureAfter({CredHubAutoConfiguration.class,
CredHubOAuth2AutoConfiguration.class,
OAuth2ClientAutoConfiguration.class,
ReactiveOAuth2ClientAutoConfiguration.class})
@ConditionalOnProperty("spring.credhub.oauth2.client-id")
@ConditionalOnClass(name = "org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails")
public class CredHubOAuth2TemplateAutoConfiguration {
private final OAuth2CredHubTemplateFactory credHubTemplateFactory = new OAuth2CredHubTemplateFactory();
private final CredHubTemplateFactory credHubTemplateFactory = new CredHubTemplateFactory();
/**
* Bean that holds OAuth2 credential information for CredHub.
*
* @return the {@link ClientCredentialsResourceDetails} bean
*/
@Bean
@CredHubCredentialsDetails
@ConfigurationProperties("spring.credhub.oauth2")
public ClientCredentialsResourceDetails credHubCredentialsDetails() {
return new ClientCredentialsResourceDetails();
}
/**
* Preconfigured {@link OAuth2RestTemplate} with OAuth2 credentials for CredHub.
*
* @param credHubProperties {@link CredHubProperties} for CredHub
* @param credHubCredentialsDetails OAuth2 credentials for use with the {@link OAuth2RestTemplate}
* @param clientFactoryWrapper a {@link ClientFactoryWrapper}
* to customize CredHub http requests
* Create the {@link CredHubTemplate} that the application will use to interact
* with CredHub.
*
* @param credHubProperties {@link CredHubProperties} for CredHub
* @param clientFactoryWrapper a {@link ClientFactoryWrapper}
* to customize CredHub http requests
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientService a repository of authorized OAuth2 clients
* @return the {@link CredHubOperations} bean
*/
@Bean
public CredHubOperations credHubTemplate(
CredHubProperties credHubProperties,
@CredHubCredentialsDetails ClientCredentialsResourceDetails credHubCredentialsDetails,
ClientFactoryWrapper clientFactoryWrapper) {
return credHubTemplateFactory.credHubTemplate(credHubCredentialsDetails,
credHubProperties,
clientFactoryWrapper.getClientHttpRequestFactory());
public CredHubOperations oAuth2credHubTemplate(CredHubProperties credHubProperties,
ClientFactoryWrapper clientFactoryWrapper,
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
return credHubTemplateFactory.credHubTemplate(credHubProperties,
clientFactoryWrapper.getClientHttpRequestFactory(),
clientRegistrationRepository,
authorizedClientService);
}
/**
* Create the {@link ReactiveCredHubTemplate} that the application will use to interact
* with CredHub.
*
* @param credHubProperties {@link CredHubProperties} for CredHub
* @param clientHttpConnector a {@link ClientHttpConnector} to customize CredHub
* http requests
* @param clientRegistrationRepository a repository of OAuth2 client registrations
* @param authorizedClientRepository a repository of OAuth2 authorized clients
* @return the {@link CredHubTemplate} bean
*/
@Bean
@ConditionalOnClass(WebClient.class)
public ReactiveCredHubOperations oAuth2reactiveCredHubTemplate(CredHubProperties credHubProperties,
ClientHttpConnector clientHttpConnector,
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
return credHubTemplateFactory.credHubTemplate(credHubProperties, clientHttpConnector,
clientRegistrationRepository, authorizedClientRepository);
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.credhub.autoconfig;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -27,6 +28,10 @@ import org.springframework.credhub.configuration.CredHubTemplateFactory;
import org.springframework.credhub.core.CredHubOperations;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.core.ReactiveCredHubOperations;
import org.springframework.credhub.core.ReactiveCredHubTemplate;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link CredHubTemplate}.
@@ -35,7 +40,7 @@ import org.springframework.credhub.core.CredHubTemplate;
* @author Daniel Lavoie
*/
@Configuration
@AutoConfigureAfter(CredHubOAuth2TemplateAutoConfiguration.class)
@AutoConfigureAfter({CredHubAutoConfiguration.class, CredHubOAuth2TemplateAutoConfiguration.class})
@ConditionalOnProperty(value = "spring.credhub.url")
public class CredHubTemplateAutoConfiguration {
private final CredHubTemplateFactory credHubTemplateFactory = new CredHubTemplateFactory();
@@ -46,7 +51,7 @@ public class CredHubTemplateAutoConfiguration {
*
* @param credHubProperties {@link CredHubProperties} for CredHub
* @param clientFactoryWrapper a {@link ClientFactoryWrapper} to customize CredHub
* http requests
* HTTP requests
* @return the {@link CredHubTemplate} bean
*/
@Bean
@@ -56,4 +61,22 @@ public class CredHubTemplateAutoConfiguration {
return credHubTemplateFactory.credHubTemplate(credHubProperties,
clientFactoryWrapper.getClientHttpRequestFactory());
}
/**
* Create the {@link ReactiveCredHubTemplate} that the application will use to interact
* with CredHub.
*
* @param credHubProperties {@link CredHubProperties} for CredHub
* @param clientHttpConnector a {@link ClientHttpConnector} to customize CredHub
* HTTP requests
* @return the {@link CredHubTemplate} bean
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(WebClient.class)
public ReactiveCredHubOperations reactiveCredHubTemplate(CredHubProperties credHubProperties,
ClientHttpConnector clientHttpConnector) {
return credHubTemplateFactory.credHubTemplate(credHubProperties, clientHttpConnector);
}
}

View File

@@ -1,24 +0,0 @@
package org.springframework.credhub.autoconfig.security;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Qualifies a {@link ClientCredentialsResourceDetails} used by Spring CredHub.
*
* @author Scott Frederick
*/
@Qualifier
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CredHubCredentialsDetails {
}

View File

@@ -1,4 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.credhub.autoconfig.CredHubAutoConfiguration,\
org.springframework.credhub.autoconfig.CredHubOAuth2AutoConfiguration,\
org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration,\
org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration
org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration

View File

@@ -37,12 +37,14 @@ public class CredHubAutoConfigurationTests {
.withPropertyValues(
"spring.credhub.url=https://localhost",
"spring.credhub.connection-timeout=30",
"spring.credhub.read-timeout=60"
"spring.credhub.read-timeout=60",
"spring.credhub.oauth2.client-id=test-client"
)
.run((context) -> {
assertThat(context).hasSingleBean(CredHubProperties.class);
CredHubProperties properties = context.getBean(CredHubProperties.class);
assertThat(properties.getUrl()).isEqualTo("https://localhost");
assertThat(properties.getOauth2().getClientId()).isEqualTo("test-client");
assertThat(context).hasSingleBean(ClientOptions.class);
ClientOptions options = context.getBean(ClientOptions.class);

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.credhub.configuration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2AutoConfiguration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import static org.assertj.core.api.Assertions.assertThat;
public class CredHubOAuth2AutoConfigurationTests {
private ApplicationContextRunner context = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
CredHubAutoConfiguration.class,
CredHubOAuth2AutoConfiguration.class,
ReactiveOAuth2ClientAutoConfiguration.class))
.withPropertyValues(
"spring.security.oauth2.client.registration.credhub-client.provider=uaa",
"spring.security.oauth2.client.registration.credhub-client.client-id=test-client",
"spring.security.oauth2.client.registration.credhub-client.client-secret=test-secret",
"spring.security.oauth2.client.registration.credhub-client.authorization-grant-type=client_credentials",
"spring.security.oauth2.client.provider.uaa.token-uri=http://example.com/uaa/oauth/token",
"debug=true"
);
@Test
public void oauth2ContextConfigured() {
context.run((context) -> {
assertThat(context).hasSingleBean(ClientRegistrationRepository.class);
assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class);
assertThat(context).hasSingleBean(ReactiveClientRegistrationRepository.class);
assertThat(context).hasSingleBean(ServerOAuth2AuthorizedClientRepository.class);
});
}
}

View File

@@ -18,14 +18,18 @@ package org.springframework.credhub.configuration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2AutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.core.OAuth2CredHubTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.credhub.core.ReactiveCredHubTemplate;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,41 +39,41 @@ import static org.assertj.core.api.Assertions.assertThat;
public class CredHubOAuth2TemplateAutoConfigurationTests {
private ApplicationContextRunner context = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CredHubAutoConfiguration.class,
.withConfiguration(AutoConfigurations.of(
ReactiveOAuth2ClientAutoConfiguration.class,
CredHubAutoConfiguration.class,
CredHubOAuth2AutoConfiguration.class,
CredHubOAuth2TemplateAutoConfiguration.class,
CredHubTemplateAutoConfiguration.class))
.withPropertyValues(
"spring.credhub.url=https://localhost",
"spring.credhub.oauth2.client-id=test-user",
"spring.credhub.oauth2.client-secret=test-secret",
"spring.credhub.oauth2.access-token-uri=https://uaa.example.com/oauth/token",
"debug"
"spring.credhub.oauth2.client-id=credhub-client",
"spring.security.oauth2.client.registration.credhub-client.provider=uaa",
"spring.security.oauth2.client.registration.credhub-client.client-id=test-client",
"spring.security.oauth2.client.registration.credhub-client.client-secret=test-secret",
"spring.security.oauth2.client.registration.credhub-client.authorization-grant-type=client_credentials",
"spring.security.oauth2.client.provider.uaa.token-uri=http://example.com/uaa/oauth/token",
"debug=true"
);
@Test
public void contextLoadsWithSpringSecurityOAuth2() {
public void credHubTemplateConfiguredWithOAuth2() {
context.run((context) -> {
assertThat(context).hasSingleBean(OAuth2CredHubTemplate.class);
assertThat(context).hasSingleBean(CredHubTemplate.class);
assertThat(context).hasSingleBean(CredHubAutoConfiguration.ClientFactoryWrapper.class);
assertThat(context).hasSingleBean(ClientCredentialsResourceDetails.class);
ClientCredentialsResourceDetails credentialsDetails =
context.getBean(ClientCredentialsResourceDetails.class);
assertThat(credentialsDetails).isNotNull();
assertThat(credentialsDetails.getClientId()).isEqualTo("test-user");
assertThat(credentialsDetails.getClientSecret()).isEqualTo("test-secret");
assertThat(credentialsDetails.getAccessTokenUri())
.isEqualTo("https://uaa.example.com/oauth/token");
assertThat(context).hasSingleBean(ClientRegistrationRepository.class);
});
}
@Test
public void contextLoadsWithoutSpringSecurityOAuth2() {
context.withClassLoader(new FilteredClassLoader(ClientCredentialsResourceDetails.class))
.run((context) -> {
assertThat(context).hasSingleBean(CredHubTemplate.class);
assertThat(context).doesNotHaveBean(OAuth2CredHubTemplate.class);
public void reactiveCredHubTemplateConfiguredWithOAuth2() {
context.run((context) -> {
assertThat(context).hasSingleBean(ReactiveCredHubTemplate.class);
assertThat(context).hasSingleBean(ClientHttpConnector.class);
assertThat(context).doesNotHaveBean(ClientCredentialsResourceDetails.class);
});
assertThat(context).hasSingleBean(ReactiveClientRegistrationRepository.class);
assertThat(context).hasSingleBean(ServerOAuth2AuthorizedClientRepository.class);
});
}
}

View File

@@ -18,32 +18,53 @@ package org.springframework.credhub.configuration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.credhub.autoconfig.CredHubAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration;
import org.springframework.credhub.autoconfig.CredHubTemplateAutoConfiguration;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.core.OAuth2CredHubTemplate;
import org.springframework.credhub.core.ReactiveCredHubTemplate;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Daniel Lavoie
*/
public class CredHubTemplateAutoConfigurationTests {
private final ApplicationContextRunner context = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CredHubAutoConfiguration.class,
CredHubOAuth2TemplateAutoConfiguration.class,
CredHubTemplateAutoConfiguration.class))
.withPropertyValues(
"spring.credhub.url=https://localhost",
"debug");
@Test
public void contextLoads() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CredHubAutoConfiguration.class,
CredHubOAuth2TemplateAutoConfiguration.class,
CredHubTemplateAutoConfiguration.class))
.withPropertyValues(
"spring.credhub.url=https://localhost",
"debug"
)
.run((context) -> {
public void credHubTemplateConfigured() {
context.run((context) -> {
assertThat(context).hasSingleBean(CredHubTemplate.class);
assertThat(context).doesNotHaveBean(OAuth2CredHubTemplate.class);
});
}
@Test
public void reactiveCredHubTemplateConfigured() {
context.run((context) -> {
assertThat(context).hasSingleBean(ReactiveCredHubTemplate.class);
assertThat(context).hasSingleBean(ClientHttpConnector.class);
assertThat(context).doesNotHaveBean(ReactiveClientRegistrationRepository.class);
assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class);
});
}
@Test
public void reactiveCredHubTemplateNotConfiguredWithoutWebClient() {
context.withClassLoader(new FilteredClassLoader(WebClient.class))
.run((context) -> {
assertThat(context).doesNotHaveBean(ReactiveCredHubTemplate.class);
assertThat(context).doesNotHaveBean(ClientHttpConnector.class);
});
}
}