KEYCLOAK-12829 Require PKCE for admin and account console
This commit is contained in:
parent
7969aed8e0
commit
dda829710e
7 changed files with 80 additions and 8 deletions
|
@ -57,6 +57,7 @@ public class MigrateTo9_0_0 implements Migration {
|
|||
protected void migrateRealmCommon(RealmModel realm) {
|
||||
addAccountConsoleClient(realm);
|
||||
addAccountApiRoles(realm);
|
||||
enablePkceAdminAccountClients(realm);
|
||||
}
|
||||
|
||||
private void addAccountApiRoles(RealmModel realm) {
|
||||
|
@ -100,4 +101,17 @@ public class MigrateTo9_0_0 implements Migration {
|
|||
client.addProtocolMapper(audienceMapper);
|
||||
}
|
||||
}
|
||||
|
||||
private void enablePkceAdminAccountClients(RealmModel realm) {
|
||||
ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||
if (adminConsole != null) {
|
||||
adminConsole.setAttribute("pkce.code.challenge.method", "S256");
|
||||
}
|
||||
|
||||
ClientModel accountConsole = realm.getClientByClientId(Constants.ACCOUNT_CONSOLE_CLIENT_ID);
|
||||
if (accountConsole != null) {
|
||||
accountConsole.setAttribute("pkce.code.challenge.method", "S256");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.models.utils.DefaultRequiredActions;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper;
|
||||
|
@ -170,6 +171,8 @@ public class RealmManager {
|
|||
adminConsole.setPublicClient(true);
|
||||
adminConsole.setFullScopeAllowed(false);
|
||||
adminConsole.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
|
||||
adminConsole.setAttribute(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD, "S256");
|
||||
}
|
||||
|
||||
protected void setupAdminConsoleLocaleMapper(RealmModel realm) {
|
||||
|
@ -461,6 +464,8 @@ public class RealmManager {
|
|||
audienceMapper.setProtocolMapper(AudienceResolveProtocolMapper.PROVIDER_ID);
|
||||
|
||||
accountConsoleClient.addProtocolMapper(audienceMapper);
|
||||
|
||||
accountConsoleClient.setAttribute(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD, "S256");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -801,9 +801,8 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void openLoginFormWithDifferentApplication() throws Exception {
|
||||
// Login form shown after redirect from admin console
|
||||
oauth.clientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console");
|
||||
oauth.clientId("root-url-client");
|
||||
oauth.redirectUri("http://localhost:8180/foo/bar/");
|
||||
oauth.openLoginForm();
|
||||
|
||||
// Login form shown after redirect from app
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.models.LDAPConstants;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
|
@ -281,6 +282,8 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||
testFirstBrokerLoginFlowMigrated(migrationRealm);
|
||||
testAccountClient(masterRealm);
|
||||
testAccountClient(migrationRealm);
|
||||
testAdminClientPkce(masterRealm);
|
||||
testAdminClientPkce(migrationRealm);
|
||||
}
|
||||
|
||||
private void testAccountClient(RealmResource realm) {
|
||||
|
@ -312,6 +315,11 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||
assertEquals(1, adminConsoleClient.getWebOrigins().size());
|
||||
}
|
||||
|
||||
private void testAdminClientPkce(RealmResource realm) {
|
||||
ClientRepresentation adminConsoleClient = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
|
||||
assertEquals("S256", adminConsoleClient.getAttributes().get(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD));
|
||||
}
|
||||
|
||||
private void testAccountClientUrls(RealmResource realm) {
|
||||
ClientRepresentation accountConsoleClient = realm.clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
|
||||
|
||||
|
@ -331,6 +339,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||
assertFalse(accountConsoleClient.isFullScopeAllowed());
|
||||
assertTrue(accountConsoleClient.isStandardFlowEnabled());
|
||||
assertFalse(accountConsoleClient.isDirectAccessGrantsEnabled());
|
||||
assertEquals("S256", accountConsoleClient.getAttributes().get(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD));
|
||||
|
||||
ClientResource clientResource = realm.clients().get(accountConsoleClient.getId());
|
||||
|
||||
|
|
|
@ -238,8 +238,6 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
// KEYCLOAK-3692
|
||||
@Test
|
||||
public void accessTokenWrongCode() throws Exception {
|
||||
oauth.clientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
|
||||
oauth.openLoginForm();
|
||||
|
||||
String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
@ -247,9 +245,9 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
oauth.fillLoginForm("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client(Constants.ADMIN_CONSOLE_CLIENT_ID).detail(Details.REDIRECT_URI, AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html").assertEvent();
|
||||
events.expectLogin().assertEvent();
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(loginPageCode, null);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(loginPageCode, "password");
|
||||
|
||||
assertEquals(400, response.getStatusCode());
|
||||
assertNull(response.getRefreshToken());
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.oidc.PkceGenerator;
|
||||
import org.keycloak.testsuite.runonserver.ServerVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -65,9 +66,11 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
|
|||
try (CloseableHttpClient client = HttpClients.custom().setDefaultCookieStore(cookieStore).build()) {
|
||||
String redirectUri = URLEncoder.encode(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/admin/master/console", "UTF-8");
|
||||
|
||||
PkceGenerator pkce = new PkceGenerator();
|
||||
|
||||
HttpGet get = new HttpGet(
|
||||
suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/auth?response_type=code&client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID +
|
||||
"&redirect_uri=" + redirectUri);
|
||||
"&redirect_uri=" + redirectUri + "&scope=openid&code_challenge_method=S256&code_challenge=" + pkce.getCodeChallenge());
|
||||
|
||||
CloseableHttpResponse response = client.execute(get);
|
||||
String s = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PkceGenerator {
|
||||
|
||||
private String codeVerifier;
|
||||
|
||||
private String codeChallenge;
|
||||
|
||||
public PkceGenerator() {
|
||||
codeVerifier = UUID.randomUUID().toString() + "-" + UUID.randomUUID().toString(); // Good enough for testing, but shouldn't be used elsewhere
|
||||
codeChallenge = generateS256CodeChallenge(codeVerifier);
|
||||
}
|
||||
|
||||
public PkceGenerator(String codeVerifier) {
|
||||
this.codeVerifier = codeVerifier;
|
||||
codeChallenge = generateS256CodeChallenge(codeVerifier);
|
||||
}
|
||||
|
||||
public String getCodeVerifier() {
|
||||
return codeVerifier;
|
||||
}
|
||||
|
||||
public String getCodeChallenge() {
|
||||
return codeChallenge;
|
||||
}
|
||||
|
||||
private String generateS256CodeChallenge(String codeVerifier) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(codeVerifier.getBytes("ISO_8859_1"));
|
||||
byte[] digestBytes = md.digest();
|
||||
String codeChallenge = Base64Url.encode(digestBytes);
|
||||
return codeChallenge;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue