From e0a59baaf247024304588542992d6e7f553ed9c9 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 5 Aug 2016 13:08:05 +0200 Subject: [PATCH] KEYCLOAK-3321 OIDC requests without 'nonce' claim should be rejected unless using the code flow. Started responseType tests --- .../java/org/keycloak/OAuth2Constants.java | 4 + .../protocol/oidc/OIDCLoginProtocol.java | 4 +- .../oidc/endpoints/AuthorizationEndpoint.java | 6 + .../keycloak/testsuite/util/OAuthClient.java | 37 +++++- .../testsuite/TestRealmKeycloakTest.java | 2 +- .../keycloak/testsuite/admin/ClientTest.java | 2 +- .../oauth/AuthorizationCodeTest.java | 12 +- .../testsuite/oauth/OAuthRedirectUriTest.java | 4 +- .../oidc/OIDCAdvancedRequestParamsTest.java | 43 +----- .../AbstractOIDCResponseTypeTest.java | 123 ++++++++++++++++++ .../OIDCBasicResponseTypeCodeTest.java | 72 ++++++++++ ...OIDCHybridResponseTypeCodeIDTokenTest.java | 76 +++++++++++ .../OIDCImplicitResponseTypeIDTokenTest.java | 72 ++++++++++ .../testsuite/util/ClientManager.java | 14 ++ .../src/test/resources/log4j.properties | 3 + 15 files changed, 419 insertions(+), 55 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/AbstractOIDCResponseTypeTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCBasicResponseTypeCodeTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCHybridResponseTypeCodeIDTokenTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCImplicitResponseTypeIDTokenTest.java diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java index 188d7593b9..2a7d37b4f7 100644 --- a/core/src/main/java/org/keycloak/OAuth2Constants.java +++ b/core/src/main/java/org/keycloak/OAuth2Constants.java @@ -42,6 +42,10 @@ public interface OAuth2Constants { String RESPONSE_TYPE = "response_type"; + String ACCESS_TOKEN = "access_token"; + + String ID_TOKEN = "id_token"; + String REFRESH_TOKEN = "refresh_token"; String AUTHORIZATION_CODE = "authorization_code"; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 3105122cf1..8271cc70c7 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -171,11 +171,11 @@ public class OIDCLoginProtocol implements LoginProtocol { .build(); if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) { - redirectUri.addParam("id_token", res.getIdToken()); + redirectUri.addParam(OAuth2Constants.ID_TOKEN, res.getIdToken()); } if (responseType.hasResponseType(OIDCResponseType.TOKEN)) { - redirectUri.addParam("access_token", res.getToken()); + redirectUri.addParam(OAuth2Constants.ACCESS_TOKEN, res.getToken()); redirectUri.addParam("token_type", res.getTokenType()); redirectUri.addParam("session_state", res.getSessionState()); redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn())); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 95aa9a6f56..b08d3d8298 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -340,6 +340,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, null); } + if (parsedResponseType.isImplicitOrHybridFlow() && nonce == null) { + logger.missingParameter(OIDCLoginProtocol.NONCE_PARAM); + event.error(Errors.INVALID_REQUEST); + return redirectErrorToClient(parsedResponseMode, OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce"); + } + return null; } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java index 8c7b2908e5..1d632c1051 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java @@ -41,6 +41,7 @@ import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.representations.JSONWebKeySet; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.IDToken; import org.keycloak.representations.RefreshToken; @@ -120,7 +121,7 @@ public class OAuthClient { maxAge = null; } - public AuthorizationCodeResponse doLogin(String username, String password) { + public AuthorizationEndpointResponse doLogin(String username, String password) { openLoginForm(); String src = driver.getPageSource(); try { @@ -132,7 +133,7 @@ public class OAuthClient { throw t; } - return new AuthorizationCodeResponse(this); + return new AuthorizationEndpointResponse(this); } public void doLoginGrant(String username, String password) { @@ -637,7 +638,7 @@ public class OAuthClient { return realm; } - public static class AuthorizationCodeResponse { + public static class AuthorizationEndpointResponse { private boolean isRedirected; private String code; @@ -645,11 +646,25 @@ public class OAuthClient { private String error; private String errorDescription; - public AuthorizationCodeResponse(OAuthClient client) { - this(client, false); + // Just during OIDC implicit or hybrid flow + private String accessToken; + private String idToken; + + public AuthorizationEndpointResponse(OAuthClient client) { + boolean fragment; + try { + fragment = client.responseType != null && OIDCResponseType.parse(client.responseType).isImplicitOrHybridFlow(); + } catch (IllegalArgumentException iae) { + fragment = false; + } + init (client, fragment); } - public AuthorizationCodeResponse(OAuthClient client, boolean fragment) { + public AuthorizationEndpointResponse(OAuthClient client, boolean fragment) { + init(client, fragment); + } + + private void init(OAuthClient client, boolean fragment) { isRedirected = client.getCurrentRequest().equals(client.getRedirectUri()); Map params = fragment ? client.getCurrentFragment() : client.getCurrentQuery(); @@ -657,6 +672,8 @@ public class OAuthClient { state = params.get(OAuth2Constants.STATE); error = params.get(OAuth2Constants.ERROR); errorDescription = params.get(OAuth2Constants.ERROR_DESCRIPTION); + accessToken = params.get(OAuth2Constants.ACCESS_TOKEN); + idToken = params.get(OAuth2Constants.ID_TOKEN); } public boolean isRedirected() { @@ -678,6 +695,14 @@ public class OAuthClient { public String getErrorDescription() { return errorDescription; } + + public String getAccessToken() { + return accessToken; + } + + public String getIdToken() { + return idToken; + } } public static class AccessTokenResponse { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java index 0e10c589ce..040bb4c593 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/TestRealmKeycloakTest.java @@ -86,7 +86,7 @@ public abstract class TestRealmKeycloakTest extends AbstractKeycloakTest { String sessionId = loginEvent.getSessionId(); String codeId = loginEvent.getDetails().get(Details.CODE_ID); - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + String code = new OAuthClient.AuthorizationEndpointResponse(oauth).getCode(); OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); Assert.assertEquals(200, response.getStatusCode()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java index 2a9c643f3b..5202cda067 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java @@ -128,7 +128,7 @@ public class ClientTest extends AbstractAdminTest { OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); assertEquals(200, response.getStatusCode()); - OAuthClient.AuthorizationCodeResponse codeResponse = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse codeResponse = oauth.doLogin("test-user@localhost", "password"); OAuthClient.AccessTokenResponse response2 = oauth.doAccessTokenRequest(codeResponse.getCode(), "password"); assertEquals(200, response2.getStatusCode()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java index ece81d876e..cf69f74883 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java @@ -78,7 +78,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { public void authorizationRequest() throws IOException { oauth.state("OpenIdConnect.AuthenticationProperties=2302984sdlk"); - OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); Assert.assertTrue(response.isRedirected()); Assert.assertNotNull(response.getCode()); @@ -116,7 +116,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { oauth.state("mystate"); - OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); Assert.assertTrue(response.isRedirected()); Assert.assertNotNull(response.getCode()); @@ -131,7 +131,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { public void authorizationRequestNoState() throws IOException { oauth.state(null); - OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); Assert.assertTrue(response.isRedirected()); Assert.assertNotNull(response.getCode()); @@ -150,7 +150,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl()); driver.navigate().to(b.build().toURL()); - OAuthClient.AuthorizationCodeResponse errorResponse = new OAuthClient.AuthorizationCodeResponse(oauth, true); + OAuthClient.AuthorizationEndpointResponse errorResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true); Assert.assertTrue(errorResponse.isRedirected()); Assert.assertEquals(errorResponse.getError(), OAuthErrorException.UNSUPPORTED_RESPONSE_TYPE); Assert.assertEquals(errorResponse.getErrorDescription(), "Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client."); @@ -164,7 +164,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl()); driver.navigate().to(b.build().toURL()); - OAuthClient.AuthorizationCodeResponse errorResponse = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse errorResponse = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertTrue(errorResponse.isRedirected()); Assert.assertEquals(errorResponse.getError(), OAuthErrorException.INVALID_REQUEST); @@ -177,7 +177,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest { UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl()); driver.navigate().to(b.build().toURL()); - OAuthClient.AuthorizationCodeResponse errorResponse = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse errorResponse = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertTrue(errorResponse.isRedirected()); Assert.assertEquals(errorResponse.getError(), OAuthErrorException.UNSUPPORTED_RESPONSE_TYPE); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index 853b38b09d..f80e789ef7 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -154,7 +154,7 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest { @Test public void testValid() throws IOException { oauth.redirectUri(APP_ROOT + "/auth"); - OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); Assert.assertNotNull(response.getCode()); URL url = new URL(driver.getCurrentUrl()); @@ -175,7 +175,7 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest { @Test public void testWithParams() throws IOException { oauth.redirectUri(APP_ROOT + "/auth?key=value"); - OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); Assert.assertNotNull(response.getCode()); URL url = new URL(driver.getCurrentUrl()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java index 2296908243..191581e600 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java @@ -47,7 +47,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** - * Test for supporting advanced parameters of OIDC specs (max_age, nonce, prompt, ...) + * Test for supporting advanced parameters of OIDC specs (max_age, prompt, ...) * * @author Marek Posolda */ @@ -169,7 +169,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { events.assertEmpty(); // Assert error response was sent because not logged in - OAuthClient.AuthorizationCodeResponse resp = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertNull(resp.getCode()); Assert.assertEquals(OAuthErrorException.LOGIN_REQUIRED, resp.getError()); @@ -224,7 +224,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { assertTrue(appPage.isCurrent()); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - OAuthClient.AuthorizationCodeResponse resp = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertNull(resp.getCode()); Assert.assertEquals(OAuthErrorException.INTERACTION_REQUIRED, resp.getError()); @@ -242,7 +242,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=none"); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - resp = new OAuthClient.AuthorizationCodeResponse(oauth); + resp = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertNotNull(resp.getCode()); Assert.assertNull(resp.getError()); @@ -289,37 +289,6 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { } - - // NONCE - - @Test - public void nonceNotUsed() { - driver.navigate().to(oauth.getLoginFormUrl()); - - loginPage.assertCurrent(); - loginPage.login("test-user@localhost", "password"); - Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - - EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent(); - IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent); - - Assert.assertNull(idToken.getNonce()); - } - - @Test - public void nonceMatches() { - driver.navigate().to(oauth.getLoginFormUrl() + "&nonce=abcdef123456"); - - loginPage.assertCurrent(); - loginPage.login("test-user@localhost", "password"); - Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - - EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent(); - IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent); - - Assert.assertEquals("abcdef123456", idToken.getNonce()); - } - // DISPLAY & OTHERS @Test @@ -346,7 +315,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { assertTrue(appPage.isCurrent()); // Assert error response was sent because not logged in - OAuthClient.AuthorizationCodeResponse resp = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertNull(resp.getCode()); Assert.assertEquals(OAuthErrorException.REQUEST_NOT_SUPPORTED, resp.getError()); } @@ -359,7 +328,7 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest { assertTrue(appPage.isCurrent()); // Assert error response was sent because not logged in - OAuthClient.AuthorizationCodeResponse resp = new OAuthClient.AuthorizationCodeResponse(oauth); + OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); Assert.assertNull(resp.getCode()); Assert.assertEquals(OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, resp.getError()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/AbstractOIDCResponseTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/AbstractOIDCResponseTypeTest.java new file mode 100644 index 0000000000..047a62d4c9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/AbstractOIDCResponseTypeTest.java @@ -0,0 +1,123 @@ +/* + * 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.oidc.resptype; + +import java.util.List; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Rule; +import org.keycloak.OAuthErrorException; +import org.keycloak.events.Details; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.TestRealmKeycloakTest; +import org.keycloak.testsuite.admin.AbstractAdminTest; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.OAuthGrantPage; +import org.keycloak.testsuite.util.OAuthClient; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Abstract test for various values of response_type + * + * @author Marek Posolda + */ +public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Page + protected AppPage appPage; + + @Page + protected LoginPage loginPage; + + @Page + protected AccountUpdateProfilePage profilePage; + + @Page + protected OAuthGrantPage grantPage; + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + } + + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + + protected void nonceMatches() { + driver.navigate().to(oauth.getLoginFormUrl() + "&nonce=abcdef123456"); + + loginPage.assertCurrent(); + loginPage.login("test-user@localhost", "password"); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent(); + List idTokens = retrieveIDTokens(loginEvent); + + for (IDToken idToken : idTokens) { + Assert.assertEquals("abcdef123456", idToken.getNonce()); + } + } + + + protected void nonceNotUsed() { + driver.navigate().to(oauth.getLoginFormUrl()); + + loginPage.assertCurrent(); + loginPage.login("test-user@localhost", "password"); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent(); + + List idTokens = retrieveIDTokens(loginEvent); + for (IDToken idToken : idTokens) { + Assert.assertNull(idToken.getNonce()); + } + } + + + protected void nonceNotUsedErrorExpected() { + driver.navigate().to(oauth.getLoginFormUrl()); + + assertFalse(loginPage.isCurrent()); + assertTrue(appPage.isCurrent()); + + // Assert error response was sent because not logged in + OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth); + Assert.assertNull(resp.getCode()); + Assert.assertNull(resp.getIdToken()); + Assert.assertEquals(OAuthErrorException.INVALID_REQUEST, resp.getError()); + Assert.assertEquals("Missing parameter: nonce", resp.getErrorDescription()); + } + + protected abstract List retrieveIDTokens(EventRepresentation loginEvent); +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCBasicResponseTypeCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCBasicResponseTypeCodeTest.java new file mode 100644 index 0000000000..a8f51fb96e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCBasicResponseTypeCodeTest.java @@ -0,0 +1,72 @@ +/* + * 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.oidc.resptype; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.events.Details; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.OAuthClient; + +/** + * Test for response_type=code + * + * @author Marek Posolda + */ +public class OIDCBasicResponseTypeCodeTest extends AbstractOIDCResponseTypeTest { + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(true).implicitFlow(false); + + oauth.clientId("test-app"); + oauth.responseType(OIDCResponseType.CODE); + } + + + protected List retrieveIDTokens(EventRepresentation loginEvent) { + Assert.assertEquals(OIDCResponseType.CODE, loginEvent.getDetails().get(Details.RESPONSE_TYPE)); + + OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, false); + Assert.assertNull(authzResponse.getAccessToken()); + Assert.assertNull(authzResponse.getIdToken()); + + IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent); + + return Collections.singletonList(idToken); + } + + + @Test + public void nonceNotUsed() { + super.nonceNotUsed(); + } + + + @Test + public void nonceMatches() { + super.nonceMatches(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCHybridResponseTypeCodeIDTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCHybridResponseTypeCodeIDTokenTest.java new file mode 100644 index 0000000000..ff16927a52 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCHybridResponseTypeCodeIDTokenTest.java @@ -0,0 +1,76 @@ +/* + * 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.oidc.resptype; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.events.Details; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.OAuthClient; + +/** + * Tests with response_type=code id_token + * + * @author Marek Posolda + */ +public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseTypeTest { + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(true).implicitFlow(true); + + oauth.clientId("test-app"); + oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN); + } + + + protected List retrieveIDTokens(EventRepresentation loginEvent) { + Assert.assertEquals(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE)); + + // IDToken from the authorization response + OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true); + Assert.assertNull(authzResponse.getAccessToken()); + String idTokenStr = authzResponse.getIdToken(); + IDToken idToken = oauth.verifyIDToken(idTokenStr); + + // IDToken exchanged for the code + IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); + + return Arrays.asList(idToken, idToken2); + } + + + @Test + public void nonceNotUsedErrorExpected() { + super.nonceNotUsedErrorExpected(); + } + + + @Test + public void nonceMatches() { + super.nonceMatches(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCImplicitResponseTypeIDTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCImplicitResponseTypeIDTokenTest.java new file mode 100644 index 0000000000..4bcdc6508c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/resptype/OIDCImplicitResponseTypeIDTokenTest.java @@ -0,0 +1,72 @@ +/* + * 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.oidc.resptype; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.events.Details; +import org.keycloak.protocol.oidc.utils.OIDCResponseType; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.util.ClientManager; +import org.keycloak.testsuite.util.OAuthClient; + +/** + * Tests with response_type=id_token + * + * @author Marek Posolda + */ +public class OIDCImplicitResponseTypeIDTokenTest extends AbstractOIDCResponseTypeTest { + + @Before + public void clientConfiguration() { + ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(false).implicitFlow(true); + + oauth.clientId("test-app"); + oauth.responseType(OIDCResponseType.ID_TOKEN); + } + + + protected List retrieveIDTokens(EventRepresentation loginEvent) { + Assert.assertEquals(OIDCResponseType.ID_TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE)); + + OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true); + Assert.assertNull(authzResponse.getAccessToken()); + String idTokenStr = authzResponse.getIdToken(); + IDToken idToken = oauth.verifyIDToken(idTokenStr); + + return Collections.singletonList(idToken); + } + + + @Test + public void nonceNotUsedErrorExpected() { + super.nonceNotUsedErrorExpected(); + } + + + @Test + public void nonceMatches() { + super.nonceMatches(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java index 6702ec57eb..6bc7151772 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientManager.java @@ -67,6 +67,20 @@ public class ClientManager { clientResource.update(app); } + public ClientManagerBuilder standardFlow(Boolean enable) { + ClientRepresentation app = clientResource.toRepresentation(); + app.setStandardFlowEnabled(enable); + clientResource.update(app); + return this; + } + + public ClientManagerBuilder implicitFlow(Boolean enable) { + ClientRepresentation app = clientResource.toRepresentation(); + app.setImplicitFlowEnabled(enable); + clientResource.update(app); + return this; + } + public void fullScopeAllowed(boolean enable) { ClientRepresentation app = clientResource.toRepresentation(); app.setFullScopeAllowed(enable); diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties index 327102e806..5d5369c20b 100755 --- a/testsuite/integration/src/test/resources/log4j.properties +++ b/testsuite/integration/src/test/resources/log4j.properties @@ -56,6 +56,9 @@ log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=${ # Enable to view hibernate statistics log4j.logger.org.keycloak.connections.jpa.HibernateStatsReporter=debug +# Enable to view ldap logging +# log4j.logger.org.keycloak.federation.ldap=trace + # Enable to view kerberos/spnego logging # log4j.logger.org.keycloak.federation.kerberos=trace