[KEYCLOAK-18729] - Support JAR when using PAR

This commit is contained in:
Pedro Igor 2021-07-13 21:35:45 -03:00 committed by Marek Posolda
parent 009d4ca445
commit a79d28f115
10 changed files with 337 additions and 27 deletions

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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");
}

View file

@ -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));
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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 {