process claims parameter

also support parsing from request object
This commit is contained in:
Áron Bustya 2017-10-01 16:46:27 +02:00
parent 6119572934
commit 632414cc92
5 changed files with 114 additions and 8 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {
@ -466,4 +483,77 @@ 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);
});
}
} }