Merge pull request #4523 from abustya/master
KEYCLOAK-5616 Processing of claims parameter
This commit is contained in:
commit
c6483f8b1e
5 changed files with 114 additions and 8 deletions
|
@ -433,6 +433,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
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.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
|
||||
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
|
||||
|
|
|
@ -36,6 +36,7 @@ public class AuthorizationEndpointRequest {
|
|||
String nonce;
|
||||
Integer maxAge;
|
||||
String idpHint;
|
||||
String claims;
|
||||
Map<String, String> additionalReqParams = new HashMap<>();
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-6.1
|
||||
|
@ -86,6 +87,10 @@ public class AuthorizationEndpointRequest {
|
|||
return idpHint;
|
||||
}
|
||||
|
||||
public String getClaims() {
|
||||
return claims;
|
||||
}
|
||||
|
||||
public Map<String, String> getAdditionalReqParams() {
|
||||
return additionalReqParams;
|
||||
}
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.endpoints.request;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
|
@ -39,7 +39,7 @@ import org.keycloak.util.JsonSerialization;
|
|||
*/
|
||||
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||
|
||||
private final Map<String, Object> requestParams;
|
||||
private final JsonNode requestParams;
|
||||
|
||||
public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception {
|
||||
JWSInput input = new JWSInput(requestObject);
|
||||
|
@ -52,7 +52,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
}
|
||||
|
||||
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) {
|
||||
PublicKey clientPublicKey = PublicKeyStorageManager.getClientPublicKey(session, client, input);
|
||||
if (clientPublicKey == null) {
|
||||
|
@ -64,7 +64,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
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 {
|
||||
throw new RuntimeException("Unsupported JWA algorithm used for signed request");
|
||||
}
|
||||
|
@ -72,8 +72,14 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
|
||||
@Override
|
||||
protected String getParameter(String paramName) {
|
||||
Object val = this.requestParams.get(paramName);
|
||||
return val==null ? null : val.toString();
|
||||
JsonNode val = this.requestParams.get(paramName);
|
||||
if (val == null) {
|
||||
return null;
|
||||
} else if (val.isValueNode()) {
|
||||
return val.asText();
|
||||
} else {
|
||||
return val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,7 +90,9 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
|||
|
||||
@Override
|
||||
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> {
|
||||
|
|
|
@ -61,6 +61,7 @@ abstract class AuthzEndpointRequestParser {
|
|||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
|
||||
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_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
|
||||
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.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_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
|
||||
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
|
||||
|
|
|
@ -17,17 +17,24 @@
|
|||
|
||||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Details;
|
||||
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.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
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.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
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.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
@ -83,6 +96,10 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(OIDCAdvancedRequestParamsTest.class, AbstractTestRealmKeycloakTest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
|
@ -478,5 +495,78 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
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