KEYCLOAK-18258 Not possible to login with public client, which was confidential with custom client authenticator set
This commit is contained in:
parent
afb8da7ff0
commit
d4374f37ae
3 changed files with 129 additions and 4 deletions
|
@ -75,8 +75,9 @@ public class ClientAuthenticationFlow implements AuthenticationFlow {
|
|||
if (client != null) {
|
||||
String expectedClientAuthType = client.getClientAuthenticatorType();
|
||||
|
||||
// Fallback to secret just in case (for backwards compatibility)
|
||||
if (expectedClientAuthType == null) {
|
||||
// Fallback to secret just in case (for backwards compatibility). Also for public clients, ignore the "clientAuthenticatorType", which is set to them and stick to the
|
||||
// default, which set the client just based on "client_id" parameter
|
||||
if (expectedClientAuthType == null || client.isPublicClient()) {
|
||||
expectedClientAuthType = KeycloakModelUtils.getDefaultClientAuthenticatorType();
|
||||
ServicesLogger.LOGGER.authMethodFallback(client.getClientId(), expectedClientAuthType);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2021 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.oidc;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OIDCPublicClientTest extends AbstractKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
||||
@Override
|
||||
public void beforeAbstractKeycloakTest() throws Exception {
|
||||
super.beforeAbstractKeycloakTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void addBouncyCastleProvider() {
|
||||
if (Security.getProvider("BC") == null) Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clientConfiguration() {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
|
||||
/*
|
||||
* Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
|
||||
* For example: If some test case configure oauth.clientId("sample-public-client"), other tests
|
||||
* will faile and the clientID will always be "sample-public-client
|
||||
* @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
|
||||
*/
|
||||
oauth.clientId("test-app");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
|
||||
// KEYCLOAK-18258
|
||||
@Test
|
||||
public void accessTokenRequest() throws Exception {
|
||||
// Update client to use custom client authenticator
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realms().realm("test"), "test-app");
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// Switch client to public client now
|
||||
clientRep = clientResource.toRepresentation();
|
||||
Assert.assertEquals(JWTClientAuthenticator.PROVIDER_ID, clientRep.getClientAuthenticatorType());
|
||||
clientRep.setPublicClient(true);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// It should be possible to authenticate
|
||||
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);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, null);
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
assertNotNull(response.getAccessToken());
|
||||
EventRepresentation event = events.expectCodeToToken(codeId, sessionId).assertEvent();
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -314,7 +315,9 @@ public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakT
|
|||
clientRep.setPublicClient(true);
|
||||
testRealm().clients().get(clientRep.getId()).update(clientRep);
|
||||
try {
|
||||
new Review().invoke().assertError(401, "Public client is not permitted to invoke token review endpoint");
|
||||
new Review()
|
||||
.clientAuthMethod(ClientIdAndSecretAuthenticator.PROVIDER_ID)
|
||||
.invoke().assertError(401, "Public client is not permitted to invoke token review endpoint");
|
||||
} finally {
|
||||
clientRep.setPublicClient(false);
|
||||
clientRep.setSecret("password");
|
||||
|
@ -332,6 +335,7 @@ public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakT
|
|||
private InvokeRunnable runAfterTokenRequest;
|
||||
|
||||
private String token;
|
||||
private String clientAuthMethod = "testsuite-client-dummy";
|
||||
private int responseStatus;
|
||||
private OpenShiftTokenReviewResponseRepresentation response;
|
||||
|
||||
|
@ -345,6 +349,11 @@ public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakT
|
|||
return this;
|
||||
}
|
||||
|
||||
public Review clientAuthMethod(String clientAuthMethod) {
|
||||
this.clientAuthMethod = clientAuthMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Review runAfterTokenRequest(InvokeRunnable runnable) {
|
||||
this.runAfterTokenRequest = runnable;
|
||||
return this;
|
||||
|
@ -360,7 +369,7 @@ public class OpenShiftTokenReviewEndpointTest extends AbstractTestRealmKeycloakT
|
|||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).detail("client_auth_method", "testsuite-client-dummy").user(userId).assertEvent();
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId()).detail("client_auth_method", this.clientAuthMethod).user(userId).assertEvent();
|
||||
|
||||
token = accessTokenResponse.getAccessToken();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue