diff --git a/build.gradle b/build.gradle index 8dd7118..11de9b6 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/spring-credhub-core/build.gradle b/spring-credhub-core/build.gradle index 4c44c08..f545319 100644 --- a/spring-credhub-core/build.gradle +++ b/spring-credhub-core/build.gradle @@ -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}") diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/ClientHttpConnectorFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/ClientHttpConnectorFactory.java new file mode 100644 index 0000000..d3fcd58 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/ClientHttpConnectorFactory.java @@ -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; + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java index f3dc297..30ba347 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/CredHubTemplateFactory.java @@ -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}. * diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/OAuth2CredHubTemplateFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/OAuth2CredHubTemplateFactory.java deleted file mode 100644 index da382aa..0000000 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/configuration/OAuth2CredHubTemplateFactory.java +++ /dev/null @@ -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); - } -} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubClientFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubClientFactory.java deleted file mode 100644 index 3ce9f6a..0000000 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubClientFactory.java +++ /dev/null @@ -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> createMessageConverters() { - List> 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 createInterceptors() { - List 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); - } - } -} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubException.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubException.java index 742302b..90c1032 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubException.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubException.java @@ -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); } } diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java new file mode 100644 index 0000000..92e5bc1 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubOAuth2RequestInterceptor.java @@ -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(); + } + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubProperties.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubProperties.java index 36d109f..16c37fb 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubProperties.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubProperties.java @@ -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; + } + } } diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java new file mode 100644 index 0000000..bc8e70e --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubRestTemplateFactory.java @@ -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); + } + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java index 66bc3f7..ecbe569 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubTemplate.java @@ -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. * diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java new file mode 100644 index 0000000..c6089c0 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/CredHubWebClientFactory.java @@ -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); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/ExceptionUtils.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ExceptionUtils.java index 33a21db..d82d411 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/ExceptionUtils.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ExceptionUtils.java @@ -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 buildError(ClientResponse response) { + return Mono.error(new CredHubException(response.statusCode())); + } } diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/OAuth2CredHubTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/OAuth2CredHubTemplate.java deleted file mode 100644 index a0f9c37..0000000 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/core/OAuth2CredHubTemplate.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubOperations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubOperations.java new file mode 100644 index 0000000..42d0184 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubOperations.java @@ -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 the publisher type + * @param the credential implementation type + * @return the return value from the callback method + */ + > T doWithWebClient(Function callback); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubTemplate.java new file mode 100644 index 0000000..90c101f --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/ReactiveCredHubTemplate.java @@ -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 the credential implementation type + * @return the return value from the callback method + */ + @Override + public > T doWithWebClient(Function callback) { + Assert.notNull(callback, "callback must not be null"); + + try { + return callback.apply(webClient); + } + catch (HttpStatusCodeException e) { + throw new CredHubException(e); + } + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateOperations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateOperations.java new file mode 100644 index 0000000..ea469af --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateOperations.java @@ -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 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 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 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 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 updateTransitionalVersion(final String id, final String versionId); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateTemplate.java new file mode 100644 index 0000000..7b08c54 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/certificate/ReactiveCredHubCertificateTemplate.java @@ -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 getAll() { + return credHubOperations.doWithWebClient(webClient -> webClient + .get() + .uri(BASE_URL_PATH) + .retrieve() + .onStatus(HttpStatus::isError, ExceptionUtils::buildError) + .bodyToFlux(CertificateSummary.class)); + } + + @Override + public Mono 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 regenerate(final String id, final boolean setAsTransitional) { + Assert.notNull(id, "credential ID must not be null"); + + final ParameterizedTypeReference ref = + new ParameterizedTypeReference() {}; + + Map 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 regenerate(final CredentialName certificateName) { + Assert.notNull(certificateName, "certificate name must not be null"); + + final ParameterizedTypeReference>> ref = + new ParameterizedTypeReference>>() {}; + + Map 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 updateTransitionalVersion(final String id, + final String versionId) { + Assert.notNull(id, "credential ID must not be null"); + + Map 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)); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialOperations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialOperations.java new file mode 100644 index 0000000..ebb4c50 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialOperations.java @@ -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 the credential implementation type + * @return the details of the written credential + */ + Mono> write(final CredentialRequest 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 the credential implementation type + * @param

