/*
* 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.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.UserInfo;
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.UserInfoClientUtil;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertTrue;
public class OIDCPairwiseClientRegistrationTest extends AbstractClientRegistrationTest {
@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(OAuthClient.APP_ROOT);
client.setRedirectUris(Collections.singletonList(oauth.getRedirectUri()));
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, 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));
}
}
}
@Test
public void createPairwiseClient() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals("pairwise", response.getSubjectType());
}
@Test
public void updateClientToPairwise() throws Exception {
OIDCClientRepresentation response = create();
Assert.assertEquals("public", response.getSubjectType());
reg.auth(Auth.token(response));
response.setSubjectType("pairwise");
OIDCClientRepresentation updated = reg.oidc().update(response);
Assert.assertEquals("pairwise", updated.getSubjectType());
}
@Test
public void updateSectorIdentifierUri() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals("pairwise", response.getSubjectType());
Assert.assertNull(response.getSectorIdentifierUri());
reg.auth(Auth.token(response));
// Push redirect uris to the sector identifier URI
List<String> sectorRedirects = new ArrayList<>();
sectorRedirects.addAll(response.getRedirectUris());
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
response.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
OIDCClientRepresentation updated = reg.oidc().update(response);
Assert.assertEquals("pairwise", updated.getSubjectType());
Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), updated.getSectorIdentifierUri());
}
@Test
public void updateToPairwiseThroughAdminRESTSuccess() throws Exception {
OIDCClientRepresentation response = create();
Assert.assertEquals("public", response.getSubjectType());
Assert.assertNull(response.getSectorIdentifierUri());
// Push redirect uris to the sector identifier URI
List<String> sectorRedirects = new ArrayList<>();
sectorRedirects.addAll(response.getRedirectUris());
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
String sectorIdentifierUri = TestApplicationResourceUrls.pairwiseSectorIdentifierUri();
// Add protocolMapper through admin REST endpoint
String clientId = response.getClientId();
ProtocolMapperRepresentation pairwiseProtMapper = SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, null);
RealmResource realmResource = realmsResouce().realm("test");
ClientManager.realm(realmResource).clientId(clientId).addProtocolMapper(pairwiseProtMapper);
reg.auth(Auth.token(response));
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
Assert.assertEquals("pairwise", rep.getSubjectType());
Assert.assertEquals(sectorIdentifierUri, rep.getSectorIdentifierUri());
}
@Test
public void updateToPairwiseThroughAdminRESTFailure() throws Exception {
OIDCClientRepresentation response = create();
Assert.assertEquals("public", response.getSubjectType());
Assert.assertNull(response.getSectorIdentifierUri());
// Push empty list to the sector identifier URI
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(new ArrayList<>());
String sectorIdentifierUri = TestApplicationResourceUrls.pairwiseSectorIdentifierUri();
// Add protocolMapper through admin REST endpoint
String clientId = response.getClientId();
ProtocolMapperRepresentation pairwiseProtMapper = SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, null);
RealmResource realmResource = realmsResouce().realm("test");
ClientResource clientResource = ApiUtil.findClientByClientId(realmsResouce().realm("test"), clientId);
Response resp = clientResource.getProtocolMappers().createMapper(pairwiseProtMapper);
Assert.assertEquals(400, resp.getStatus());
// Assert still public
reg.auth(Auth.token(response));
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
Assert.assertEquals("public", rep.getSubjectType());
Assert.assertNull(rep.getSectorIdentifierUri());
}
@Test
public void createPairwiseClientWithSectorIdentifierURI() throws Exception {
OIDCClientRepresentation clientRep = createRep();
// Push redirect uris to the sector identifier URI
List<String> sectorRedirects = new ArrayList<>();
sectorRedirects.addAll(clientRep.getRedirectUris());
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
clientRep.setSubjectType("pairwise");
clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals("pairwise", response.getSubjectType());
Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
}
@Test
public void createPairwiseClientWithRedirectsToMultipleHostsWithoutSectorIdentifierURI() throws Exception {
OIDCClientRepresentation clientRep = createRep();
List<String> redirects = new ArrayList<>();
redirects.add("http://redirect1");
redirects.add("http://redirect2");
clientRep.setSubjectType("pairwise");
clientRep.setRedirectUris(redirects);
assertCreateFail(clientRep, 400, "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.");
}
@Test
public void createPairwiseClientWithRedirectsToMultipleHosts() throws Exception {
OIDCClientRepresentation clientRep = createRep();
// Push redirect URIs to the sector identifier URI
List<String> redirects = new ArrayList<>();
redirects.add("http://redirect1");
redirects.add("http://redirect2");
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(redirects);
clientRep.setSubjectType("pairwise");
clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
clientRep.setRedirectUris(redirects);
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals("pairwise", response.getSubjectType());
Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
Assert.assertNames(response.getRedirectUris(), "http://redirect1", "http://redirect2");
}
@Test
public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirects() throws Exception {
OIDCClientRepresentation clientRep = createRep();
// Push redirect uris to the sector identifier URI
List<String> sectorRedirects = new ArrayList<>();
sectorRedirects.add("http://someotherredirect");
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
clientRep.setSubjectType("pairwise");
clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
}
@Test
public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirectsPublicSubject() throws Exception {
OIDCClientRepresentation clientRep = createRep();
// Push redirect uris to the sector identifier URI
List<String> sectorRedirects = new ArrayList<>();
sectorRedirects.add("http://someotherredirect");
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
clientRep.setSubjectType("public");
clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
}
@Test
public void createPairwiseClientWithInvalidSectorIdentifierURI() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
clientRep.setSectorIdentifierUri("malformed");
assertCreateFail(clientRep, 400, "Invalid Sector Identifier URI.");
}
@Test
public void createPairwiseClientWithUnreachableSectorIdentifierURI() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
clientRep.setSectorIdentifierUri("http://localhost/dummy");
assertCreateFail(clientRep, 400, "Failed to get redirect URIs from the Sector Identifier URI.");
}
@Test
public void loginUserToPairwiseClient() throws Exception {
// Create public client
OIDCClientRepresentation publicClient = create();
// Login to public client
oauth.clientId(publicClient.getClientId());
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin("test-user@localhost", "password");
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), publicClient.getClientSecret());
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
Assert.assertEquals("test-user", accessToken.getPreferredUsername());
Assert.assertEquals("test-user@localhost", accessToken.getEmail());
String tokenUserId = accessToken.getSubject();
// Assert public client has same subject like userId
UserRepresentation user = realmsResouce().realm("test").users().search("test-user", 0, 1).get(0);
Assert.assertEquals(user.getId(), tokenUserId);
// Create pairwise client
OIDCClientRepresentation clientRep = createRep();
clientRep.setSubjectType("pairwise");
OIDCClientRepresentation pairwiseClient = reg.oidc().create(clientRep);
Assert.assertEquals("pairwise", pairwiseClient.getSubjectType());
// Login to pairwise client
oauth.clientId(pairwiseClient.getClientId());
oauth.openLoginForm();
loginResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
accessTokenResponse = oauth.doAccessTokenRequest(loginResponse.getCode(), pairwiseClient.getClientSecret());
accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
Assert.assertEquals("test-user", accessToken.getPreferredUsername());
Assert.assertEquals("test-user@localhost", accessToken.getEmail());
// Assert pairwise client has different subject like userId
String pairwiseUserId = accessToken.getSubject();
Assert.assertNotEquals(pairwiseUserId, user.getId());
// Send request to userInfo endpoint
Client jaxrsClient = javax.ws.rs.client.ClientBuilder.newClient();
try {
// Check that userInfo contains pairwise subjectId as well
Response userInfoResponse = UserInfoClientUtil.executeUserInfoRequest_getMethod(jaxrsClient, accessTokenResponse.getAccessToken());
UserInfo userInfo = UserInfoClientUtil.testSuccessfulUserInfoResponse(userInfoResponse, "test-user", "test-user@localhost");
String userInfoSubId = userInfo.getSubject();
Assert.assertEquals(pairwiseUserId, userInfoSubId);
} finally {
jaxrsClient.close();
}
}
}