diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 6b93561af2..0122657138 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -17,6 +17,11 @@ package org.keycloak.protocol.oidc.endpoints; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import javax.ws.rs.GET; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -33,7 +38,6 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.OIDCResponseMode; @@ -56,6 +60,43 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { public static final String CODE_AUTH_TYPE = "code"; + /** + * Prefix used to store additional HTTP GET params from original client request into {@link ClientSessionModel} note to be available later in Authenticators, RequiredActions etc. Prefix is used to + * prevent collisions with internally used notes. + * + * @see ClientSessionModel#getNote(String) + * @see #KNOWN_REQ_PARAMS + * @see #additionalReqParams + */ + public static final String CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX = "client_request_param_"; + /** + * Max number of additional req params copied into client session note to prevent DoS attacks + * + * @see #additionalReqParams + */ + public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5; + /** + * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored + * + * @see #additionalReqParams + */ + public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200; + + /** Set of known protocol GET params not to be stored into {@link #additionalReqParams} */ + private static final Set KNOWN_REQ_PARAMS = new HashSet<>(); + static { + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM); + KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT); + KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM); + } + private enum Action { REGISTER, CODE, FORGOT_CREDENTIALS } @@ -77,6 +118,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { private String prompt; private String nonce; private String idpHint; + protected Map additionalReqParams = new HashMap<>(); public AuthorizationEndpoint(RealmModel realm, EventBuilder event) { super(realm, event); @@ -98,6 +140,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT); nonce = params.getFirst(OIDCLoginProtocol.NONCE_PARAM); + extractAdditionalReqParams(params); + checkSsl(); checkRealm(); checkResponseType(); @@ -119,6 +163,27 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { throw new RuntimeException("Unknown action " + action); } + protected void extractAdditionalReqParams(MultivaluedMap params) { + for (String paramName : params.keySet()) { + if (!KNOWN_REQ_PARAMS.contains(paramName)) { + String value = params.getFirst(paramName); + if (value != null && value.trim().isEmpty()) { + value = null; + } + if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) { + if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) { + logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!"); + break; + } + additionalReqParams.put(paramName, value); + } else { + logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE); + } + } + + } + } + public AuthorizationEndpoint register() { event.event(EventType.REGISTER); action = Action.REGISTER; @@ -249,6 +314,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt); if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint); if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode); + + if (additionalReqParams != null) { + for (String paramName : additionalReqParams.keySet()) { + clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, additionalReqParams.get(paramName)); + } + } } private Response buildAuthorizationCodeAuthorizationResponse() { @@ -292,7 +363,4 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase { return processor.authenticate(); } - - - }