KEYCLOAK-14200 Client Policy - Executor : Enforce Holder-of-Key Token
Co-authored-by: Hryhorii Hevorkian <hhe@adorsys.com.ua>
This commit is contained in:
parent
ab1dba5fa6
commit
5f445ec18e
9 changed files with 423 additions and 7 deletions
|
@ -17,10 +17,16 @@
|
|||
|
||||
package org.keycloak.services.clientpolicy;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.keycloak.OAuthErrorException;
|
||||
|
||||
public class ClientPolicyException extends Exception {
|
||||
|
||||
private String error;
|
||||
private String error = OAuthErrorException.INVALID_REQUEST;
|
||||
private String errorDetail;
|
||||
private Status errorStatus = Response.Status.BAD_REQUEST;
|
||||
|
||||
public ClientPolicyException(String error, String errorDetail) {
|
||||
super(error);
|
||||
|
@ -28,12 +34,26 @@ public class ClientPolicyException extends Exception {
|
|||
setErrorDetail(errorDetail);
|
||||
}
|
||||
|
||||
public ClientPolicyException(String error, String errorDetail, Status errorStatus) {
|
||||
super(error);
|
||||
setError(error);
|
||||
setErrorDetail(errorDetail);
|
||||
setErrorStatus(errorStatus);
|
||||
}
|
||||
|
||||
public ClientPolicyException(String error, String errorDetail, Throwable throwable) {
|
||||
super(throwable);
|
||||
setError(error);
|
||||
setErrorDetail(errorDetail);
|
||||
}
|
||||
|
||||
public ClientPolicyException(String error, String errorDetail, Status errorStatus, Throwable throwable) {
|
||||
super(throwable);
|
||||
setError(error);
|
||||
setErrorDetail(errorDetail);
|
||||
setErrorStatus(errorStatus);
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
@ -50,6 +70,14 @@ public class ClientPolicyException extends Exception {
|
|||
this.errorDetail = errorDetail;
|
||||
}
|
||||
|
||||
public Status getErrorStatus() {
|
||||
return errorStatus;
|
||||
}
|
||||
|
||||
public void setErrorStatus(Status errorStatus) {
|
||||
this.errorStatus = errorStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link ClientPolicyException} is used to notify the event so that it needs not to have stack trace.
|
||||
* @return always null
|
||||
|
|
|
@ -212,7 +212,7 @@ public class LogoutEndpoint {
|
|||
try {
|
||||
session.clientPolicy().triggerOnEvent(new LogoutRequestContext(form));
|
||||
} catch (ClientPolicyException cpe) {
|
||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||
}
|
||||
|
||||
RefreshToken token = null;
|
||||
|
|
|
@ -539,7 +539,7 @@ public class TokenEndpoint {
|
|||
session.clientPolicy().triggerOnEvent(new TokenRefreshContext(formParams));
|
||||
} catch (ClientPolicyException cpe) {
|
||||
event.error(cpe.getError());
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||
}
|
||||
|
||||
AccessTokenResponse res;
|
||||
|
|
|
@ -52,6 +52,7 @@ import org.keycloak.services.clientpolicy.TokenRevokeContext;
|
|||
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.util.MtlsHoKTokenUtil;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
/**
|
||||
|
@ -102,7 +103,7 @@ public class TokenRevocationEndpoint {
|
|||
session.clientPolicy().triggerOnEvent(new TokenRevokeContext(formParams));
|
||||
} catch (ClientPolicyException cpe) {
|
||||
event.error(cpe.getError());
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(cors, cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||
}
|
||||
|
||||
checkToken();
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
|
@ -136,7 +137,7 @@ public class UserInfoEndpoint {
|
|||
try {
|
||||
session.clientPolicy().triggerOnEvent(new UserInfoRequestContext(tokenString));
|
||||
} catch (ClientPolicyException cpe) {
|
||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), cpe.getErrorStatus());
|
||||
}
|
||||
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection)
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 2020 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.services.clientpolicy.executor;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.clientpolicy.*;
|
||||
import org.keycloak.services.util.MtlsHoKTokenUtil;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class HolderOfKeyEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ComponentModel componentModel;
|
||||
|
||||
public HolderOfKeyEnforceExecutor(KeycloakSession session, ComponentModel componentModel) {
|
||||
super(session, componentModel);
|
||||
this.session = session;
|
||||
this.componentModel = componentModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return componentModel.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderId() {
|
||||
return componentModel.getProviderId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void augment(ClientRepresentation rep) {
|
||||
if (Boolean.parseBoolean(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT))) {
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setUseMtlsHoKToken(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate(ClientRepresentation rep) throws ClientPolicyException {
|
||||
boolean useMtlsHokToken = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).isUseMtlsHokToken();
|
||||
if (!useMtlsHokToken) {
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: MTLS token in disabled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||
super.executeOnEvent(context);
|
||||
HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
|
||||
|
||||
switch (context.getEvent()) {
|
||||
|
||||
case TOKEN_REQUEST:
|
||||
AccessToken.CertConf certConf = MtlsHoKTokenUtil.bindTokenWithClientCertificate(request, session);
|
||||
if (certConf == null) {
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Client Certification missing for MTLS HoK Token Binding");
|
||||
}
|
||||
break;
|
||||
|
||||
case TOKEN_REFRESH:
|
||||
checkTokenRefresh((TokenRefreshContext) context, request);
|
||||
break;
|
||||
|
||||
case TOKEN_REVOKE:
|
||||
checkTokenRevoke((TokenRevokeContext) context, request);
|
||||
break;
|
||||
|
||||
case USERINFO_REQUEST:
|
||||
checkUserInfo((UserInfoRequestContext) context, request);
|
||||
break;
|
||||
|
||||
case LOGOUT_REQUEST:
|
||||
checkLogout((LogoutRequestContext) context, request);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLogout(LogoutRequestContext context, HttpRequest request) throws ClientPolicyException {
|
||||
MultivaluedMap<String, String> formParameters = context.getParams();
|
||||
String encodedRefreshToken = formParameters.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
||||
|
||||
RefreshToken refreshToken = session.tokens().decode(encodedRefreshToken, RefreshToken.class);
|
||||
if (refreshToken == null) {
|
||||
// this executor does not treat this error case.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(refreshToken, request, session)) {
|
||||
throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUserInfo(UserInfoRequestContext context, HttpRequest request) throws ClientPolicyException {
|
||||
String encodedAccessToken = context.getTokenString();
|
||||
|
||||
AccessToken accessToken = session.tokens().decode(encodedAccessToken, AccessToken.class);
|
||||
if (accessToken == null) {
|
||||
// this executor does not treat this error case.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(accessToken, request, session)) {
|
||||
throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkTokenRevoke(TokenRevokeContext context, HttpRequest request) throws ClientPolicyException {
|
||||
MultivaluedMap<String, String> revokeParameters = context.getParams();
|
||||
String encodedRevokeToken = revokeParameters.getFirst("token");
|
||||
|
||||
RefreshToken refreshToken = session.tokens().decode(encodedRevokeToken, RefreshToken.class);
|
||||
if (refreshToken == null) {
|
||||
// this executor does not treat this error case.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(refreshToken, request, session)) {
|
||||
throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkTokenRefresh(TokenRefreshContext context, HttpRequest request) throws ClientPolicyException {
|
||||
MultivaluedMap<String, String> formParameters = context.getParams();
|
||||
String encodedRefreshToken = formParameters.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
||||
|
||||
RefreshToken refreshToken = session.tokens().decode(encodedRefreshToken, RefreshToken.class);
|
||||
if (refreshToken == null) {
|
||||
// this executor does not treat this error case.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(refreshToken, request, session)) {
|
||||
throw new ClientPolicyException(Errors.NOT_ALLOWED, MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2020 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.services.clientpolicy.executor;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class HolderOfKeyEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "holder-of-key-enforce-executor";
|
||||
|
||||
@Override
|
||||
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new HolderOfKeyEnforceExecutor(session, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "It prohibits the client whose MTLS certificate does not match with the certificate thumbprint from the tokens.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
|
@ -5,4 +5,5 @@ org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory
|
|||
org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory
|
|
@ -124,6 +124,7 @@ import org.keycloak.services.clientpolicy.condition.ClientUpdateSourceRolesCondi
|
|||
import org.keycloak.services.clientpolicy.condition.ClientRolesConditionFactory;
|
||||
import org.keycloak.services.clientpolicy.condition.ClientScopesConditionFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
|
||||
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforceExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory;
|
||||
|
@ -144,6 +145,7 @@ import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResou
|
|||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject;
|
||||
import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory;
|
||||
import org.keycloak.testsuite.util.MutualTLSUtils;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
@ -1351,6 +1353,162 @@ public class ClientPolicyBasicsTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHolderOfKeyEnforceExecutor() throws Exception {
|
||||
String policyName = "MyPolicy";
|
||||
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
|
||||
logger.info("... Created Policy : " + policyName);
|
||||
|
||||
createCondition("ClientRolesCondition", ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
|
||||
setConditionClientRoles(provider, Collections.singletonList("sample-client-role"));
|
||||
});
|
||||
registerCondition("ClientRolesCondition", policyName);
|
||||
logger.info("... Registered Condition : ClientRolesCondition");
|
||||
|
||||
createExecutor("HolderOfKeyEnforceExecutor", HolderOfKeyEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
|
||||
setExecutorAugmentActivate(provider);
|
||||
});
|
||||
registerExecutor("HolderOfKeyEnforceExecutor", policyName);
|
||||
logger.info("... Registered Executor : HolderOfKeyEnforceExecutor");
|
||||
|
||||
String clientName = "Zahlungs-App";
|
||||
String userPassword = "password";
|
||||
String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> {
|
||||
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.TLS_CLIENT_AUTH);
|
||||
});
|
||||
|
||||
try {
|
||||
checkMtlsFlow(userPassword);
|
||||
} finally {
|
||||
deleteClientByAdmin(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMtlsFlow(String password) throws IOException {
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), "test-app");
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
clientRep.setDefaultRoles(new String[]{"sample-client-role"});
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseMtlsHoKToken(true);
|
||||
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// Check login.
|
||||
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin("test-user@localhost", password);
|
||||
Assert.assertNull(loginResponse.getError());
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
// Check token obtaining.
|
||||
OAuthClient.AccessTokenResponse accessTokenResponse;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
accessTokenResponse = oauth.doAccessTokenRequest(code, password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(200, accessTokenResponse.getStatusCode());
|
||||
|
||||
// Check token refresh.
|
||||
OAuthClient.AccessTokenResponse accessTokenResponseRefreshed;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(200, accessTokenResponseRefreshed.getStatusCode());
|
||||
|
||||
// Check token introspection.
|
||||
String tokenResponse;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenResponse = oauth.introspectTokenWithClientCredential(TEST_CLIENT, password, "access_token", accessTokenResponse.getAccessToken(), client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
Assert.assertNotNull(tokenResponse);
|
||||
TokenMetadataRepresentation tokenMetadataRepresentation = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||
Assert.assertTrue(tokenMetadataRepresentation.isActive());
|
||||
|
||||
// Check token revoke.
|
||||
CloseableHttpResponse tokenRevokeResponse;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(200, tokenRevokeResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// Check logout.
|
||||
CloseableHttpResponse logoutResponse;
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
|
||||
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// Check login.
|
||||
loginResponse = oauth.doLogin("test-user@localhost", password);
|
||||
Assert.assertNull(loginResponse.getError());
|
||||
|
||||
code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
// Check token obtaining without certificate
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
accessTokenResponse = oauth.doAccessTokenRequest(code, password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(400, accessTokenResponse.getStatusCode());
|
||||
assertEquals(OAuthErrorException.INVALID_GRANT, accessTokenResponse.getError());
|
||||
|
||||
// Check frontchannel logout and login.
|
||||
oauth.openLogout();
|
||||
loginResponse = oauth.doLogin("test-user@localhost", password);
|
||||
Assert.assertNull(loginResponse.getError());
|
||||
|
||||
code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
// Check token obtaining.
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
accessTokenResponse = oauth.doAccessTokenRequest(code, password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(200, accessTokenResponse.getStatusCode());
|
||||
|
||||
// Check token refresh with other certificate
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
accessTokenResponseRefreshed = oauth.doRefreshTokenRequest(accessTokenResponse.getRefreshToken(), password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(401, accessTokenResponseRefreshed.getStatusCode());
|
||||
assertEquals(Errors.NOT_ALLOWED, accessTokenResponseRefreshed.getError());
|
||||
|
||||
// Check token revoke with other certificate
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
|
||||
tokenRevokeResponse = oauth.doTokenRevoke(accessTokenResponse.getRefreshToken(), "refresh_token", password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(401, tokenRevokeResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// Check logout without certificate
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
|
||||
logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
assertEquals(401, logoutResponse.getStatusLine().getStatusCode());
|
||||
|
||||
// Check logout.
|
||||
try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
|
||||
logoutResponse = oauth.doLogout(accessTokenResponse.getRefreshToken(), password, client);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse sendRequest(String requestUrl, List<NameValuePair> parameters) throws Exception {
|
||||
CloseableHttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
|
@ -2065,4 +2223,4 @@ public class ClientPolicyBasicsTest extends AbstractKeycloakTest {
|
|||
private void setExecutorAugmentedClientAuthMethod(ComponentRepresentation provider, String augmentedClientAuthMethod) {
|
||||
provider.getConfig().putSingle(SecureClientAuthEnforceExecutorFactory.CLIENT_AUTHNS_AUGMENT, augmentedClientAuthMethod);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue