package org.keycloak.testsuite.hok;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.hamcrest.Matchers;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HashProvider;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.drone.Different;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.HoKTokenUtils;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.UserInfoClientUtil;
import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.WebDriver;
public class HoKTest extends AbstractTestRealmKeycloakTest {
// KEYCLOAK-6771 Certificate Bound Token
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
@Drone
@Different
protected WebDriver driver2;
private static final List<String> CLIENT_LIST = Arrays.asList("test-app", "named-test-app");
public static class HoKAssertEvents extends AssertEvents {
public HoKAssertEvents(AbstractKeycloakTest ctx) {
super(ctx);
}
private final String defaultRedirectUri = "https://localhost:8543/auth/realms/master/app/auth";
@Override
public ExpectedEvent expectLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
//.detail(Details.USERNAME, DEFAULT_USERNAME)
//.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL)
//.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE)
.detail(Details.REDIRECT_URI, defaultRedirectUri)
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
.session(isUUID());
}
}
@Rule
public HoKAssertEvents events = new HoKAssertEvents(this);
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
// override due to effects caused by enabling TLS
for (String clientId : CLIENT_LIST) addRedirectUrlForTls(testRealm, clientId);
// for token introspection
configTestRealmForTokenIntrospection(testRealm);
}
private void addRedirectUrlForTls(RealmRepresentation testRealm, String clientId) {
for (ClientRepresentation client : testRealm.getClients()) {
if (client.getClientId().equals(clientId)) {
URI baseUri = URI.create(client.getRedirectUris().get(0));
URI redir = URI.create("https://localhost:" + System.getProperty("auth.server.https.port", "8543") + baseUri.getRawPath());
client.getRedirectUris().add(redir.toString());
break;
}
}
}
private void configTestRealmForTokenIntrospection(RealmRepresentation testRealm) {
ClientRepresentation confApp = KeycloakModelUtils.createClient(testRealm, "confidential-cli");
confApp.setSecret("secret1");
confApp.setServiceAccountsEnabled(Boolean.TRUE);
ClientRepresentation pubApp = KeycloakModelUtils.createClient(testRealm, "public-cli");
pubApp.setPublicClient(Boolean.TRUE);
UserRepresentation user = new UserRepresentation();
user.setUsername("no-permissions");
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType("password");
credential.setValue("password");
List<CredentialRepresentation> creds = new ArrayList<>();
creds.add(credential);
user.setCredentials(creds);
user.setEnabled(Boolean.TRUE);
List<String> realmRoles = new ArrayList<>();
realmRoles.add("user");
user.setRealmRoles(realmRoles);
testRealm.getUsers().add(user);
}
// enable HoK Token as default
@Before
public void enableHoKToken() {
// Enable MTLS HoK Token
for (String clientId : CLIENT_LIST) enableHoKToken(clientId);
}
private void enableHoKToken(String clientId) {
// Enable MTLS HoK Token
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), clientId);
ClientRepresentation clientRep = clientResource.toRepresentation();
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true);
clientResource.update(clientRep);
}
// Authorization Code Flow
// Bind HoK Token
@Test
public void accessTokenRequestWithClientCertificate() throws Exception {
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Success Pattern
expectSuccessfulResponseFromTokenEndpoint(sessionId, codeId, response);
verifyHoKTokenDefaultCertThumbPrint(response);
}
@Test
public void accessTokenRequestWithoutClientCertificate() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Error Pattern
assertEquals(400, response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError());
assertEquals("Client Certification missing for MTLS HoK Token Binding", response.getErrorDescription());
}
private void expectSuccessfulResponseFromTokenEndpoint(String sessionId, String codeId, AccessTokenResponse response) throws Exception {
assertEquals(200, response.getStatusCode());
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
Assert.assertThat(response.getRefreshExpiresIn(), allOf(greaterThanOrEqualTo(1750), lessThanOrEqualTo(1800)));
assertEquals("bearer", response.getTokenType());
String expectedKid = oauth.doCertsRequest("test").getKeys()[0].getKeyId();
JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
assertEquals("RS256", header.getAlgorithm().name());
assertEquals("JWT", header.getType());
assertEquals(expectedKid, header.getKeyId());
assertNull(header.getContentType());
header = new JWSInput(response.getIdToken()).getHeader();
assertEquals("RS256", header.getAlgorithm().name());
assertEquals("JWT", header.getType());
assertEquals(expectedKid, header.getKeyId());
assertNull(header.getContentType());
header = new JWSInput(response.getRefreshToken()).getHeader();
assertEquals("RS256", header.getAlgorithm().name());
assertEquals("JWT", header.getType());
assertEquals(expectedKid, header.getKeyId());
assertNull(header.getContentType());
AccessToken token = oauth.verifyToken(response.getAccessToken());
assertEquals(findUserByUsername(adminClient.realm("test"), "test-user@localhost").getId(), token.getSubject());
Assert.assertNotEquals("test-user@localhost", token.getSubject());
assertEquals(sessionId, token.getSessionState());
//assertEquals(1, token.getRealmAccess().getRoles().size());
assertTrue(token.getRealmAccess().isUserInRole("user"));
assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
assertTrue(token.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
EventRepresentation event = events.expectCodeToToken(codeId, sessionId).assertEvent();
assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
assertEquals(sessionId, token.getSessionState());
}
// verify HoK Token - Token Refresh
@Test
public void refreshTokenRequestByHoKRefreshTokenByOtherClient() throws Exception {
// first client user login
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
String refreshTokenString = tokenResponse.getRefreshToken();
// second client user login
OAuthClient oauth2 = new OAuthClient();
oauth2.init(adminClient, driver2);
oauth2.doLogin("john-doh@localhost", "password");
String code2 = oauth2.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse2 = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
tokenResponse2 = oauth2.doAccessTokenRequest(code2, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
verifyHoKTokenOtherCertThumbPrint(tokenResponse2);
// token refresh by second client by first client's refresh token
AccessTokenResponse response = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
response = oauth2.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Error Pattern
assertEquals(401, response.getStatusCode());
assertEquals(OAuthErrorException.UNAUTHORIZED_CLIENT, response.getError());
assertEquals("Client certificate missing, or its thumbprint and one in the refresh token did NOT match", response.getErrorDescription());
}
@Test
public void refreshTokenRequestByHoKRefreshTokenWithClientCertificate() throws Exception {
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
String refreshTokenString = tokenResponse.getRefreshToken();
RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
EventRepresentation tokenEvent = events.expectCodeToToken(codeId, sessionId).assertEvent();
Assert.assertNotNull(refreshTokenString);
assertEquals("bearer", tokenResponse.getTokenType());
Assert.assertThat(token.getExpiration() - getCurrentTime(), allOf(greaterThanOrEqualTo(200), lessThanOrEqualTo(350)));
int actual = refreshToken.getExpiration() - getCurrentTime();
Assert.assertThat(actual, allOf(greaterThanOrEqualTo(1799), lessThanOrEqualTo(1800)));
assertEquals(sessionId, refreshToken.getSessionState());
setTimeOffset(2);
AccessTokenResponse response = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Success Pattern
expectSuccessfulResponseFromTokenEndpoint(response, sessionId, token, refreshToken, tokenEvent);
verifyHoKTokenDefaultCertThumbPrint(response);
}
@Test
public void refreshTokenRequestByRefreshTokenWithoutClientCertificate() throws Exception {
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
tokenResponse = oauth.doAccessTokenRequest(code, "password");
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
String refreshTokenString = tokenResponse.getRefreshToken();
RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
Assert.assertNotNull(refreshTokenString);
assertEquals("bearer", tokenResponse.getTokenType());
Assert.assertThat(token.getExpiration() - getCurrentTime(), allOf(greaterThanOrEqualTo(200), lessThanOrEqualTo(350)));
int actual = refreshToken.getExpiration() - getCurrentTime();
Assert.assertThat(actual, allOf(greaterThanOrEqualTo(1799), lessThanOrEqualTo(1800)));
assertEquals(sessionId, refreshToken.getSessionState());
setTimeOffset(2);
AccessTokenResponse response = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Error Pattern
assertEquals(401, response.getStatusCode());
assertEquals(OAuthErrorException.UNAUTHORIZED_CLIENT, response.getError());
assertEquals("Client certificate missing, or its thumbprint and one in the refresh token did NOT match", response.getErrorDescription());
}
private void expectSuccessfulResponseFromTokenEndpoint(AccessTokenResponse response, String sessionId, AccessToken token, RefreshToken refreshToken, EventRepresentation tokenEvent) {
expectSuccessfulResponseFromTokenEndpoint(oauth, "test-user@localhost", response, sessionId, token, refreshToken, tokenEvent);
}
private void expectSuccessfulResponseFromTokenEndpoint(OAuthClient oauth, String username, AccessTokenResponse response, String sessionId, AccessToken token, RefreshToken refreshToken, EventRepresentation tokenEvent) {
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
if (refreshedToken.getCertConf() != null) {
log.warnf("refreshed access token's cnf-x5t#256 = %s", refreshedToken.getCertConf().getCertThumbprint());
log.warnf("refreshed refresh token's cnf-x5t#256 = %s", refreshedRefreshToken.getCertConf().getCertThumbprint());
}
assertEquals(200, response.getStatusCode());
assertEquals(sessionId, refreshedToken.getSessionState());
assertEquals(sessionId, refreshedRefreshToken.getSessionState());
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
Assert.assertThat(refreshedToken.getExpiration() - getCurrentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
Assert.assertThat(refreshedToken.getExpiration() - token.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(10)));
Assert.assertThat(refreshedRefreshToken.getExpiration() - refreshToken.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(10)));
Assert.assertNotEquals(token.getId(), refreshedToken.getId());
Assert.assertNotEquals(refreshToken.getId(), refreshedRefreshToken.getId());
assertEquals("bearer", response.getTokenType());
assertEquals(findUserByUsername(adminClient.realm("test"), username).getId(), refreshedToken.getSubject());
Assert.assertNotEquals(username, refreshedToken.getSubject());
//assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
EventRepresentation refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId).user(AssertEvents.isUUID()).assertEvent();
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
setTimeOffset(0);
}
// verify HoK Token - Get UserInfo
@Test
public void getUserInfoByHoKAccessTokenWithClientCertificate() throws Exception {
// get an access token
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
events.expectCodeToToken(codeId, sessionId).assertEvent();
// execute the access token to get UserInfo with token binded client certificate in mutual authentication TLS
ClientBuilder clientBuilder = ClientBuilder.newBuilder();
KeyStore keystore = null;
keystore = KeystoreUtil.loadKeyStore(HoKTokenUtils.DEFAULT_KEYSTOREPATH, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
clientBuilder.keyStore(keystore, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
Client client = clientBuilder.build();
WebTarget userInfoTarget = null;
Response response = null;
try {
userInfoTarget = UserInfoClientUtil.getUserInfoWebTarget(client);
response = userInfoTarget.request().header(HttpHeaders.AUTHORIZATION, "bearer " + tokenResponse.getAccessToken()).get();
testSuccessfulUserInfoResponse(response);
} finally {
response.close();
client.close();
}
}
@Test
public void getUserInfoByHoKAccessTokenWithoutClientCertificate() throws Exception {
// get an access token
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
events.expectCodeToToken(codeId, sessionId).assertEvent();
// execute the access token to get UserInfo without token binded client certificate in mutual authentication TLS
ClientBuilder clientBuilder = ClientBuilder.newBuilder();
Client client = clientBuilder.build();
WebTarget userInfoTarget = null;
Response response = null;
try {
userInfoTarget = UserInfoClientUtil.getUserInfoWebTarget(client);
response = userInfoTarget.request().header(HttpHeaders.AUTHORIZATION, "bearer " + tokenResponse.getAccessToken()).get();
assertEquals(401, response.getStatus());
} finally {
response.close();
client.close();
}
}
private void testSuccessfulUserInfoResponse(Response response) {
events.expect(EventType.USER_INFO_REQUEST)
.session(Matchers.notNullValue(String.class))
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)
.detail(Details.USERNAME, "test-user@localhost")
.detail(Details.SIGNATURE_REQUIRED, "false")
.assertEvent();
UserInfoClientUtil.testSuccessfulUserInfoResponse(response, "test-user@localhost", "test-user@localhost");
}
// verify HoK Token - Back Channel Logout
@Test
public void postLogoutByHoKRefreshTokenWithClientCertificate() throws Exception {
String refreshTokenString = execPreProcessPostLogout();
CloseableHttpResponse response = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doLogout(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Success Pattern
assertThat(response, org.keycloak.testsuite.util.Matchers.statusCodeIsHC(Status.NO_CONTENT));
assertNotNull(testingClient.testApp().getAdminLogoutAction());
}
@Test
public void postLogoutByHoKRefreshTokenWithoutClientCertificate() throws Exception {
String refreshTokenString = execPreProcessPostLogout();
CloseableHttpResponse response = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doLogout(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Error Pattern
assertEquals(401, response.getStatusLine().getStatusCode());
}
private String execPreProcessPostLogout() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
oauth.clientSessionState("client-session");
AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
verifyHoKTokenDefaultCertThumbPrint(tokenResponse);
return tokenResponse.getRefreshToken();
}
// Hybrid Code Flow : response_type = code id_token
// Bind HoK Token
@Test
public void accessTokenRequestWithClientCertificateInHybridFlowWithCodeIDToken() throws Exception {
String nonce = "ckw938gnspa93dj";
ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(true).implicitFlow(true);
oauth.clientId("test-app");
oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN);
oauth.nonce(nonce);
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
Assert.assertNotNull(authzResponse.getSessionState());
List<IDToken> idTokens = testAuthzResponseAndRetrieveIDTokens(authzResponse, loginEvent);
for (IDToken idToken : idTokens) {
Assert.assertEquals(nonce, idToken.getNonce());
Assert.assertEquals(authzResponse.getSessionState(), idToken.getSessionState());
}
}
protected List<IDToken> testAuthzResponseAndRetrieveIDTokens(OAuthClient.AuthorizationEndpointResponse authzResponse, EventRepresentation loginEvent) {
Assert.assertEquals(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE));
// IDToken from the authorization response
Assert.assertNull(authzResponse.getAccessToken());
String idTokenStr = authzResponse.getIdToken();
IDToken idToken = oauth.verifyIDToken(idTokenStr);
// Validate "c_hash"
Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNotNull(idToken.getCodeHash());
Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(Algorithm.RS256, authzResponse.getCode()));
// IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
return Arrays.asList(idToken, idToken2);
}
@Test
public void testIntrospectHoKAccessToken() throws Exception {
// get an access token with client certificate in mutual authenticate TLS
// mimic Client
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
EventRepresentation loginEvent = events.expectLogin().assertEvent();
AccessTokenResponse accessTokenResponse = null;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
accessTokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Do token introspection
// mimic Resource Server
String tokenResponse;
try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
tokenResponse = oauth.introspectTokenWithClientCredential("confidential-cli", "secret1", "access_token", accessTokenResponse.getAccessToken(), client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
JWSInput jws = new JWSInput(accessTokenResponse.getAccessToken());
AccessToken at = jws.readJsonContent(AccessToken.class);
jws = new JWSInput(accessTokenResponse.getRefreshToken());
RefreshToken rt = jws.readJsonContent(RefreshToken.class);
String certThumprintFromAccessToken = at.getCertConf().getCertThumbprint();
String certThumprintFromRefreshToken = rt.getCertConf().getCertThumbprint();
String certThumprintFromTokenIntrospection = rep.getCertConf().getCertThumbprint();
String certThumprintFromBoundClientCertificate = HoKTokenUtils.getThumbprintFromDefaultClientCert();
assertTrue(rep.isActive());
assertEquals("test-user@localhost", rep.getUserName());
assertEquals("test-app", rep.getClientId());
assertEquals(loginEvent.getUserId(), rep.getSubject());
assertEquals(certThumprintFromTokenIntrospection, certThumprintFromBoundClientCertificate);
assertEquals(certThumprintFromBoundClientCertificate, certThumprintFromAccessToken);
assertEquals(certThumprintFromAccessToken, certThumprintFromRefreshToken);
}
private void verifyHoKTokenDefaultCertThumbPrint(AccessTokenResponse response) throws Exception {
verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromDefaultClientCert());
}
private void verifyHoKTokenOtherCertThumbPrint(AccessTokenResponse response) throws Exception {
verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromOtherClientCert());
}
private void verifyHoKTokenCertThumbPrint(AccessTokenResponse response, String certThumbPrint) {
JWSInput jws = null;
AccessToken at = null;
try {
jws = new JWSInput(response.getAccessToken());
at = jws.readJsonContent(AccessToken.class);
} catch (JWSInputException e) {
Assert.fail(e.toString());
}
assertTrue(MessageDigest.isEqual(certThumbPrint.getBytes(), at.getCertConf().getCertThumbprint().getBytes()));
RefreshToken rt = null;
try {
jws = new JWSInput(response.getRefreshToken());
rt = jws.readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
Assert.fail(e.toString());
}
assertTrue(MessageDigest.isEqual(certThumbPrint.getBytes(), rt.getCertConf().getCertThumbprint().getBytes()));
}
}