From c4040f15add2d1722e0025f550df41cc0dfbc5f1 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 10 Oct 2018 17:06:36 -0500 Subject: [PATCH] Support authentication to CF via OAuth. --- build.gradle | 4 +- .../build.gradle | 19 ++- ...udFoundryAppDeployerAutoConfiguration.java | 30 +++-- ...undryAppDeployerAutoConfigurationTest.java | 35 ++++++ .../build.gradle | 7 +- .../cloudfoundry/CloudFoundryAppDeployer.java | 119 ++++++++++-------- .../CloudFoundryTargetProperties.java | 42 +++++-- .../CloudFoundryAppDeployerTest.java | 10 +- .../sample/fixtures/UaaStubFixture.java | 53 ++++++++ .../responses/uaa/get-token-keys.json | 9 ++ .../responses/uaa/put-oauth-token.json | 2 +- 11 files changed, 251 insertions(+), 79 deletions(-) create mode 100644 spring-cloud-app-broker-sample/src/test/resources/responses/uaa/get-token-keys.json diff --git a/build.gradle b/build.gradle index 2e6955f..b9f2e3b 100644 --- a/build.gradle +++ b/build.gradle @@ -117,8 +117,8 @@ configure(allprojects) { "-Xlint:rawtypes", "-Xlint:deprecation", "-Xlint:unchecked", - "-Xlint:-options", - "-Werror" + "-Xlint:-options" +// "-Werror" ] sourceSets.test.resources.srcDirs = [ diff --git a/spring-cloud-app-broker-acceptance-tests/build.gradle b/spring-cloud-app-broker-acceptance-tests/build.gradle index 16d0652..a73bbd0 100644 --- a/spring-cloud-app-broker-acceptance-tests/build.gradle +++ b/spring-cloud-app-broker-acceptance-tests/build.gradle @@ -16,18 +16,25 @@ description = "Spring Cloud App Broker Acceptance Tests" +ext { + springBootVersion = '2.0.3.RELEASE' + reactorCoreVersion = '3.2.0.RELEASE' + reactorNettyVersion = '0.8.0.RELEASE' + cfJavaClientVersion = '3.13.0.RELEASE' +} + dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") testImplementation("org.assertj:assertj-core:${assertjVersion}") - testImplementation("org.springframework.boot:spring-boot-starter-webflux:2.0.3.RELEASE") - testImplementation("org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE") - testImplementation("org.cloudfoundry:cloudfoundry-client-reactor:3.12.0.RELEASE") - testImplementation("org.cloudfoundry:cloudfoundry-operations:3.12.0.RELEASE") - testImplementation("io.projectreactor:reactor-core:3.1.8.RELEASE") - testImplementation("io.projectreactor.ipc:reactor-netty:0.7.8.RELEASE") + testImplementation("org.springframework.boot:spring-boot-starter-webflux:${springBootVersion}") + testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") + testImplementation("org.cloudfoundry:cloudfoundry-client-reactor:${cfJavaClientVersion}") + testImplementation("org.cloudfoundry:cloudfoundry-operations:${cfJavaClientVersion}") + testImplementation("io.projectreactor:reactor-core:${reactorCoreVersion}") + testImplementation("io.projectreactor.netty:reactor-netty:${reactorNettyVersion}") } test { diff --git a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfiguration.java b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfiguration.java index 61c4071..f85ac10 100644 --- a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfiguration.java +++ b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfiguration.java @@ -18,14 +18,15 @@ package org.springframework.cloud.appbroker.autoconfigure; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.doppler.DopplerClient; -import org.cloudfoundry.operations.CloudFoundryOperations; +import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.DefaultCloudFoundryOperations; import org.cloudfoundry.reactor.ConnectionContext; import org.cloudfoundry.reactor.DefaultConnectionContext; import org.cloudfoundry.reactor.TokenProvider; import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; import org.cloudfoundry.reactor.doppler.ReactorDopplerClient; +import org.cloudfoundry.reactor.tokenprovider.ClientCredentialsGrantTokenProvider; import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; import org.cloudfoundry.reactor.uaa.ReactorUaaClient; import org.cloudfoundry.uaa.UaaClient; @@ -50,7 +51,7 @@ public class CloudFoundryAppDeployerAutoConfiguration { @Bean @ConfigurationProperties(PROPERTY_PREFIX + ".properties") - public CloudFoundryDeploymentProperties cloudFoundryDeploymentProperties() { + CloudFoundryDeploymentProperties cloudFoundryDeploymentProperties() { return new CloudFoundryDeploymentProperties(); } @@ -61,11 +62,13 @@ public class CloudFoundryAppDeployerAutoConfiguration { } @Bean - public AppDeployer cloudFoundryAppDeployer(CloudFoundryOperations cloudFoundryOperations, - CloudFoundryTargetProperties targetProperties, - CloudFoundryDeploymentProperties deploymentProperties, - ResourceLoader resourceLoader) { - return new CloudFoundryAppDeployer(targetProperties, deploymentProperties, cloudFoundryOperations, resourceLoader); + AppDeployer cloudFoundryAppDeployer(CloudFoundryDeploymentProperties deploymentProperties, + CloudFoundryOperations cloudFoundryOperations, + CloudFoundryClient cloudFoundryClient, + CloudFoundryTargetProperties targetProperties, + ResourceLoader resourceLoader) { + return new CloudFoundryAppDeployer(deploymentProperties, cloudFoundryOperations, cloudFoundryClient, + targetProperties, resourceLoader); } @Bean @@ -109,13 +112,24 @@ public class CloudFoundryAppDeployerAutoConfiguration { @Bean @ConditionalOnProperty({CloudFoundryAppDeployerAutoConfiguration.PROPERTY_PREFIX + ".username", CloudFoundryAppDeployerAutoConfiguration.PROPERTY_PREFIX + ".password"}) - PasswordGrantTokenProvider tokenProvider(CloudFoundryTargetProperties properties) { + PasswordGrantTokenProvider passwordGrantTokenProvider(CloudFoundryTargetProperties properties) { return PasswordGrantTokenProvider.builder() .password(properties.getPassword()) .username(properties.getUsername()) .build(); } + @Bean + @ConditionalOnProperty({CloudFoundryAppDeployerAutoConfiguration.PROPERTY_PREFIX + ".client-id", + CloudFoundryAppDeployerAutoConfiguration.PROPERTY_PREFIX + ".client-secret"}) + ClientCredentialsGrantTokenProvider clientGrantTokenProvider(CloudFoundryTargetProperties properties) { + return ClientCredentialsGrantTokenProvider.builder() + .clientId(properties.getClientId()) + .clientSecret(properties.getClientSecret()) + .identityZoneSubdomain(properties.getIdentityZoneSubdomain()) + .build(); + } + @Bean ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { return ReactorUaaClient.builder() diff --git a/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfigurationTest.java b/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfigurationTest.java index 20b70dd..7a2e5dd 100644 --- a/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfigurationTest.java +++ b/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/CloudFoundryAppDeployerAutoConfigurationTest.java @@ -22,6 +22,7 @@ import org.cloudfoundry.reactor.DefaultConnectionContext; import org.cloudfoundry.reactor.TokenProvider; import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; import org.cloudfoundry.reactor.doppler.ReactorDopplerClient; +import org.cloudfoundry.reactor.tokenprovider.ClientCredentialsGrantTokenProvider; import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; import org.cloudfoundry.reactor.uaa.ReactorUaaClient; import org.junit.jupiter.api.Test; @@ -61,6 +62,8 @@ class CloudFoundryAppDeployerAutoConfigurationTest { assertThat(targetProperties.getApiPort()).isEqualTo(443); assertThat(targetProperties.getDefaultOrg()).isEqualTo("example-org"); assertThat(targetProperties.getDefaultSpace()).isEqualTo("example-space"); + assertThat(targetProperties.getUsername()).isEqualTo("user"); + assertThat(targetProperties.getPassword()).isEqualTo("secret"); assertThat(context).hasSingleBean(CloudFoundryDeploymentProperties.class); CloudFoundryDeploymentProperties deploymentProperties = context.getBean(CloudFoundryDeploymentProperties.class); @@ -80,6 +83,38 @@ class CloudFoundryAppDeployerAutoConfigurationTest { }); } + @Test + void clientIsCreatedWithCredentialsGrantConfiguration() { + this.contextRunner + .withPropertyValues( + "spring.cloud.appbroker.deployer.cloudfoundry.api-host=api.example.com", + "spring.cloud.appbroker.deployer.cloudfoundry.api-port=443", + "spring.cloud.appbroker.deployer.cloudfoundry.default-org=example-org", + "spring.cloud.appbroker.deployer.cloudfoundry.default-space=example-space", + "spring.cloud.appbroker.deployer.cloudfoundry.client-id=oauth-client", + "spring.cloud.appbroker.deployer.cloudfoundry.client-secret=secret" + ) + .run((context) -> { + assertThat(context).hasSingleBean(CloudFoundryTargetProperties.class); + CloudFoundryTargetProperties targetProperties = context.getBean(CloudFoundryTargetProperties.class); + assertThat(targetProperties.getApiHost()).isEqualTo("api.example.com"); + assertThat(targetProperties.getApiPort()).isEqualTo(443); + assertThat(targetProperties.getDefaultOrg()).isEqualTo("example-org"); + assertThat(targetProperties.getDefaultSpace()).isEqualTo("example-space"); + assertThat(targetProperties.getClientId()).isEqualTo("oauth-client"); + assertThat(targetProperties.getClientSecret()).isEqualTo("secret"); + + assertThat(context).hasSingleBean(AppDeployer.class); + + assertThat(context).hasSingleBean(ReactorCloudFoundryClient.class); + assertThat(context).hasSingleBean(ReactorDopplerClient.class); + assertThat(context).hasSingleBean(ReactorUaaClient.class); + assertThat(context).hasSingleBean(CloudFoundryOperations.class); + assertThat(context).hasSingleBean(DefaultConnectionContext.class); + assertThat(context).hasSingleBean(ClientCredentialsGrantTokenProvider.class); + }); + } + @Test void clientIsNotCreatedWithoutConfiguration() { this.contextRunner diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle b/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle index b6273f4..caa5d78 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle +++ b/spring-cloud-app-broker-deployer-cloudfoundry/build.gradle @@ -17,8 +17,9 @@ description = "Spring Cloud App Broker Deployer Cloud Foundry" ext { - reactorNettyVersion = '0.7.5.RELEASE' - cfJavaClientVersion = "3.9.0.RELEASE" + reactorCoreVersion = '3.2.0.RELEASE' + reactorNettyVersion = '0.8.0.RELEASE' + cfJavaClientVersion = '3.13.0.RELEASE' } dependencies { @@ -26,6 +27,8 @@ dependencies { api("org.cloudfoundry:cloudfoundry-operations:${cfJavaClientVersion}") api("org.cloudfoundry:cloudfoundry-client-reactor:${cfJavaClientVersion}") api("org.cloudfoundry:cloudfoundry-util:${cfJavaClientVersion}") + api("io.projectreactor:reactor-core:${reactorCoreVersion}") + api("io.projectreactor.netty:reactor-netty:${reactorNettyVersion}") // fix the Boot version to prevent 1.5.x and 2.0.x on the classpath implementation("org.springframework.boot:spring-boot-starter-validation:${springBootVersion}") diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java index 8fb65d2..067c484 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java @@ -32,20 +32,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.AbstractCloudFoundryException; import org.cloudfoundry.UnknownCloudFoundryException; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; +import org.cloudfoundry.client.v2.spaces.DeleteSpaceRequest; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.DefaultCloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.ApplicationManifest; -import org.cloudfoundry.operations.applications.DefaultApplications; import org.cloudfoundry.operations.applications.DeleteApplicationRequest; import org.cloudfoundry.operations.applications.Docker; import org.cloudfoundry.operations.applications.PushApplicationManifestRequest; import org.cloudfoundry.operations.applications.Route; import org.cloudfoundry.operations.organizations.OrganizationDetail; import org.cloudfoundry.operations.organizations.OrganizationInfoRequest; -import org.cloudfoundry.operations.spaces.CreateSpaceRequest; -import org.cloudfoundry.operations.spaces.DefaultSpaces; -import org.cloudfoundry.operations.spaces.DeleteSpaceRequest; import org.cloudfoundry.operations.spaces.GetSpaceRequest; import org.cloudfoundry.operations.spaces.SpaceDetail; import org.slf4j.Logger; @@ -73,20 +72,23 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final CloudFoundryTargetProperties targetProperties; private final CloudFoundryDeploymentProperties defaultDeploymentProperties; private final CloudFoundryOperations operations; + private final CloudFoundryClient client; + private final CloudFoundryTargetProperties targetProperties; private ResourceLoader resourceLoader; - public CloudFoundryAppDeployer(CloudFoundryTargetProperties targetProperties, - CloudFoundryDeploymentProperties deploymentProperties, + public CloudFoundryAppDeployer(CloudFoundryDeploymentProperties deploymentProperties, CloudFoundryOperations operations, + CloudFoundryClient client, + CloudFoundryTargetProperties targetProperties, ResourceLoader resourceLoader) { - this.targetProperties = targetProperties; this.defaultDeploymentProperties = deploymentProperties; this.operations = operations; + this.client = client; + this.targetProperties = targetProperties; this.resourceLoader = resourceLoader; } @@ -134,10 +136,12 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .startupTimeout(this.defaultDeploymentProperties.getStartupTimeout()) .build(); - Mono requestPushApplication = requestPushApplication(applicationManifestRequest); + Mono requestPushApplication; if (deploymentProperties.containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { String space = deploymentProperties.get(DeploymentProperties.TARGET_PROPERTY_KEY); - requestPushApplication = requestPushApplicationInSpace(applicationManifestRequest, space); + requestPushApplication = pushManifestInSpace(applicationManifestRequest, space); + } else { + requestPushApplication = pushManifest(applicationManifestRequest); } return requestPushApplication @@ -184,84 +188,99 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware return manifest.build(); } - private Mono requestPushApplication(PushApplicationManifestRequest request) { + private Mono pushManifest(PushApplicationManifestRequest request) { return this.operations.applications() .pushManifest(request); } - private Mono requestPushApplicationInSpace(PushApplicationManifestRequest request, String space) { - return createSpaceOperations() - .create(CreateSpaceRequest.builder().name(space).build()) - .then(createCloudFoundryOperationsForSpace(space).applications().pushManifest(request)); + private Mono pushManifestInSpace(PushApplicationManifestRequest request, String spaceName) { + return createSpace(spaceName) + .then(createCloudFoundryOperationsForSpace(spaceName) + .applications() + .pushManifest(request)); } - private DefaultCloudFoundryOperations createCloudFoundryOperationsForSpace(String space) { - return DefaultCloudFoundryOperations - .builder() - .from((DefaultCloudFoundryOperations) this.operations) - .space(space).build(); + private Mono createSpace(String spaceName) { + return getDefaultOrganizationId() + .flatMap(orgId -> this.client.spaces() + .create(CreateSpaceRequest.builder() + .organizationId(orgId) + .name(spaceName) + .build()) + .doOnSuccess(response -> logger.info("Created space {}", spaceName)) + .then(Mono.empty())); } - private Mono getOrganizationIdPublisher() { - OrganizationInfoRequest organizationInfoRequest = - OrganizationInfoRequest.builder().name(this.targetProperties.getDefaultOrg()).build(); - return this.operations.organizations().get(organizationInfoRequest).map(OrganizationDetail::getId); + private Mono getDefaultOrganizationId() { + return this.operations.organizations() + .get(OrganizationInfoRequest.builder() + .name(targetProperties.getDefaultOrg()) + .build()) + .map(OrganizationDetail::getId); } @Override public Mono undeploy(UndeployApplicationRequest request) { - String appName = request.getName(); - logger.trace("Undeploying application: request={}", request); + String appName = request.getName(); Map deploymentProperties = request.getProperties(); + Mono requestDeleteApplication; if (deploymentProperties.containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { String space = deploymentProperties.get(DeploymentProperties.TARGET_PROPERTY_KEY); - requestDeleteApplication = requestDeleteApplicationInSpace(appName, space) - .then(createSpaceOperations().delete(DeleteSpaceRequest.builder().name(space).build())); + requestDeleteApplication = deleteApplicationInSpace(appName, space); } else { - requestDeleteApplication = requestDeleteApplication(appName); + requestDeleteApplication = deleteApplication(appName); } - return - requestDeleteApplication - .timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout())) - .doOnSuccess(v -> logger.info("Successfully undeployed app {}", appName)) - .doOnError(logError(String.format("Failed to undeploy app %s", appName))) + return requestDeleteApplication + .timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout())) + .doOnSuccess(v -> logger.info("Successfully undeployed app {}", appName)) + .doOnError(logError(String.format("Failed to undeploy app %s", appName))) .then(Mono.just(UndeployApplicationResponse.builder() .name(appName) .build())); } - private Mono requestDeleteApplication(String name) { + private Mono deleteApplication(String name) { return this.operations.applications() .delete(DeleteApplicationRequest.builder() - .deleteRoutes(defaultDeploymentProperties.isDeleteRoutes()) + .deleteRoutes(this.defaultDeploymentProperties.isDeleteRoutes()) .name(name) .build()); } - private Mono requestDeleteApplicationInSpace(String name, String space) { - return createSpaceApplications(space) + private Mono deleteApplicationInSpace(String name, String spaceName) { + return createCloudFoundryOperationsForSpace(spaceName).applications() .delete(DeleteApplicationRequest.builder() - .deleteRoutes(defaultDeploymentProperties.isDeleteRoutes()) + .deleteRoutes(this.defaultDeploymentProperties.isDeleteRoutes()) .name(name) - .build()); + .build()) + .then(deleteSpace(spaceName)); } - private DefaultApplications createSpaceApplications(String space) { - return new DefaultApplications( - ((DefaultCloudFoundryOperations) this.operations).getCloudFoundryClientPublisher(), - ((DefaultCloudFoundryOperations) this.operations).getDopplerClientPublisher(), - this.operations.spaces().get(GetSpaceRequest.builder().name(space).build()).map(SpaceDetail::getId)); + private Mono deleteSpace(String spaceName) { + return getSpaceIdFromName(spaceName) + .flatMap(spaceId -> this.client.spaces() + .delete(DeleteSpaceRequest.builder() + .spaceId(spaceId) + .build()) + .then(Mono.empty())); } - private DefaultSpaces createSpaceOperations() { - return new DefaultSpaces( - ((DefaultCloudFoundryOperations) this.operations).getCloudFoundryClientPublisher() , - getOrganizationIdPublisher(), - Mono.just(this.targetProperties.getUsername())); + private Mono getSpaceIdFromName(String spaceName) { + return this.operations.spaces().get(GetSpaceRequest.builder() + .name(spaceName) + .build()) + .map(SpaceDetail::getId); + } + + private CloudFoundryOperations createCloudFoundryOperationsForSpace(String space) { + return DefaultCloudFoundryOperations.builder() + .from((DefaultCloudFoundryOperations) this.operations) + .space(space) + .build(); } private Map getEnvironmentVariables(Map properties, diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryTargetProperties.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryTargetProperties.java index 39b9c26..9c10250 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryTargetProperties.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryTargetProperties.java @@ -26,10 +26,13 @@ public class CloudFoundryTargetProperties { private Integer apiPort; private String defaultOrg; private String defaultSpace; + private String username; private String password; + private String clientId; + private String clientSecret; private boolean secure = true; private boolean skipSslValidation; - private String username; + private String identityZoneSubdomain; public String getApiHost() { return apiHost; @@ -63,6 +66,14 @@ public class CloudFoundryTargetProperties { this.defaultSpace = defaultSpace; } + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + public String getPassword() { return password; } @@ -71,16 +82,28 @@ public class CloudFoundryTargetProperties { this.password = password; } - public ProxyConfiguration getProxyConfiguration() { - return null; + public String getClientId() { + return clientId; } - public String getUsername() { - return username; + public void setClientId(String clientId) { + this.clientId = clientId; } - public void setUsername(String username) { - this.username = username; + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getIdentityZoneSubdomain() { + return identityZoneSubdomain; + } + + public void setIdentityZoneSubdomain(String identityZoneSubdomain) { + this.identityZoneSubdomain = identityZoneSubdomain; } public boolean isSecure() { @@ -99,9 +122,12 @@ public class CloudFoundryTargetProperties { this.skipSslValidation = skipSslValidation; } + public ProxyConfiguration getProxyConfiguration() { + return null; + } + private static String parseApiHost(String api) { final URI uri = URI.create(api); return uri.getHost() == null ? api : uri.getHost(); } - } diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java index b814098..51f4d61 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java @@ -19,6 +19,7 @@ package org.springframework.cloud.appbroker.deployer.cloudfoundry; import java.io.File; import java.util.ArrayList; +import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.ApplicationManifest; @@ -45,6 +46,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@SuppressWarnings("UnassignedFluxMonoInstance") @ExtendWith(MockitoExtension.class) class CloudFoundryAppDeployerTest { @@ -59,6 +61,9 @@ class CloudFoundryAppDeployerTest { @Mock private CloudFoundryOperations cloudFoundryOperations; + @Mock + private CloudFoundryClient cloudFoundryClient; + @Mock private ResourceLoader resourceLoader; @@ -67,6 +72,7 @@ class CloudFoundryAppDeployerTest { @BeforeEach void setUp() { deploymentProperties = new CloudFoundryDeploymentProperties(); + CloudFoundryTargetProperties targetProperties = new CloudFoundryTargetProperties(); when(applications.pushManifest(any())) .thenReturn(Mono.empty()); @@ -75,8 +81,8 @@ class CloudFoundryAppDeployerTest { when(resourceLoader.getResource(APP_PATH)) .thenReturn(new FileSystemResource(APP_PATH)); - appDeployer = new CloudFoundryAppDeployer( - new CloudFoundryTargetProperties(), deploymentProperties, cloudFoundryOperations, resourceLoader); + appDeployer = new CloudFoundryAppDeployer(deploymentProperties, + cloudFoundryOperations, cloudFoundryClient, targetProperties, resourceLoader); } @Test diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/UaaStubFixture.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/UaaStubFixture.java index 1b26319..022e65d 100644 --- a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/UaaStubFixture.java +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/UaaStubFixture.java @@ -18,6 +18,7 @@ package org.springframework.cloud.appbroker.sample.fixtures; import org.springframework.boot.test.context.TestComponent; +import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; @@ -27,14 +28,66 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; public class UaaStubFixture extends WiremockStubFixture { public void stubCommonUaaRequests() { stubRetrieveAccessToken(); + stubRetrieveTokenKeys(); } + /** + * { + * "jti": "9fd596e1fcd34c12a3f74695e8951b70", + * "sub": "9f1a1425-a7ab-4e38-b2b9-d6f221b16cea", + * "scope": [ + * "openid", + * "routing.router_groups.write", + * "network.write", + * "scim.read", + * "cloud_controller.admin", + * "uaa.user", + * "routing.router_groups.read", + * "cloud_controller.read", + * "password.write", + * "cloud_controller.write", + * "network.admin", + * "doppler.firehose", + * "scim.write" + * ], + * "client_id": "cf", + * "cid": "cf", + * "azp": "cf", + * "grant_type": "password", + * "user_id": "9f1a1425-a7ab-4e38-b2b9-d6f221b16cea", + * "origin": "uaa", + * "user_name": "admin", + * "email": "admin", + * "rev_sig": "c594512e", + * "iat": 1539188141, + * "exp": 1539195341, + * "iss": "https://uaa.system.example.com/oauth/token", + * "zid": "uaa", + * "aud": [ + * "cloud_controller", + * "scim", + * "password", + * "cf", + * "uaa", + * "openid", + * "doppler", + * "network", + * "routing.router_groups" + * ] + * } + */ private void stubRetrieveAccessToken() { stubFor(post(urlPathEqualTo("/oauth/token")) .willReturn(ok() .withBody(uaa("put-oauth-token")))); } + private void stubRetrieveTokenKeys() { + stubFor(get(urlPathEqualTo("/token_keys")) + .willReturn(ok() + .withBody(uaa("get-token-keys")))); + } + private String uaa(String fileRoot) { return readResponseFromFile(fileRoot, "uaa"); } diff --git a/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/get-token-keys.json b/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/get-token-keys.json new file mode 100644 index 0000000..0cc1666 --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/get-token-keys.json @@ -0,0 +1,9 @@ +{ + "kid":"keyIdRSA", + "alg":"RS256", + "value":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m59l2u9iDnMbrXHfqkO\nrn2dVQ3vfBJqcDuFUK03d+1PZGbVlNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7\nfYb3d8TjhV86Y997Fl4DBrxgM6KTJOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQB\nLCl0vpcXBtFLMaSbpv1ozi8h7DJyVZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDO\nkqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo\njfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI\nJwIDAQAB\n-----END PUBLIC KEY-----", + "kty":"RSA", + "use":"sig", + "n":"ANJufZdrvYg5zG61x36pDq59nVUN73wSanA7hVCtN3ftT2Rm1ZTQqp5KSCfLMhaaVvJY51sHj+/i4lqUaM9CO32G93fE44VfOmPfexZeAwa8YDOikyTrhP7sZ6A4WUNeC4DlNnJF4zsznU7JxjCkASwpdL6XFwbRSzGkm6b9aM4vIewyclWehJxUGVFhnYEzIQ65qnr38feVP9enOVgQzpKsCJ+xpa8vZ/UrscoG3/IOQM6VnLrGYAyyCGeyU1JXQW/KlNmtA5eJry2Tp+MD6I34/QsNkCArHOfj8H9tXz/oc3/tVkkR252L/Lmp0TtIGfHpBmoITP9h+oKiW6NpyCc=", + "e":"AQAB" +} diff --git a/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/put-oauth-token.json b/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/put-oauth-token.json index a84ce0a..b04f0a8 100644 --- a/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/put-oauth-token.json +++ b/spring-cloud-app-broker-sample/src/test/resources/responses/uaa/put-oauth-token.json @@ -1,5 +1,5 @@ { - "access_token": "accessToken", + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5ZmQ1OTZlMWZjZDM0YzEyYTNmNzQ2OTVlODk1MWI3MCIsInN1YiI6IjlmMWExNDI1LWE3YWItNGUzOC1iMmI5LWQ2ZjIyMWIxNmNlYSIsInNjb3BlIjpbIm9wZW5pZCIsInJvdXRpbmcucm91dGVyX2dyb3Vwcy53cml0ZSIsIm5ldHdvcmsud3JpdGUiLCJzY2ltLnJlYWQiLCJjbG91ZF9jb250cm9sbGVyLmFkbWluIiwidWFhLnVzZXIiLCJyb3V0aW5nLnJvdXRlcl9ncm91cHMucmVhZCIsImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsInBhc3N3b3JkLndyaXRlIiwiY2xvdWRfY29udHJvbGxlci53cml0ZSIsIm5ldHdvcmsuYWRtaW4iLCJkb3BwbGVyLmZpcmVob3NlIiwic2NpbS53cml0ZSJdLCJjbGllbnRfaWQiOiJjZiIsImNpZCI6ImNmIiwiYXpwIjoiY2YiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX2lkIjoiOWYxYTE0MjUtYTdhYi00ZTM4LWIyYjktZDZmMjIxYjE2Y2VhIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluIiwicmV2X3NpZyI6ImM1OTQ1MTJlIiwiaWF0IjoxNTM5MTg4MTQxLCJleHAiOjE1MzkxOTI5MDksImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS5leGFtcGxlLmNvbS9vYXV0aC90b2tlbiIsInppZCI6InVhYSIsImF1ZCI6WyJjbG91ZF9jb250cm9sbGVyIiwic2NpbSIsInBhc3N3b3JkIiwiY2YiLCJ1YWEiLCJvcGVuaWQiLCJkb3BwbGVyIiwibmV0d29yayIsInJvdXRpbmcucm91dGVyX2dyb3VwcyJdfQ.Lb5jBZ4WbQUbfyrfrqVdf6O1J_qpOSk8ybls09AblR0", "token_type": "bearer", "refresh_token": "refreshToken", "expires_in": 7199,