the credential parameter implementation type + * @return the details of the generated credential + */ + Mono> generate(final ParametersRequest

parametersRequest, + Class 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 the credential implementation type + * @return the details of the regenerated credential + */ + Mono> regenerate(final CredentialName name, Class 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 the credential implementation type + * @return the details of the retrieved credential + */ + Mono> getById(final String id, final Class 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 the credential implementation type + * @return the details of the retrieved credential + */ + Mono> getByName(final CredentialName name, final Class 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 the credential implementation type + * @return the details of the retrieved credential, including history + */ + Flux> getByNameWithHistory(final CredentialName name, final Class 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 the credential implementation type + * @return the details of the retrieved credential, including history + */ + Flux> getByNameWithHistory(final CredentialName name, int versions, + final Class 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 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 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 deleteByName(final CredentialName name); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialTemplate.java new file mode 100644 index 0000000..0a2d344 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/credential/ReactiveCredHubCredentialTemplate.java @@ -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 + "¤t=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 Mono> write(final CredentialRequest credentialRequest) { + Assert.notNull(credentialRequest, "credentialRequest must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + return credHubOperations.doWithWebClient(webClient -> webClient + .put() + .uri(BASE_URL_PATH) + .syncBody(credentialRequest) + .retrieve() + .onStatus(HttpStatus::isError, ExceptionUtils::buildError) + .bodyToMono(ref)); + } + + @Override + public Mono> generate(final ParametersRequest

parametersRequest, + Class credentialType) { + Assert.notNull(parametersRequest, "parametersRequest must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + return credHubOperations.doWithWebClient(webClient -> webClient + .post() + .uri(BASE_URL_PATH) + .syncBody(parametersRequest) + .retrieve() + .onStatus(HttpStatus::isError, ExceptionUtils::buildError) + .bodyToMono(ref)); + } + + @Override + public Mono> regenerate(final CredentialName name, Class credentialType) { + Assert.notNull(name, "credential name must not be null"); + Assert.notNull(credentialType, "credential type must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + Map 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 Mono> getById(final String id, final Class credentialType) { + Assert.notNull(id, "credential id must not be null"); + Assert.notNull(credentialType, "credential type must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + return credHubOperations.doWithWebClient(webClient -> webClient + .get() + .uri(ID_URL_PATH, id) + .retrieve() + .onStatus(HttpStatus::isError, ExceptionUtils::buildError) + .bodyToMono(ref)); + } + + @Override + public Mono> getByName(final CredentialName name, final Class credentialType) { + Assert.notNull(name, "credential name must not be null"); + Assert.notNull(credentialType, "credential type must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + 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 Flux> getByNameWithHistory(final CredentialName name, final Class credentialType) { + Assert.notNull(name, "credential name must not be null"); + Assert.notNull(credentialType, "credential type must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + 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 Flux> getByNameWithHistory(final CredentialName name, final int versions, + final Class credentialType) { + Assert.notNull(name, "credential name must not be null"); + Assert.notNull(credentialType, "credential type must not be null"); + + final ParameterizedTypeReference> ref = + new ParameterizedTypeReference>() {}; + + 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 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 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 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)); + } +} diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/package-info.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoOperations.java similarity index 57% rename from spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/package-info.java rename to spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoOperations.java index 972bc96..a812d21 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/package-info.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoOperations.java @@ -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; \ No newline at end of file +public interface ReactiveCredHubInfoOperations { + + /** + * Retrieve the version information from the CredHub server. + * + * @return the server version information + */ + Mono version(); + +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoTemplate.java new file mode 100644 index 0000000..ff3d19f --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/info/ReactiveCredHubInfoTemplate.java @@ -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 version() { + return credHubOperations.doWithWebClient(webClient -> webClient + .get() + .uri(VERSION_URL_PATH) + .retrieve() + .onStatus(HttpStatus::isError, ExceptionUtils::buildError) + .bodyToMono(VersionInfo.class)); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationOperations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationOperations.java new file mode 100644 index 0000000..b309af9 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationOperations.java @@ -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: + * + *

+	 * {@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"
+	 *      ]
+	 *    }]
+	 * }
+	 * }
+	 * 
+ * + * 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: + * + *
+	 * {@code
+	 * {
+	 *    "service-offering": [{
+	 *      "credentials": {
+	 *        "url": "https://servicehost.example.com/",
+	 *        "username": "someuser",
+	 *        "password": "secret"
+	 *      }
+	 *      "label": "service-offering",
+	 *      "name": "service-instance",
+	 *      "plan": "standard",
+	 *      "tags": ["
+	 *        "cloud-service"
+	 *      ]
+	 *    }]
+	 * }
+	 * }
+	 * 
+ * + * @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 interpolateServiceData(final ServicesData serviceData); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationTemplate.java new file mode 100644 index 0000000..05f1bc7 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/interpolation/ReactiveCredHubInterpolationTemplate.java @@ -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 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)); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionOperations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionOperations.java new file mode 100644 index 0000000..5048d3f --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionOperations.java @@ -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 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 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 deletePermission(final CredentialName name, final Actor actor); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionTemplate.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionTemplate.java new file mode 100644 index 0000000..e2abd25 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permission/ReactiveCredHubPermissionTemplate.java @@ -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 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 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 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)); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Operations.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Operations.java new file mode 100644 index 0000000..287517e --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Operations.java @@ -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 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 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 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 deletePermission(final String id); +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Template.java b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Template.java new file mode 100644 index 0000000..de37b9a --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/core/permissionV2/ReactiveCredHubPermissionV2Template.java @@ -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 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 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 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 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)); + } +} diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java new file mode 100644 index 0000000..074c437 --- /dev/null +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java @@ -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 "exchanges" + * 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 Nimbus OAuth 2.0 SDK + * @see Section 4.1.3 Access Token Request (Authorization Code Grant) + * @see Section 4.1.4 Access Token Response (Authorization Code Grant) + */ +public class WebClientReactiveClientCredentialsTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient { + private WebClient webClient = WebClient.builder() + .build(); + + @Override + public Mono getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { + return Mono.defer(() -> { + ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); + + String tokenUri = clientRegistration.getProviderDetails().getTokenUri(); + BodyInserters.FormInserter 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 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 body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { + ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); + BodyInserters.FormInserter body = BodyInserters + .fromFormData(OAuth2ParameterNames.GRANT_TYPE, authorizationGrantRequest.getGrantType().getValue()); + Set 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; + } +} \ No newline at end of file diff --git a/spring-credhub-core/src/main/java/org/springframework/credhub/support/utils/JsonUtils.java b/spring-credhub-core/src/main/java/org/springframework/credhub/support/utils/JsonUtils.java index fdab8ed..a321fd7 100644 --- a/spring-credhub-core/src/main/java/org/springframework/credhub/support/utils/JsonUtils.java +++ b/spring-credhub-core/src/main/java/org/springframework/credhub/support/utils/JsonUtils.java @@ -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 subtypes = new ArrayList(); + List subtypes = new ArrayList<>(); for (CredentialType type : CredentialType.values()) { subtypes.add(new NamedType(type.getModelClass(), type.getValueType())); } diff --git a/spring-credhub-core/src/test/java/org/springframework/credhub/configuration/ClientHttpConnectorFactoryTests.java b/spring-credhub-core/src/test/java/org/springframework/credhub/configuration/ClientHttpConnectorFactoryTests.java new file mode 100644 index 0000000..8e9dc7f --- /dev/null +++ b/spring-credhub-core/src/test/java/org/springframework/credhub/configuration/ClientHttpConnectorFactoryTests.java @@ -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(); + } +} \ No newline at end of file diff --git a/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubClientFactoryUnitTests.java b/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubRestTemplateFactoryUnitTests.java similarity index 65% rename from spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubClientFactoryUnitTests.java rename to spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubRestTemplateFactoryUnitTests.java index c30adbf..2daae55 100644 --- a/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubClientFactoryUnitTests.java +++ b/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubRestTemplateFactoryUnitTests.java @@ -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(); } } \ No newline at end of file diff --git a/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubWebClientFactoryTests.java b/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubWebClientFactoryTests.java new file mode 100644 index 0000000..d33eb26 --- /dev/null +++ b/spring-credhub-core/src/test/java/org/springframework/credhub/core/CredHubWebClientFactoryTests.java @@ -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(); + } +} \ No newline at end of file diff --git a/spring-credhub-core/src/test/java/org/springframework/credhub/support/JsonParsingUnitTestsBase.java b/spring-credhub-core/src/test/java/org/springframework/credhub/support/JsonParsingUnitTestsBase.java index 34835f8..14a6614 100644 --- a/spring-credhub-core/src/test/java/org/springframework/credhub/support/JsonParsingUnitTestsBase.java +++ b/spring-credhub-core/src/test/java/org/springframework/credhub/support/JsonParsingUnitTestsBase.java @@ -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 parseResponse(String json, Class type) { diff --git a/spring-credhub-demo/build.gradle b/spring-credhub-demo/build.gradle index bf1ac81..a0a5b67 100644 --- a/spring-credhub-demo/build.gradle +++ b/spring-credhub-demo/build.gradle @@ -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" } } diff --git a/spring-credhub-integration-tests/build.gradle b/spring-credhub-integration-tests/build.gradle index f0db614..1f50f11 100644 --- a/spring-credhub-integration-tests/build.gradle +++ b/spring-credhub-integration-tests/build.gradle @@ -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}") } diff --git a/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/CredHubIntegrationTests.java b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/CredHubIntegrationTests.java index 0f1bb27..25ac60b 100644 --- a/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/CredHubIntegrationTests.java +++ b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/CredHubIntegrationTests.java @@ -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 { diff --git a/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredHubIntegrationTests.java b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredHubIntegrationTests.java new file mode 100644 index 0000000..2ede8fa --- /dev/null +++ b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredHubIntegrationTests.java @@ -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 + } + } +} diff --git a/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredentialIntegrationTests.java b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredentialIntegrationTests.java new file mode 100644 index 0000000..1d9ec01 --- /dev/null +++ b/spring-credhub-integration-tests/src/test/java/org/springframework/credhub/integration/ReactiveCredentialIntegrationTests.java @@ -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> 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> 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> 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> 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> 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(); + } +} diff --git a/spring-credhub-starter/build.gradle b/spring-credhub-starter/build.gradle index 031b8fa..7b45afd 100644 --- a/spring-credhub-starter/build.gradle +++ b/spring-credhub-starter/build.gradle @@ -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') } diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubAutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubAutoConfiguration.java index 6e7be27..c4b5f00 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubAutoConfiguration.java +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubAutoConfiguration.java @@ -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. */ diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java new file mode 100644 index 0000000..de471f8 --- /dev/null +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2AutoConfiguration.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2TemplateAutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2TemplateAutoConfiguration.java index 8bbfee5..9f46050 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2TemplateAutoConfiguration.java +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubOAuth2TemplateAutoConfiguration.java @@ -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); } } diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java index 8a34e22..2eae464 100644 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java +++ b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/CredHubTemplateAutoConfiguration.java @@ -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); + } + } diff --git a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/CredHubCredentialsDetails.java b/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/CredHubCredentialsDetails.java deleted file mode 100644 index ea2684d..0000000 --- a/spring-credhub-starter/src/main/java/org/springframework/credhub/autoconfig/security/CredHubCredentialsDetails.java +++ /dev/null @@ -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 { -} diff --git a/spring-credhub-starter/src/main/resources/META-INF/spring.factories b/spring-credhub-starter/src/main/resources/META-INF/spring.factories index 35706ff..a747250 100644 --- a/spring-credhub-starter/src/main/resources/META-INF/spring.factories +++ b/spring-credhub-starter/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file +org.springframework.credhub.autoconfig.CredHubOAuth2TemplateAutoConfiguration diff --git a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubAutoConfigurationTests.java b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubAutoConfigurationTests.java index 5a52c28..2d2191b 100644 --- a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubAutoConfigurationTests.java +++ b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubAutoConfigurationTests.java @@ -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); diff --git a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2AutoConfigurationTests.java b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2AutoConfigurationTests.java new file mode 100644 index 0000000..afe57c0 --- /dev/null +++ b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2AutoConfigurationTests.java @@ -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); + }); + } +} diff --git a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2TemplateAutoConfigurationTests.java b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2TemplateAutoConfigurationTests.java index e46b559..fce4374 100644 --- a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2TemplateAutoConfigurationTests.java +++ b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubOAuth2TemplateAutoConfigurationTests.java @@ -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); + }); } } diff --git a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubTemplateAutoConfigurationTests.java b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubTemplateAutoConfigurationTests.java index 47b0586..42c580e 100644 --- a/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubTemplateAutoConfigurationTests.java +++ b/spring-credhub-starter/src/test/java/org/springframework/credhub/configuration/CredHubTemplateAutoConfigurationTests.java @@ -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); }); } }