process claims parameter
also support parsing from request object
This commit is contained in:
parent
6119572934
commit
632414cc92
5 changed files with 114 additions and 8 deletions
|
@ -390,6 +390,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
if (request.getPrompt() != null) authenticationSession.setClientNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
|
if (request.getPrompt() != null) authenticationSession.setClientNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
|
||||||
if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
|
if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
|
||||||
if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
|
if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
|
||||||
|
if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims());
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-4
|
// https://tools.ietf.org/html/rfc7636#section-4
|
||||||
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
|
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class AuthorizationEndpointRequest {
|
||||||
String nonce;
|
String nonce;
|
||||||
Integer maxAge;
|
Integer maxAge;
|
||||||
String idpHint;
|
String idpHint;
|
||||||
|
String claims;
|
||||||
Map<String, String> additionalReqParams = new HashMap<>();
|
Map<String, String> additionalReqParams = new HashMap<>();
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||||
|
@ -86,6 +87,10 @@ public class AuthorizationEndpointRequest {
|
||||||
return idpHint;
|
return idpHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getClaims() {
|
||||||
|
return claims;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getAdditionalReqParams() {
|
public Map<String, String> getAdditionalReqParams() {
|
||||||
return additionalReqParams;
|
return additionalReqParams;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.protocol.oidc.endpoints.request;
|
package org.keycloak.protocol.oidc.endpoints.request;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
|
@ -39,7 +39,7 @@ import org.keycloak.util.JsonSerialization;
|
||||||
*/
|
*/
|
||||||
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
|
|
||||||
private final Map<String, Object> requestParams;
|
private final JsonNode requestParams;
|
||||||
|
|
||||||
public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception {
|
public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception {
|
||||||
JWSInput input = new JWSInput(requestObject);
|
JWSInput input = new JWSInput(requestObject);
|
||||||
|
@ -52,7 +52,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.getAlgorithm() == Algorithm.none) {
|
if (header.getAlgorithm() == Algorithm.none) {
|
||||||
this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
|
this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class);
|
||||||
} else if (header.getAlgorithm() == Algorithm.RS256) {
|
} else if (header.getAlgorithm() == Algorithm.RS256) {
|
||||||
PublicKey clientPublicKey = PublicKeyStorageManager.getClientPublicKey(session, client, input);
|
PublicKey clientPublicKey = PublicKeyStorageManager.getClientPublicKey(session, client, input);
|
||||||
if (clientPublicKey == null) {
|
if (clientPublicKey == null) {
|
||||||
|
@ -64,7 +64,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
throw new RuntimeException("Failed to verify signature on 'request' object");
|
throw new RuntimeException("Failed to verify signature on 'request' object");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
|
this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unsupported JWA algorithm used for signed request");
|
throw new RuntimeException("Unsupported JWA algorithm used for signed request");
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,14 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getParameter(String paramName) {
|
protected String getParameter(String paramName) {
|
||||||
Object val = this.requestParams.get(paramName);
|
JsonNode val = this.requestParams.get(paramName);
|
||||||
return val==null ? null : val.toString();
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val.isValueNode()) {
|
||||||
|
return val.asText();
|
||||||
|
} else {
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,7 +90,9 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<String> keySet() {
|
protected Set<String> keySet() {
|
||||||
return requestParams.keySet();
|
HashSet<String> keys = new HashSet<>();
|
||||||
|
requestParams.fieldNames().forEachRemaining(keys::add);
|
||||||
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TypedHashMap extends HashMap<String, Object> {
|
static class TypedHashMap extends HashMap<String, Object> {
|
||||||
|
|
|
@ -61,6 +61,7 @@ abstract class AuthzEndpointRequestParser {
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
||||||
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
|
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
|
||||||
|
@ -87,6 +88,7 @@ abstract class AuthzEndpointRequestParser {
|
||||||
request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
|
request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
|
||||||
request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
|
request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
|
||||||
request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
|
request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
|
||||||
|
request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM));
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||||
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
|
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
|
||||||
|
|
|
@ -17,17 +17,24 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.oidc;
|
package org.keycloak.testsuite.oidc;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
@ -49,10 +56,16 @@ import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||||
|
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||||
import org.keycloak.testsuite.util.ClientManager;
|
import org.keycloak.testsuite.util.ClientManager;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -83,6 +96,10 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
||||||
@Page
|
@Page
|
||||||
protected ErrorPage errorPage;
|
protected ErrorPage errorPage;
|
||||||
|
|
||||||
|
@Deployment
|
||||||
|
public static WebArchive deploy() {
|
||||||
|
return RunOnServerDeployment.create(OIDCAdvancedRequestParamsTest.class, AbstractTestRealmKeycloakTest.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
@ -465,5 +482,78 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
||||||
|
|
||||||
events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLAIMS
|
||||||
|
// included in the session client notes, so custom providers can make use of it
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processClaimsQueryParam() throws IOException {
|
||||||
|
Map<String, Object> claims = ImmutableMap.of(
|
||||||
|
"id_token", ImmutableMap.of(
|
||||||
|
"test_claim", ImmutableMap.of(
|
||||||
|
"essential", true)));
|
||||||
|
|
||||||
|
String claimsJson = JsonSerialization.writeValueAsString(claims);
|
||||||
|
|
||||||
|
driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.CLAIMS_PARAM + "=" + claimsJson);
|
||||||
|
|
||||||
|
// need to login so session id can be read from event
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
String clientId = loginEvent.getClientId();
|
||||||
|
|
||||||
|
testingClient.server("test").run(session -> {
|
||||||
|
RealmModel realmModel = session.getContext().getRealm();
|
||||||
|
String clientUuid = realmModel.getClientByClientId(clientId).getId();
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId);
|
||||||
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUuid);
|
||||||
|
|
||||||
|
String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
assertEquals(claimsJson, claimsInSession);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processClaimsRequestParam() throws Exception {
|
||||||
|
Map<String, Object> claims = ImmutableMap.of(
|
||||||
|
"id_token", ImmutableMap.of(
|
||||||
|
"test_claim", ImmutableMap.of(
|
||||||
|
"essential", true)));
|
||||||
|
|
||||||
|
String claimsJson = JsonSerialization.writeValueAsString(claims);
|
||||||
|
|
||||||
|
Map<String, Object> oidcRequest = new HashMap<>();
|
||||||
|
oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, "test-app");
|
||||||
|
oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
|
||||||
|
oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
|
||||||
|
oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claims);
|
||||||
|
|
||||||
|
String request = new JWSBuilder().jsonContent(oidcRequest).none();
|
||||||
|
|
||||||
|
driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.REQUEST_PARAM + "=" + request);
|
||||||
|
|
||||||
|
// need to login so session id can be read from event
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
String clientId = loginEvent.getClientId();
|
||||||
|
|
||||||
|
testingClient.server("test").run(session -> {
|
||||||
|
RealmModel realmModel = session.getContext().getRealm();
|
||||||
|
String clientUuid = realmModel.getClientByClientId(clientId).getId();
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId);
|
||||||
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUuid);
|
||||||
|
|
||||||
|
String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
assertEquals(claimsJson, claimsInSession);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue