keycloak-aplcache
Changes
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java 17(+17 -0)
services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java 15(+15 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java 14(+14 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java
index d7f9939..97adcf1 100644
--- a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java
@@ -85,6 +85,8 @@ public class OIDCClientRepresentation {
private String request_object_encryption_alg;
private String request_object_encryption_enc;
+
+ private String request_object_required;
private Integer default_max_age;
@@ -336,6 +338,14 @@ public class OIDCClientRepresentation {
public void setRequestObjectEncryptionEnc(String request_object_encryption_enc) {
this.request_object_encryption_enc = request_object_encryption_enc;
}
+
+ public String getRequestObjectRequired() {
+ return request_object_required;
+ }
+
+ public void setRequestObjectRequired(String request_object_required) {
+ this.request_object_required = request_object_required;
+ }
public Integer getDefaultMaxAge() {
return default_max_age;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
index 3562ed5..d645708 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
@@ -23,6 +23,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
@@ -31,6 +32,9 @@ import org.keycloak.services.messages.Messages;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.InputStream;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_URI;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -50,6 +54,19 @@ public class AuthorizationEndpointRequestParserProcessor {
throw new RuntimeException("Illegal to use both 'request' and 'request_uri' parameters together");
}
+ String requestObjectRequired = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectRequired();
+
+ if (REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI.equals(requestObjectRequired)
+ && requestParam == null && requestUriParam == null) {
+ throw new RuntimeException("Client is required to use 'request' or 'request_uri' parameter.");
+ } else if (REQUEST_OBJECT_REQUIRED_REQUEST.equals(requestObjectRequired)
+ && requestParam == null) {
+ throw new RuntimeException("Client is required to use 'request' parameter.");
+ } else if (REQUEST_OBJECT_REQUIRED_REQUEST_URI.equals(requestObjectRequired)
+ && requestUriParam == null) {
+ throw new RuntimeException("Client is required to use 'request_uri' parameter.");
+ }
+
if (requestParam != null) {
new AuthzEndpointRequestObjectParser(session, requestParam, client).parseRequest(request);
} else if (requestUriParam != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 6f4ff75..9fc49ba 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -31,6 +31,8 @@ public class OIDCAdvancedConfigWrapper {
private static final String USER_INFO_RESPONSE_SIGNATURE_ALG = "user.info.response.signature.alg";
private static final String REQUEST_OBJECT_SIGNATURE_ALG = "request.object.signature.alg";
+
+ private static final String REQUEST_OBJECT_REQUIRED = "request.object.required";
private static final String JWKS_URL = "jwks.url";
@@ -79,6 +81,14 @@ public class OIDCAdvancedConfigWrapper {
String algStr = alg==null ? null : alg.toString();
setAttribute(REQUEST_OBJECT_SIGNATURE_ALG, algStr);
}
+
+ public String getRequestObjectRequired() {
+ return getAttribute(REQUEST_OBJECT_REQUIRED);
+ }
+
+ public void setRequestObjectRequired(String requestObjectRequired) {
+ setAttribute(REQUEST_OBJECT_REQUIRED, requestObjectRequired);
+ }
public boolean isUseJwksUrl() {
String useJwksUrl = getAttribute(USE_JWKS_URL);
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 148d840..71b5066 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -92,6 +92,11 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String CLIENT_SECRET_POST = "client_secret_post";
public static final String CLIENT_SECRET_JWT = "client_secret_jwt";
public static final String PRIVATE_KEY_JWT = "private_key_jwt";
+
+ // Request object requirement options
+ public static final String REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI = "request or request_uri";
+ public static final String REQUEST_OBJECT_REQUIRED_REQUEST = "request only";
+ public static final String REQUEST_OBJECT_REQUIRED_REQUEST_URI = "request_uri only";
// https://tools.ietf.org/html/rfc7636#section-4.3
public static final String CODE_CHALLENGE_PARAM = "code_challenge";
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index e140b52..43526c6 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -48,6 +48,7 @@ import java.io.IOException;
import java.net.URI;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -115,6 +116,17 @@ public class DescriptionConverter {
configWrapper.setRequestObjectSignatureAlg(algorithm);
}
+ if (clientOIDC.getRequestObjectRequired() != null) {
+ String requestObjectRequired = clientOIDC.getRequestObjectRequired();
+ if (Arrays.asList(
+ OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI,
+ OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST,
+ OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_URI)
+ .contains(requestObjectRequired)) {
+ configWrapper.setRequestObjectRequired(requestObjectRequired);
+ }
+ }
+
return client;
}
@@ -185,6 +197,9 @@ public class DescriptionConverter {
if (config.getRequestObjectSignatureAlg() != null) {
response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
}
+ if (config.getRequestObjectRequired() != null) {
+ response.setRequestObjectRequired(config.getRequestObjectRequired());
+ }
if (config.isUseJwksUrl()) {
response.setJwksUri(config.getJwksUrl());
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index 57f71b2..9fbb6bc 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -200,6 +200,20 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256);
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256);
}
+
+ @Test
+ public void testRequestObjectRequired() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setRequestObjectRequired(OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI);
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals(OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI, response.getRequestObjectRequired());
+
+ // Test Keycloak representation
+ ClientRepresentation kcClient = getClient(response.getClientId());
+ OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
+ Assert.assertEquals(OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI, config.getRequestObjectRequired());
+ }
@Test
public void createClientImplicitFlow() throws ClientRegistrationException {
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 78ec0e7..ae258d6 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
@@ -71,6 +71,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST;
+import static org.keycloak.protocol.oidc.OIDCLoginProtocol.REQUEST_OBJECT_REQUIRED_REQUEST_URI;
+
/**
* Test for supporting advanced parameters of OIDC specs (max_age, prompt, ...)
*
@@ -376,6 +380,281 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
}
// REQUEST & REQUEST_URI
+
+ @Test
+ public void requestObjectNotRequiredNotProvided() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+
+ // Send request without request object
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response.getCode());
+ Assert.assertEquals("mystate2", response.getState());
+ assertTrue(appPage.isCurrent());
+ }
+
+ @Test
+ public void requestObjectNotRequiredProvidedInRequestParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object in "request" param
+ oauth.request(oidcClientEndpointsResource.getOIDCRequest());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response1.getCode());
+ Assert.assertEquals("mystate2", response1.getState());
+ assertTrue(appPage.isCurrent());
+ }
+
+ @Test
+ public void requestObjectNotRequiredProvidedInRequestUriParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object reference in "request_uri" param
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response2 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response2.getCode());
+ Assert.assertEquals("mystate2", response2.getState());
+ assertTrue(appPage.isCurrent());
+ }
+
+ @Test
+ public void requestObjectRequiredNotProvided() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Send request without request object
+ // Assert that the request is not accepted
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredProvidedInRequestParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object in "request" param
+ oauth.request(oidcClientEndpointsResource.getOIDCRequest());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response1.getCode());
+ Assert.assertEquals("mystate2", response1.getState());
+ assertTrue(appPage.isCurrent());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredProvidedInRequestUriParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_OR_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object reference in "request_uri" param
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response2 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response2.getCode());
+ Assert.assertEquals("mystate2", response2.getState());
+ assertTrue(appPage.isCurrent());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestParamNotProvided() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST);
+ clientResource.update(clientRep);
+
+ // Send request without request object
+ // Assert that the request is not accepted
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestParamProvidedInRequestParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object in "request" param
+ oauth.request(oidcClientEndpointsResource.getOIDCRequest());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response1.getCode());
+ Assert.assertEquals("mystate2", response1.getState());
+ assertTrue(appPage.isCurrent());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestParamProvidedInRequestUriParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object reference in "request_uri" param
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ // Assert that the request is accepted
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestUriParamNotProvided() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Send request without request object
+ // Assert that the request is not accepted
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestUriParamProvidedInRequestParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object in "request" param
+ oauth.request(oidcClientEndpointsResource.getOIDCRequest());
+ // Assert that the request is not accepted
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
+
+ @Test
+ public void requestObjectRequiredAsRequestUriParamProvidedInRequestUriParam() throws Exception {
+ oauth.stateParamHardcoded("mystate2");
+ // Set request object not required for client
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(REQUEST_OBJECT_REQUIRED_REQUEST_URI);
+ clientResource.update(clientRep);
+
+ // Set up a request object
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", oauth.getRedirectUri(), "10", Algorithm.none.toString());
+
+ // Send request object reference in "request_uri" param
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ // Assert that the request is accepted
+ OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response1.getCode());
+ Assert.assertEquals("mystate2", response1.getState());
+ assertTrue(appPage.isCurrent());
+
+ // Revert requiring request object for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectRequired(null);
+ clientResource.update(clientRep);
+ }
@Test
public void requestParamUnsigned() throws Exception {
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index c384b13..12c5e05 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -309,6 +309,8 @@ user-info-signed-response-alg=User Info Signed Response Algorithm
user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format.
request-object-signature-alg=Request Object Signature Algorithm
request-object-signature-alg.tooltip=JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', then Request object can be signed by any algorithm (including 'none' ).
+request-object-required=Request Object Required
+request-object-required-alg.tooltip=Specifies if the client needs to provide a request object with their authorization requests, and what method they can use for this. If set to "not required", providing a request object is optional. In all other cases providing a request object is mandatory. If set to "request", the request object must be provided by value. If set to "request_uri", the request object must be provided by reference. If set to "request or request_uri", either method can be used.
fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service.
assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 632ef2c..381a4c2 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -891,6 +891,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
"none",
"RS256"
];
+
+ $scope.requestObjectRequiredOptions = [
+ "not required",
+ "request or request_uri",
+ "request only",
+ "request_uri only"
+ ];
$scope.realm = realm;
$scope.samlAuthnStatement = false;
@@ -1045,6 +1052,9 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
var attrVal2 = $scope.client.attributes['request.object.signature.alg'];
$scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2;
+ var attrVal3 = $scope.client.attributes['request.object.required'];
+ $scope.requestObjectRequired = attrVal3==null ? 'not required' : attrVal3;
+
if ($scope.client.attributes["exclude.session.state.from.auth.response"]) {
if ($scope.client.attributes["exclude.session.state.from.auth.response"] == "true") {
$scope.excludeSessionStateFromAuthResponse = true;
@@ -1143,6 +1153,14 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
$scope.clientEdit.attributes['request.object.signature.alg'] = $scope.requestObjectSignatureAlg;
}
};
+
+ $scope.changeRequestObjectRequired = function() {
+ if ($scope.requestObjectRequired === 'not required') {
+ $scope.clientEdit.attributes['request.object.required'] = null;
+ } else {
+ $scope.clientEdit.attributes['request.object.required'] = $scope.requestObjectRequired;
+ }
+ };
$scope.$watch(function() {
return $location.path();
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 96e00e9..9352ff1 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -409,6 +409,19 @@
</div>
<kc-tooltip>{{:: 'request-object-signature-alg.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
+ <label class="col-md-2 control-label" for="changeRequestObjectRequired">{{:: 'request-object-required' | translate}}</label>
+ <div class="col-sm-6">
+ <div>
+ <select class="form-control" id="requestObjectRequired"
+ ng-change="changeRequestObjectRequired()"
+ ng-model="requestObjectRequired"
+ ng-options="sig for sig in requestObjectRequiredOptions">
+ </select>
+ </div>
+ </div>
+ <kc-tooltip>{{:: 'request-object-required.tooltip' | translate}}</kc-tooltip>
+ </div>
</fieldset>
<fieldset data-ng-show="protocol == 'openid-connect'">