/*
* 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.cloudfoundry.uaa;
import io.netty.util.AsciiString;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.uaa.clients.BatchChangeSecretRequest;
import org.cloudfoundry.uaa.clients.BatchChangeSecretResponse;
import org.cloudfoundry.uaa.clients.BatchCreateClientsRequest;
import org.cloudfoundry.uaa.clients.BatchCreateClientsResponse;
import org.cloudfoundry.uaa.clients.BatchDeleteClientsRequest;
import org.cloudfoundry.uaa.clients.BatchUpdateClientsRequest;
import org.cloudfoundry.uaa.clients.BatchUpdateClientsResponse;
import org.cloudfoundry.uaa.clients.ChangeSecret;
import org.cloudfoundry.uaa.clients.ChangeSecretRequest;
import org.cloudfoundry.uaa.clients.Client;
import org.cloudfoundry.uaa.clients.CreateClient;
import org.cloudfoundry.uaa.clients.CreateClientAction;
import org.cloudfoundry.uaa.clients.CreateClientRequest;
import org.cloudfoundry.uaa.clients.CreateClientResponse;
import org.cloudfoundry.uaa.clients.DeleteClientAction;
import org.cloudfoundry.uaa.clients.DeleteClientRequest;
import org.cloudfoundry.uaa.clients.GetClientRequest;
import org.cloudfoundry.uaa.clients.GetMetadataRequest;
import org.cloudfoundry.uaa.clients.GetMetadataResponse;
import org.cloudfoundry.uaa.clients.ListClientsRequest;
import org.cloudfoundry.uaa.clients.ListClientsResponse;
import org.cloudfoundry.uaa.clients.ListMetadatasRequest;
import org.cloudfoundry.uaa.clients.ListMetadatasResponse;
import org.cloudfoundry.uaa.clients.MixedActionsRequest;
import org.cloudfoundry.uaa.clients.UpdateClient;
import org.cloudfoundry.uaa.clients.UpdateClientAction;
import org.cloudfoundry.uaa.clients.UpdateClientRequest;
import org.cloudfoundry.uaa.clients.UpdateMetadataRequest;
import org.cloudfoundry.uaa.clients.UpdateMetadataResponse;
import org.cloudfoundry.uaa.clients.UpdateSecretAction;
import org.cloudfoundry.uaa.clients.UpdateSecretClientAction;
import org.cloudfoundry.util.PaginationUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.cloudfoundry.uaa.tokens.GrantType.CLIENT_CREDENTIALS;
import static org.cloudfoundry.uaa.tokens.GrantType.IMPLICIT;
import static org.cloudfoundry.uaa.tokens.GrantType.PASSWORD;
import static org.cloudfoundry.uaa.tokens.GrantType.REFRESH_TOKEN;
public final class ClientsTest extends AbstractIntegrationTest {
@Autowired
private String clientId;
@Autowired
private UaaClient uaaClient;
@Test
public void batchChangeSecret() throws TimeoutException, InterruptedException {
String clientId1 = this.nameFactory.getClientId();
String clientId2 = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
String newClientSecret1 = this.nameFactory.getClientSecret();
String newClientSecret2 = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId1, clientSecret)
.then(requestCreateClient(this.uaaClient, clientId2, clientSecret))
.then(this.uaaClient.clients()
.batchChangeSecret(BatchChangeSecretRequest.builder()
.changeSecret(ChangeSecret.builder()
.clientId(clientId1)
.oldSecret(clientSecret)
.secret(newClientSecret1)
.build(),
ChangeSecret.builder()
.clientId(clientId2)
.oldSecret(clientSecret)
.secret(newClientSecret2)
.build())
.build()))
.flatMapIterable(BatchChangeSecretResponse::getClients)
.as(StepVerifier::create)
.expectNextCount(2)
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void batchCreate() throws TimeoutException, InterruptedException {
String clientId1 = this.nameFactory.getClientId();
String clientId2 = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
this.uaaClient.clients()
.batchCreate(BatchCreateClientsRequest.builder()
.client(CreateClient.builder()
.approvalsDeleted(true)
.authorizedGrantType(PASSWORD)
.clientId(clientId1)
.clientSecret(clientSecret)
.scope("client.read", "client.write")
.tokenSalt("test-token-salt")
.build())
.client(CreateClient.builder()
.approvalsDeleted(true)
.authorizedGrantType(PASSWORD, REFRESH_TOKEN)
.clientId(clientId2)
.clientSecret(clientSecret)
.scope("client.write")
.tokenSalt("filtered-test-token-salt")
.build())
.build())
.flatMapIterable(BatchCreateClientsResponse::getClients)
.filter(client -> clientId1.equals(client.getClientId()))
.as(StepVerifier::create)
.consumeNextWith(response -> {
assertThat(response.getAuthorizedGrantTypes()).containsExactly(PASSWORD, REFRESH_TOKEN);
assertThat(response.getClientId()).isEqualTo(clientId1);
assertThat(response.getScopes()).containsExactly("client.read", "client.write");
assertThat(response.getTokenSalt()).isEqualTo("test-token-salt");
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void batchDelete() throws TimeoutException, InterruptedException {
String clientId1 = this.nameFactory.getClientId();
String clientId2 = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
batchCreateClients(this.uaaClient, clientId1, clientId2, clientSecret)
.flatMapIterable(BatchCreateClientsResponse::getClients)
.map(Client::getClientId)
.collectList()
.then(clientIds -> this.uaaClient.clients()
.batchDelete(BatchDeleteClientsRequest.builder()
.clientIds(clientIds)
.build()))
.thenMany(requestListClients(this.uaaClient))
.filter(client -> clientId1.equals(client.getClientId()) || clientId2.equals(client.getClientId()))
.as(StepVerifier::create)
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void batchUpdate() throws TimeoutException, InterruptedException {
String clientId1 = this.nameFactory.getClientId();
String clientId2 = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId1, clientSecret)
.then(requestCreateClient(this.uaaClient, clientId2, clientSecret))
.then(this.uaaClient.clients()
.batchUpdate(BatchUpdateClientsRequest.builder()
.client(UpdateClient.builder()
.authorizedGrantType(CLIENT_CREDENTIALS, IMPLICIT)
.clientId(clientId1)
.name("test-name")
.scope("client.read", "client.write")
.tokenSalt("test-token-salt")
.build(),
UpdateClient.builder()
.authorizedGrantType(PASSWORD)
.clientId(clientId2)
.name("filtered-test-name")
.scope("client.write")
.tokenSalt("filtered-test-token-salt")
.build())
.build()))
.flatMapIterable(BatchUpdateClientsResponse::getClients)
.filter(client -> clientId1.equals(client.getClientId()))
.as(StepVerifier::create)
.consumeNextWith(response -> {
assertThat(response.getAuthorizedGrantTypes()).containsExactly(IMPLICIT, CLIENT_CREDENTIALS);
assertThat(response.getClientId()).isEqualTo(clientId1);
assertThat(response.getName()).isEqualTo("test-name");
assertThat(response.getScopes()).containsExactly("client.read", "client.write");
assertThat(response.getTokenSalt()).isEqualTo("test-token-salt");
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void changeSecret() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String newClientSecret = this.nameFactory.getClientSecret();
String oldClientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId, oldClientSecret)
.then(this.uaaClient.clients()
.changeSecret(ChangeSecretRequest.builder()
.clientId(clientId)
.oldSecret(oldClientSecret)
.secret(newClientSecret)
.build()))
.as(StepVerifier::create)
// TODO: Update test based on https://www.pivotaltracker.com/n/projects/997278/stories/130645469 to use the following
// .expectThat(response -> {
// assertEquals("secret updated", response.getMessage());
// assertEquals("ok", response.getStatus());
// }));
.consumeErrorWith(t -> assertThat(t).isInstanceOf(UaaException.class).hasMessage("invalid_client: Only a client can change client secret"))
.verify(Duration.ofMinutes(5));
}
@Test
public void create() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
this.uaaClient.clients()
.create(CreateClientRequest.builder()
.approvalsDeleted(true)
.authorizedGrantType(PASSWORD)
.clientId(clientId)
.clientSecret(clientSecret)
.scope("client.read", "client.write")
.tokenSalt("test-token-salt")
.build())
.as(StepVerifier::create)
.consumeNextWith(response -> {
assertThat(response.getAuthorizedGrantTypes()).containsExactly(PASSWORD, REFRESH_TOKEN);
assertThat(response.getClientId()).isEqualTo(clientId);
assertThat(response.getScopes()).containsExactly("client.read", "client.write");
assertThat(response.getTokenSalt()).isEqualTo("test-token-salt");
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void delete() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId, clientSecret)
.then(this.uaaClient.clients()
.delete(DeleteClientRequest.builder()
.clientId(clientId)
.build()))
.thenMany(requestListClients(this.uaaClient))
.filter(client -> clientId.equals(client.getClientId()))
.as(StepVerifier::create)
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void get() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId, clientSecret)
.then(this.uaaClient.clients()
.get(GetClientRequest.builder()
.clientId(clientId)
.build()))
.as(StepVerifier::create)
.consumeNextWith(response -> {
assertThat(response.getAuthorizedGrantTypes()).containsExactly(PASSWORD, REFRESH_TOKEN);
assertThat(response.getClientId()).isEqualTo(clientId);
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void getMetadata() throws TimeoutException, InterruptedException {
requestUpdateMetadata(this.uaaClient, this.clientId, "http://test.get.url")
.then(this.uaaClient.clients()
.getMetadata(GetMetadataRequest.builder()
.clientId(this.clientId)
.build()))
.as(StepVerifier::create)
.consumeNextWith(metadata -> {
assertThat(metadata.getAppLaunchUrl()).isEqualTo("http://test.get.url");
assertThat(metadata.getClientId()).isEqualTo(this.clientId);
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void list() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId, clientSecret)
.then(this.uaaClient.clients()
.list(ListClientsRequest.builder()
.build()))
.flatMapIterable(ListClientsResponse::getResources)
.filter(client -> clientId.equals(client.getClientId()))
.as(StepVerifier::create)
.expectNextCount(1)
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void listMetadatas() throws TimeoutException, InterruptedException {
requestUpdateMetadata(this.uaaClient, this.clientId, "http://test.list.url")
.then(this.uaaClient.clients()
.listMetadatas(ListMetadatasRequest.builder()
.build()))
.flatMapIterable(ListMetadatasResponse::getMetadatas)
.filter(metadata -> this.clientId.equals(metadata.getClientId()))
.single()
.as(StepVerifier::create)
.consumeNextWith(metadata -> {
assertThat(metadata.getAppLaunchUrl()).isEqualTo("http://test.list.url");
assertThat(metadata.getClientId()).isEqualTo(this.clientId);
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void mixedActions() throws TimeoutException, InterruptedException {
String clientId1 = this.nameFactory.getClientId();
String clientId2 = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
String newClientSecret = this.nameFactory.getClientSecret();
this.uaaClient.clients()
.mixedActions(MixedActionsRequest.builder()
.action(CreateClientAction.builder()
.authorizedGrantType(PASSWORD)
.clientId(clientId1)
.clientSecret(clientSecret)
.name("test-name-old")
.build())
.action(CreateClientAction.builder()
.authorizedGrantType(PASSWORD)
.clientId(clientId2)
.clientSecret(clientSecret)
.build())
.action(UpdateClientAction.builder()
.authorizedGrantType(PASSWORD)
.clientId(clientId1)
.name("test-name-temporary")
.build())
.action(UpdateSecretAction.builder()
.clientId(clientId2)
.secret(newClientSecret)
.build())
.action(DeleteClientAction.builder()
.clientId(clientId2)
.build())
.action(UpdateSecretClientAction.builder()
.authorizedGrantType(PASSWORD)
.name("test-name-new")
.clientId(clientId1)
.secret(newClientSecret)
.build())
.build())
.thenMany(requestListClients(this.uaaClient))
.filter(client -> clientId1.equals(client.getClientId()))
.as(StepVerifier::create)
.consumeNextWith(client -> assertThat(client.getName()).isEqualTo("test-name-new"))
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void update() throws TimeoutException, InterruptedException {
String clientId = this.nameFactory.getClientId();
String clientSecret = this.nameFactory.getClientSecret();
requestCreateClient(this.uaaClient, clientId, clientSecret)
.then(this.uaaClient.clients()
.update(UpdateClientRequest.builder()
.authorizedGrantType(CLIENT_CREDENTIALS)
.clientId(clientId)
.name("test-name")
.build()))
.thenMany(requestListClients(this.uaaClient))
.filter(client -> clientId.equals(client.getClientId()))
.as(StepVerifier::create)
.consumeNextWith(response -> {
assertThat(response.getAuthorizedGrantTypes()).containsExactly(CLIENT_CREDENTIALS);
assertThat(response.getClientId()).isEqualTo(clientId);
assertThat(response.getName()).isEqualTo("test-name");
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
@Test
public void updateMetadata() throws TimeoutException, InterruptedException {
String appIcon = Base64.getEncoder().encodeToString(new AsciiString("test-image").toByteArray());
this.uaaClient.clients()
.updateMetadata(UpdateMetadataRequest.builder()
.appIcon(appIcon)
.appLaunchUrl("http://test.app.launch.url")
.clientId(this.clientId)
.showOnHomePage(true)
.clientName("test-name")
.build())
.then(requestGetMetadata(this.uaaClient, this.clientId))
.as(StepVerifier::create)
.consumeNextWith(metadata -> {
assertThat(metadata.getAppIcon()).isEqualTo(appIcon);
assertThat(metadata.getAppLaunchUrl()).isEqualTo("http://test.app.launch.url");
assertThat(metadata.getClientId()).isEqualTo(this.clientId);
assertThat(metadata.getClientName()).isEqualTo("test-name");
assertThat(metadata.getShowOnHomePage()).isTrue();
})
.expectComplete()
.verify(Duration.ofMinutes(5));
}
private static Mono<BatchCreateClientsResponse> batchCreateClients(UaaClient uaaClient, String clientId1, String clientId2, String clientSecret) {
return uaaClient.clients()
.batchCreate(BatchCreateClientsRequest.builder()
.client(CreateClient.builder()
.approvalsDeleted(true)
.authorizedGrantType(PASSWORD)
.clientId(clientId1)
.clientSecret(clientSecret)
.scope("client.read", "client.write")
.tokenSalt("test-token-salt")
.build())
.client(CreateClient.builder()
.approvalsDeleted(true)
.authorizedGrantType(PASSWORD, REFRESH_TOKEN)
.clientId(clientId2)
.clientSecret(clientSecret)
.scope("client.write")
.tokenSalt("alternate-test-token-salt")
.build())
.build());
}
private static Mono<CreateClientResponse> requestCreateClient(UaaClient uaaClient, String clientId, String clientSecret) {
return uaaClient.clients()
.create(CreateClientRequest.builder()
.authorizedGrantType(PASSWORD)
.clientId(clientId)
.clientSecret(clientSecret)
.build());
}
private static Mono<GetMetadataResponse> requestGetMetadata(UaaClient uaaClient, String clientId) {
return uaaClient.clients()
.getMetadata(GetMetadataRequest.builder()
.clientId(clientId)
.build());
}
private static Flux<Client> requestListClients(UaaClient uaaClient) {
return PaginationUtils
.requestUaaResources(startIndex -> uaaClient.clients()
.list(ListClientsRequest.builder()
.startIndex(startIndex)
.build()));
}
private static Mono<UpdateMetadataResponse> requestUpdateMetadata(UaaClient uaaClient, String clientId, String appLaunchUrl) {
return uaaClient.clients()
.updateMetadata(UpdateMetadataRequest.builder()
.appLaunchUrl(appLaunchUrl)
.clientId(clientId)
.build());
}
}