/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.oidc.endpoints.request;
import org.jboss.logging.Logger;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
abstract class AuthzEndpointRequestParser {
private static final Logger logger = Logger.getLogger(AuthzEndpointRequestParser.class);
/**
* Max number of additional req params copied into client session note to prevent DoS attacks
*
*/
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
*
*/
public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200;
/** Set of known protocol GET params not to be stored into additionalReqParams} */
private static final Set<String> 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);
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
// https://tools.ietf.org/html/rfc7636#section-6.1
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM);
}
public void parseRequest(AuthorizationEndpointRequest request) {
String clientId = getParameter(OIDCLoginProtocol.CLIENT_ID_PARAM);
if (request.clientId != null && !request.clientId.equals(clientId)) {
throw new IllegalArgumentException("The client_id parameter doesn't match the one from OIDC 'request' or 'request_uri'");
}
request.clientId = clientId;
request.responseType = replaceIfNotNull(request.responseType, getParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM));
request.responseMode = replaceIfNotNull(request.responseMode, getParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM));
request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM));
request.state = replaceIfNotNull(request.state, getParameter(OIDCLoginProtocol.STATE_PARAM));
request.scope = replaceIfNotNull(request.scope, getParameter(OIDCLoginProtocol.SCOPE_PARAM));
request.loginHint = replaceIfNotNull(request.loginHint, getParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM));
request.prompt = replaceIfNotNull(request.prompt, getParameter(OIDCLoginProtocol.PROMPT_PARAM));
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));
// https://tools.ietf.org/html/rfc7636#section-6.1
request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
request.codeChallengeMethod = replaceIfNotNull(request.codeChallengeMethod, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM));
extractAdditionalReqParams(request.additionalReqParams);
}
protected void extractAdditionalReqParams(Map<String, String> additionalReqParams) {
for (String paramName : keySet()) {
if (!KNOWN_REQ_PARAMS.contains(paramName)) {
String value = getParameter(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);
}
}
}
}
protected <T> T replaceIfNotNull(T previousVal, T newVal) {
return newVal==null ? previousVal : newVal;
}
protected abstract String getParameter(String paramName);
protected abstract Integer getIntParameter(String paramName);
protected abstract Set<String> keySet();
}