/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.testsuite.client;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import java.util.*;
import javax.ws.rs.core.Response;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
private static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
super.addTestRealms(testRealms);
testRealms.get(0).setPrivateKey(PRIVATE_KEY);
testRealms.get(0).setPublicKey(PUBLIC_KEY);
}
@Before
public void before() throws Exception {
super.before();
ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
reg.auth(Auth.token(token));
}
private OIDCClientRepresentation createRep() {
OIDCClientRepresentation client = new OIDCClientRepresentation();
client.setClientName("RegistrationAccessTokenTest");
client.setClientUri("http://root");
client.setRedirectUris(Collections.singletonList("http://redirect"));
return client;
}
public OIDCClientRepresentation create() throws ClientRegistrationException {
OIDCClientRepresentation client = createRep();
OIDCClientRepresentation response = reg.oidc().create(client);
return response;
}
private void assertCreateFail(OIDCClientRepresentation client, int expectedStatusCode) {
assertCreateFail(client, expectedStatusCode, null);
}
private void assertCreateFail(OIDCClientRepresentation client, int expectedStatusCode, String expectedErrorContains) {
try {
reg.oidc().create(client);
Assert.fail("Not expected to successfuly register client");
} catch (ClientRegistrationException expected) {
HttpErrorException httpEx = (HttpErrorException) expected.getCause();
Assert.assertEquals(expectedStatusCode, httpEx.getStatusLine().getStatusCode());
if (expectedErrorContains != null) {
assertTrue("Error response doesn't contain expected text", httpEx.getErrorResponse().contains(expectedErrorContains));
}
}
}
// KEYCLOAK-3421
@Test
public void createClientWithUriFragment() {
OIDCClientRepresentation client = createRep();
client.setRedirectUris(Arrays.asList("http://localhost/auth", "http://localhost/auth#fragment", "http://localhost/auth*"));
assertCreateFail(client, 400, "URI fragment");
}
@Test
public void createClient() throws ClientRegistrationException {
OIDCClientRepresentation response = create();
assertNotNull(response.getRegistrationAccessToken());
assertNotNull(response.getClientIdIssuedAt());
assertNotNull(response.getClientId());
assertNotNull(response.getClientSecret());
assertEquals(0, response.getClientSecretExpiresAt().intValue());
assertNotNull(response.getRegistrationClientUri());
assertEquals("RegistrationAccessTokenTest", response.getClientName());
assertEquals("http://root", response.getClientUri());
assertEquals(1, response.getRedirectUris().size());
assertEquals("http://redirect", response.getRedirectUris().get(0));
assertEquals(Arrays.asList("code", "none"), response.getResponseTypes());
assertEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes());
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod());
Assert.assertNull(response.getUserinfoSignedResponseAlg());
}
@Test
public void getClient() throws ClientRegistrationException {
OIDCClientRepresentation response = create();
reg.auth(Auth.token(response));
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
assertNotNull(rep);
assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
assertTrue(CollectionUtil.collectionEquals(Arrays.asList("code", "none"), response.getResponseTypes()));
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes()));
assertNotNull(response.getClientSecret());
assertEquals(0, response.getClientSecretExpiresAt().intValue());
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod());
}
@Test
public void updateClient() throws ClientRegistrationException {
OIDCClientRepresentation response = create();
reg.auth(Auth.token(response));
response.setRedirectUris(Collections.singletonList("http://newredirect"));
response.setResponseTypes(Arrays.asList("code", "id_token token", "code id_token token"));
response.setGrantTypes(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD));
OIDCClientRepresentation updated = reg.oidc().update(response);
assertTrue(CollectionUtil.collectionEquals(Collections.singletonList("http://newredirect"), updated.getRedirectUris()));
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD), updated.getGrantTypes()));
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token"), updated.getResponseTypes()));
}
@Test
public void updateClientError() throws ClientRegistrationException {
try {
OIDCClientRepresentation response = create();
reg.auth(Auth.token(response));
response.setResponseTypes(Arrays.asList("code", "tokenn"));
reg.oidc().update(response);
fail("Not expected to end with success");
} catch (ClientRegistrationException cre) {
}
}
@Test
public void deleteClient() throws ClientRegistrationException {
OIDCClientRepresentation response = create();
reg.auth(Auth.token(response));
reg.oidc().delete(response);
}
@Test
public void testSignaturesRequired() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString());
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg());
Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg());
Assert.assertNotNull(response.getClientSecret());
// Test Keycloak representation
ClientRepresentation kcClient = getClient(response.getClientId());
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256);
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256);
}
@Test
public void createClientImplicitFlow() throws ClientRegistrationException {
OIDCClientRepresentation clientRep = createRep();
// create implicitFlow client and assert it's public client
clientRep.setResponseTypes(Arrays.asList("id_token token"));
OIDCClientRepresentation response = reg.oidc().create(clientRep);
String clientId = response.getClientId();
ClientRepresentation kcClientRep = getKeycloakClient(clientId);
Assert.assertTrue(kcClientRep.isPublicClient());
// Update client to hybrid and check it's not public client anymore
reg.auth(Auth.token(response));
response.setResponseTypes(Arrays.asList("id_token token", "code id_token", "code"));
reg.oidc().update(response);
kcClientRep = getKeycloakClient(clientId);
Assert.assertFalse(kcClientRep.isPublicClient());
}
private ClientRepresentation getKeycloakClient(String clientId) {
return ApiUtil.findClientByClientId(adminClient.realms().realm(REALM_NAME), clientId).toRepresentation();
}
}