[KEYCLOAK-18729] - Support JAR when using PAR
This commit is contained in:
parent
009d4ca445
commit
a79d28f115
10 changed files with 337 additions and 27 deletions
|
@ -81,7 +81,7 @@ public class AuthorizationEndpointRequestParserProcessor {
|
|||
// Define, if the request is `PAR` or usual `Request Object`.
|
||||
RequestUriType requestUriType = getRequestUriType(requestUriParam);
|
||||
if (requestUriType == RequestUriType.PAR) {
|
||||
new AuthzEndpointParParser(session, requestUriParam).parseRequest(request);
|
||||
new AuthzEndpointParParser(session, client, requestUriParam).parseRequest(request);
|
||||
} else {
|
||||
// Validate "requestUriParam" with allowed requestUris
|
||||
List<String> requestUris = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestUris();
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.jose.JOSEHeader;
|
||||
import org.keycloak.jose.JOSE;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
|
@ -33,7 +34,7 @@ import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
|||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||
public class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||
|
||||
private static void validateAlgorithm(JOSE jwt, ClientModel clientModel) {
|
||||
if (jwt instanceof JWSInput) {
|
||||
|
@ -62,6 +63,16 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
throw new RuntimeException("Failed to verify signature on 'request' object");
|
||||
}
|
||||
|
||||
JsonNode clientId = this.requestParams.get(OAuth2Constants.CLIENT_ID);
|
||||
|
||||
if (clientId == null) {
|
||||
throw new RuntimeException("Request object must be set with the client_id");
|
||||
}
|
||||
|
||||
if (!client.getClientId().equals(clientId.asText())) {
|
||||
throw new RuntimeException("The client_id in the request object is not the same as the authorizing client");
|
||||
}
|
||||
|
||||
session.setAttribute(AuthzEndpointRequestParser.AUTHZ_REQUEST_OBJECT, requestParams);
|
||||
}
|
||||
|
||||
|
@ -89,4 +100,10 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
requestParams.fieldNames().forEachRemaining(keys::add);
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> T replaceIfNotNull(T previousVal, T newVal) {
|
||||
// force parameters values from request object as per spec any parameter set directly should be ignored
|
||||
return newVal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,13 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.PushedAuthzRequestStoreProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
||||
import org.keycloak.protocol.oidc.endpoints.request.AuthzEndpointRequestObjectParser;
|
||||
import org.keycloak.protocol.oidc.endpoints.request.AuthzEndpointRequestParser;
|
||||
import org.keycloak.protocol.oidc.par.endpoints.ParEndpoint;
|
||||
|
||||
|
@ -39,11 +43,14 @@ public class AuthzEndpointParParser extends AuthzEndpointRequestParser {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(AuthzEndpointParParser.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ClientModel client;
|
||||
private Map<String, String> requestParams;
|
||||
|
||||
private String invalidRequestMessage = null;
|
||||
|
||||
public AuthzEndpointParParser(KeycloakSession session, String requestUri) {
|
||||
public AuthzEndpointParParser(KeycloakSession session, ClientModel client, String requestUri) {
|
||||
this.session = session;
|
||||
this.client = client;
|
||||
PushedAuthzRequestStoreProvider parStore = session.getProvider(PushedAuthzRequestStoreProvider.class);
|
||||
UUID key;
|
||||
try {
|
||||
|
@ -67,6 +74,19 @@ public class AuthzEndpointParParser extends AuthzEndpointRequestParser {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseRequest(AuthorizationEndpointRequest request) {
|
||||
String requestParam = requestParams.get(OIDCLoginProtocol.REQUEST_PARAM);
|
||||
|
||||
if (requestParam != null) {
|
||||
// parses the request object if PAR was registered using JAR
|
||||
// parameters from requets object have precedence over those sent directly in the request
|
||||
new AuthzEndpointRequestObjectParser(session, requestParam, client).parseRequest(request);
|
||||
} else {
|
||||
super.parseRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getParameter(String paramName) {
|
||||
return requestParams.get(paramName);
|
||||
|
|
|
@ -152,7 +152,7 @@ public class SecureRequestObjectExecutor implements ClientPolicyExecutorProvider
|
|||
}
|
||||
|
||||
// check whether scope exists in both query parameter and request object
|
||||
if (params.getFirst(OIDCLoginProtocol.SCOPE_PARAM) == null || requestObject.get(OIDCLoginProtocol.SCOPE_PARAM) == null) {
|
||||
if (params.getFirst(OIDCLoginProtocol.SCOPE_PARAM) == null && requestObject.get(OIDCLoginProtocol.SCOPE_PARAM) == null) {
|
||||
logger.trace("scope object not exist.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Parameter 'scope' missing in the request parameters or in 'request' object");
|
||||
}
|
||||
|
|
|
@ -230,12 +230,18 @@ public class TestingOIDCEndpointsApplicationResource {
|
|||
@NoCache
|
||||
public void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
|
||||
@QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
|
||||
@QueryParam("state") String state,
|
||||
@QueryParam("jwaAlgorithm") String jwaAlgorithm) {
|
||||
|
||||
Map<String, Object> oidcRequest = new HashMap<>();
|
||||
oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
|
||||
oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
|
||||
oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
|
||||
|
||||
if (state != null) {
|
||||
oidcRequest.put(OIDCLoginProtocol.STATE_PARAM, state);
|
||||
}
|
||||
|
||||
if (maxAge != null) {
|
||||
oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge));
|
||||
}
|
||||
|
|
|
@ -65,8 +65,16 @@ public interface TestOIDCEndpointsApplicationResource {
|
|||
@Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
|
||||
void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
|
||||
@QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
|
||||
@QueryParam("state") String state,
|
||||
@QueryParam("jwaAlgorithm") String jwaAlgorithm);
|
||||
|
||||
@GET
|
||||
@Path("/set-oidc-request")
|
||||
@Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
|
||||
void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
|
||||
@QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
|
||||
@QueryParam("jwaAlgorithm") String jwaAlgorithm);
|
||||
|
||||
@GET
|
||||
@Path("/register-oidc-request")
|
||||
@Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
|
||||
|
|
|
@ -543,12 +543,13 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
|
|||
requestObject.setResponseType("code");
|
||||
requestObject.setRedirectUriParam(oauth.getRedirectUri());
|
||||
requestObject.setScope("openid");
|
||||
String scope = KeycloakModelUtils.generateId();
|
||||
oauth.stateParamHardcoded(scope);
|
||||
requestObject.setState(scope);
|
||||
String state = KeycloakModelUtils.generateId();
|
||||
oauth.stateParamHardcoded(state);
|
||||
requestObject.setState(state);
|
||||
requestObject.setMax_age(Integer.valueOf(600));
|
||||
requestObject.setOtherClaims("custom_claim_ein", "rot");
|
||||
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
|
||||
requestObject.setNonce(KeycloakModelUtils.generateId());
|
||||
return requestObject;
|
||||
}
|
||||
|
||||
|
|
|
@ -1193,7 +1193,15 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
|
|||
registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
|
||||
oauth.openLoginForm();
|
||||
assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
assertEquals("Invalid parameter. Parameters in 'request' object not matching with request parameters", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||
|
||||
registerRequestObject(requestObject, clientId, Algorithm.ES256, true);
|
||||
oauth.scope(null);
|
||||
oauth.openid(false);
|
||||
oauth.openLoginForm();
|
||||
assertEquals(OAuthErrorException.INVALID_REQUEST, oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
assertEquals("Parameter 'scope' missing in the request parameters or in 'request' object", oauth.getCurrentQuery().get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||
oauth.openid(true);
|
||||
|
||||
// check whether "exp" claim exists
|
||||
requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
|
||||
|
|
|
@ -547,7 +547,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object in "request" param
|
||||
oauth.request(oidcClientEndpointsResource.getOIDCRequest());
|
||||
|
@ -569,7 +569,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object reference in "request_uri" param
|
||||
oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
|
||||
|
@ -611,7 +611,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object in "request" param
|
||||
oauth.request(oidcClientEndpointsResource.getOIDCRequest());
|
||||
|
@ -637,7 +637,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object reference in "request_uri" param
|
||||
oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
|
||||
|
@ -683,7 +683,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object in "request" param
|
||||
oauth.request(oidcClientEndpointsResource.getOIDCRequest());
|
||||
|
@ -779,7 +779,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Set up a request object
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", "mystate2", Algorithm.none.toString());
|
||||
|
||||
// Send request object reference in "request_uri" param
|
||||
oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
|
||||
|
@ -812,7 +812,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Assert the value from request object has bigger priority then from the query parameter.
|
||||
oauth.redirectUri("http://invalid");
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", "mystate2", Algorithm.none.toString());
|
||||
requestStr = oidcClientEndpointsResource.getOIDCRequest();
|
||||
|
||||
oauth.request(requestStr);
|
||||
|
@ -824,13 +824,11 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
@Test
|
||||
public void requestUriParamUnsigned() throws Exception {
|
||||
oauth.stateParamHardcoded("mystate1");
|
||||
|
||||
String validRedirectUri = oauth.getRedirectUri();
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
// Send request object with invalid redirect uri.
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, "mystate1", Algorithm.none.toString());
|
||||
|
||||
oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
|
||||
oauth.openLoginForm();
|
||||
|
@ -839,7 +837,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
// Assert the value from request object has bigger priority then from the query parameter.
|
||||
oauth.redirectUri("http://invalid");
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", "mystate1", Algorithm.none.toString());
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
Assert.assertNotNull(response.getCode());
|
||||
|
@ -849,10 +847,9 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
@Test
|
||||
public void requestUriParamWithAllowedRequestUris() throws Exception {
|
||||
oauth.stateParamHardcoded("mystate1");
|
||||
String validRedirectUri = oauth.getRedirectUri();
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", "mystate1", Algorithm.none.toString());
|
||||
ClientManager.ClientManagerBuilder clientMgrBuilder = ClientManager.realm(adminClient.realm("test")).clientId("test-app");
|
||||
|
||||
oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
|
||||
|
@ -915,8 +912,6 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
@Test
|
||||
public void requestUriParamSigned() throws Exception {
|
||||
oauth.stateParamHardcoded("mystate3");
|
||||
|
||||
String validRedirectUri = oauth.getRedirectUri();
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
|
@ -937,7 +932,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys("RS256").get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
|
||||
|
||||
// Verify signed request_uri will fail due to failed signature validation
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", "mystate3", Algorithm.RS256.toString());
|
||||
oauth.openLoginForm();
|
||||
Assert.assertTrue(errorPage.isCurrent());
|
||||
assertEquals("Invalid Request", errorPage.getError());
|
||||
|
@ -968,8 +963,6 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
try {
|
||||
oauth.stateParamHardcoded("mystate3");
|
||||
|
||||
String validRedirectUri = oauth.getRedirectUri();
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
|
@ -983,7 +976,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
if (Algorithm.none != actualAlgorithm) oidcClientEndpointsResource.generateKeys(actualAlgorithm.name());
|
||||
|
||||
// register request object
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", actualAlgorithm.name());
|
||||
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", "mystate3", actualAlgorithm.name());
|
||||
|
||||
// use and set jwks_url
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
|
@ -1214,6 +1207,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
|
||||
oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
|
||||
oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claims);
|
||||
oidcRequest.put(OIDCLoginProtocol.SCOPE_PARAM, "openid");
|
||||
String request = new JWSBuilder().jsonContent(oidcRequest).none();
|
||||
|
||||
oauth = oauth.request(request);
|
||||
|
@ -1261,6 +1255,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
|
||||
oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
|
||||
oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claims);
|
||||
oidcRequest.put(OIDCLoginProtocol.SCOPE_PARAM, "openid");
|
||||
request = new JWSBuilder().jsonContent(oidcRequest).none();
|
||||
|
||||
oauth = oauth.request(request);
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.testsuite.par;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
||||
|
@ -39,14 +41,19 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.ParConfig;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
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;
|
||||
|
@ -55,9 +62,13 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
|
||||
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.client.AbstractClientPoliciesTest;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
|
||||
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.services.clientpolicy.executor.TestRaiseExeptionExecutorFactory;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
@ -66,6 +77,7 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
|
|||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient.ParResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.QUARKUS;
|
||||
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
|
||||
|
@ -203,6 +215,249 @@ public class ParTest extends AbstractClientPoliciesTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulUsingRequestParameter() throws Exception {
|
||||
try {
|
||||
// setup PAR realm settings
|
||||
int requestUriLifespan = 45;
|
||||
setParRealmSettings(requestUriLifespan);
|
||||
|
||||
// create client dynamically
|
||||
String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
|
||||
clientRep.setRequirePushedAuthorizationRequests(Boolean.TRUE);
|
||||
clientRep.setRedirectUris(new ArrayList<>(Arrays.asList(CLIENT_REDIRECT_URI)));
|
||||
});
|
||||
|
||||
oauth.clientId(clientId);
|
||||
|
||||
OIDCClientRepresentation oidcCRep = getClientDynamically(clientId);
|
||||
String clientSecret = oidcCRep.getClientSecret();
|
||||
assertEquals(Boolean.TRUE, oidcCRep.getRequirePushedAuthorizationRequests());
|
||||
assertTrue(oidcCRep.getRedirectUris().contains(CLIENT_REDIRECT_URI));
|
||||
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, oidcCRep.getTokenEndpointAuthMethod());
|
||||
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = new TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject();
|
||||
requestObject.id(KeycloakModelUtils.generateId());
|
||||
requestObject.iat(Long.valueOf(Time.currentTime()));
|
||||
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
requestObject.setClientId(oauth.getClientId());
|
||||
requestObject.setResponseType("code");
|
||||
requestObject.setRedirectUriParam(CLIENT_REDIRECT_URI);
|
||||
requestObject.setScope("openid");
|
||||
requestObject.setNonce(KeycloakModelUtils.generateId());
|
||||
|
||||
byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
|
||||
String encodedRequestObject = Base64Url.encode(contentBytes);
|
||||
TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
// use and set jwks_url
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
|
||||
clientResource.update(clientRep);
|
||||
client.generateKeys(org.keycloak.crypto.Algorithm.RS256);
|
||||
client.registerOIDCRequest(encodedRequestObject, org.keycloak.crypto.Algorithm.RS256);
|
||||
|
||||
// do not send any other parameter but the request request parameter
|
||||
oauth.request(client.getOIDCRequest());
|
||||
oauth.responseType(null);
|
||||
oauth.redirectUri(null);
|
||||
oauth.scope(null);
|
||||
ParResponse pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
String requestUri = pResp.getRequestUri();
|
||||
assertEquals(requestUriLifespan, pResp.getExpiresIn());
|
||||
|
||||
// Authorization Request with request_uri of PAR
|
||||
// remove parameters as query strings of uri
|
||||
oauth.redirectUri(null);
|
||||
oauth.scope(null);
|
||||
oauth.responseType(null);
|
||||
oauth.request(null);
|
||||
oauth.requestUri(requestUri);
|
||||
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
|
||||
|
||||
// Token Request
|
||||
oauth.redirectUri(CLIENT_REDIRECT_URI); // get tokens, it needed. https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
||||
OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(loginResponse.getCode(), clientSecret);
|
||||
assertEquals(200, res.getStatusCode());
|
||||
|
||||
oauth.verifyToken(res.getAccessToken());
|
||||
IDToken idToken = oauth.verifyIDToken(res.getIdToken());
|
||||
assertEquals(requestObject.getNonce(), idToken.getNonce());
|
||||
} finally {
|
||||
restoreParRealmSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestParameterPrecedenceOverOtherParameters() throws Exception {
|
||||
try {
|
||||
// setup PAR realm settings
|
||||
int requestUriLifespan = 45;
|
||||
setParRealmSettings(requestUriLifespan);
|
||||
|
||||
// create client dynamically
|
||||
String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
|
||||
clientRep.setRequirePushedAuthorizationRequests(Boolean.TRUE);
|
||||
clientRep.setRedirectUris(new ArrayList<>(Arrays.asList(CLIENT_REDIRECT_URI)));
|
||||
});
|
||||
|
||||
oauth.clientId(clientId);
|
||||
|
||||
OIDCClientRepresentation oidcCRep = getClientDynamically(clientId);
|
||||
String clientSecret = oidcCRep.getClientSecret();
|
||||
assertEquals(Boolean.TRUE, oidcCRep.getRequirePushedAuthorizationRequests());
|
||||
assertTrue(oidcCRep.getRedirectUris().contains(CLIENT_REDIRECT_URI));
|
||||
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, oidcCRep.getTokenEndpointAuthMethod());
|
||||
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = new TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject();
|
||||
requestObject.id(KeycloakModelUtils.generateId());
|
||||
requestObject.iat(Long.valueOf(Time.currentTime()));
|
||||
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
requestObject.setClientId(oauth.getClientId());
|
||||
requestObject.setResponseType("code");
|
||||
requestObject.setRedirectUriParam(CLIENT_REDIRECT_URI);
|
||||
requestObject.setScope("openid");
|
||||
requestObject.setNonce(KeycloakModelUtils.generateId());
|
||||
requestObject.setState(oauth.stateParamRandom().getState());
|
||||
|
||||
|
||||
byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
|
||||
String encodedRequestObject = Base64Url.encode(contentBytes);
|
||||
TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
// use and set jwks_url
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
|
||||
clientResource.update(clientRep);
|
||||
client.generateKeys(org.keycloak.crypto.Algorithm.RS256);
|
||||
client.registerOIDCRequest(encodedRequestObject, org.keycloak.crypto.Algorithm.RS256);
|
||||
|
||||
// do not send any other parameter but the request request parameter
|
||||
oauth.request(client.getOIDCRequest());
|
||||
oauth.responseType("code id_token");
|
||||
oauth.redirectUri("http://invalid");
|
||||
oauth.scope(null);
|
||||
oauth.nonce("12345");
|
||||
ParResponse pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
String requestUri = pResp.getRequestUri();
|
||||
assertEquals(requestUriLifespan, pResp.getExpiresIn());
|
||||
|
||||
oauth.scope("invalid");
|
||||
oauth.redirectUri("http://invalid");
|
||||
oauth.responseType("invalid");
|
||||
oauth.redirectUri(null);
|
||||
oauth.nonce("12345");
|
||||
oauth.request(null);
|
||||
oauth.requestUri(requestUri);
|
||||
String wrongState = oauth.stateParamRandom().getState();
|
||||
oauth.stateParamHardcoded(wrongState);
|
||||
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
|
||||
assertEquals(requestObject.getState(), loginResponse.getState());
|
||||
assertNotEquals(requestObject.getState(), wrongState);
|
||||
|
||||
// Token Request
|
||||
oauth.redirectUri(CLIENT_REDIRECT_URI); // get tokens, it needed. https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
||||
OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(loginResponse.getCode(), clientSecret);
|
||||
assertEquals(200, res.getStatusCode());
|
||||
|
||||
oauth.verifyToken(res.getAccessToken());
|
||||
IDToken idToken = oauth.verifyIDToken(res.getIdToken());
|
||||
assertEquals(requestObject.getNonce(), idToken.getNonce());
|
||||
} finally {
|
||||
restoreParRealmSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreParameterIfNotSetinRequestObject() throws Exception {
|
||||
try {
|
||||
// setup PAR realm settings
|
||||
int requestUriLifespan = 45;
|
||||
setParRealmSettings(requestUriLifespan);
|
||||
|
||||
// create client dynamically
|
||||
String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), (OIDCClientRepresentation clientRep) -> {
|
||||
clientRep.setRequirePushedAuthorizationRequests(Boolean.TRUE);
|
||||
clientRep.setRedirectUris(new ArrayList<>(Arrays.asList(CLIENT_REDIRECT_URI)));
|
||||
});
|
||||
|
||||
oauth.clientId(clientId);
|
||||
|
||||
OIDCClientRepresentation oidcCRep = getClientDynamically(clientId);
|
||||
String clientSecret = oidcCRep.getClientSecret();
|
||||
assertEquals(Boolean.TRUE, oidcCRep.getRequirePushedAuthorizationRequests());
|
||||
assertTrue(oidcCRep.getRedirectUris().contains(CLIENT_REDIRECT_URI));
|
||||
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, oidcCRep.getTokenEndpointAuthMethod());
|
||||
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = new TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject();
|
||||
requestObject.id(KeycloakModelUtils.generateId());
|
||||
requestObject.iat(Long.valueOf(Time.currentTime()));
|
||||
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
requestObject.setClientId(oauth.getClientId());
|
||||
requestObject.setResponseType("code");
|
||||
requestObject.setRedirectUriParam(CLIENT_REDIRECT_URI);
|
||||
requestObject.setScope("openid");
|
||||
requestObject.setNonce(KeycloakModelUtils.generateId());
|
||||
|
||||
byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
|
||||
String encodedRequestObject = Base64Url.encode(contentBytes);
|
||||
TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
|
||||
|
||||
// use and set jwks_url
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
|
||||
clientResource.update(clientRep);
|
||||
client.generateKeys(org.keycloak.crypto.Algorithm.RS256);
|
||||
client.registerOIDCRequest(encodedRequestObject, org.keycloak.crypto.Algorithm.RS256);
|
||||
|
||||
// do not send any other parameter but the request request parameter
|
||||
oauth.request(client.getOIDCRequest());
|
||||
oauth.responseType("code id_token");
|
||||
oauth.redirectUri("http://invalid");
|
||||
oauth.scope(null);
|
||||
oauth.nonce("12345");
|
||||
ParResponse pResp = oauth.doPushedAuthorizationRequest(clientId, clientSecret);
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
String requestUri = pResp.getRequestUri();
|
||||
assertEquals(requestUriLifespan, pResp.getExpiresIn());
|
||||
|
||||
oauth.scope("invalid");
|
||||
oauth.redirectUri("http://invalid");
|
||||
oauth.responseType("invalid");
|
||||
oauth.redirectUri(null);
|
||||
oauth.nonce("12345");
|
||||
oauth.request(null);
|
||||
oauth.requestUri(requestUri);
|
||||
String wrongState = oauth.stateParamRandom().getState();
|
||||
oauth.stateParamHardcoded(wrongState);
|
||||
OAuthClient.AuthorizationEndpointResponse loginResponse = oauth.doLogin(TEST_USER_NAME, TEST_USER_PASSWORD);
|
||||
assertNull(loginResponse.getState());
|
||||
assertNotEquals(requestObject.getState(), wrongState);
|
||||
|
||||
// Token Request
|
||||
oauth.redirectUri(CLIENT_REDIRECT_URI); // get tokens, it needed. https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
||||
OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(loginResponse.getCode(), clientSecret);
|
||||
assertEquals(200, res.getStatusCode());
|
||||
|
||||
oauth.verifyToken(res.getAccessToken());
|
||||
IDToken idToken = oauth.verifyIDToken(res.getIdToken());
|
||||
assertEquals(requestObject.getNonce(), idToken.getNonce());
|
||||
} finally {
|
||||
restoreParRealmSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// success with the same client conducting multiple authz requests + PAR simultaneously
|
||||
@Test
|
||||
public void testSuccessfulMultipleParBySameClient() throws Exception {
|
||||
|
|
Loading…
Reference in a new issue