All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.networknt.oauth.code.handler.Oauth2CodeGetHandler Maven / Gradle / Ivy

There is a newer version: 2.1.30
Show newest version
package com.networknt.oauth.code.handler;

import com.hazelcast.map.IMap;
import com.networknt.handler.LightHttpHandler;
import com.networknt.oauth.cache.CacheStartupHookProvider;
import com.networknt.oauth.cache.OAuth2Constants;
import com.networknt.oauth.cache.model.Client;
import com.networknt.utility.CodeVerifierUtil;
import com.networknt.utility.Util;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;
import io.undertow.util.StatusCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;

/**
 * This handler is for get request, the credentials might in header or just query parameters.
 * Need to check client info in order to find out which class to handle the authentication
 * clients are cached so that it has better performance. If client_id cannot be found in cache,
 * go to db to get it. It must be something added recently and not in cache yet.
 *
 */
public class Oauth2CodeGetHandler extends CodeAuditHandler implements LightHttpHandler {
    static final Logger logger = LoggerFactory.getLogger(Oauth2CodeGetHandler.class);
    static final String CLIENT_NOT_FOUND = "ERR12014";
    static final String INVALID_CODE_CHALLENGE_METHOD = "ERR12033";
    static final String CODE_CHALLENGE_TOO_SHORT = "ERR12034";
    static final String CODE_CHALLENGE_TOO_LONG = "ERR12035";
    static final String INVALID_CODE_CHALLENGE_FORMAT = "ERR12036";

    @SuppressWarnings("unchecked")
    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        // parse all the parameters here as this is a redirected get request.
        Map params = new HashMap<>();
        Map> pnames = exchange.getQueryParameters();
        for (Map.Entry> entry : pnames.entrySet()) {
            String pname = entry.getKey();
            Iterator pvalues = entry.getValue().iterator();
            if(pvalues.hasNext()) {
                params.put(pname, pvalues.next());
            }
        }
        if(logger.isDebugEnabled()) logger.debug("params", params);
        String clientId = params.get("client_id");
        // check if the client_id is valid
        IMap clients = CacheStartupHookProvider.hz.getMap("clients");
        Client client = clients.get(clientId);
        if(client == null) {
            setExchangeStatus(exchange, CLIENT_NOT_FOUND, clientId);
            processAudit(exchange);
        } else {
            String code = Util.getUUID();
            final SecurityContext context = exchange.getSecurityContext();
            String userId = context.getAuthenticatedAccount().getPrincipal().getName();
            Set roles = context.getAuthenticatedAccount().getRoles();
            Map codeMap = new HashMap<>();
            codeMap.put("userId", userId);
            if(roles != null && !roles.isEmpty()) {
                codeMap.put("roles", String.join(" ", roles));
            }
            String scope = params.get("scope");
            if(scope != null) {
                codeMap.put("scope", scope);
            }
            String redirectUri = params.get("redirect_uri");
            if(redirectUri == null) {
                redirectUri = client.getRedirectUri();
            } else {
                codeMap.put("redirectUri", redirectUri);
            }
            // https://tools.ietf.org/html/rfc7636#section-4 PKCE
            String codeChallenge = params.get(OAuth2Constants.CODE_CHALLENGE);
            String codeChallengeMethod = params.get(OAuth2Constants.CODE_CHALLENGE_METHOD);
            if (codeChallenge == null) {
                // PKCE is not used by this client.
                // Do we need to force native client to use PKCE?
            } else {
                if(codeChallengeMethod != null) {
                    // https://tools.ietf.org/html/rfc7636#section-4.2
                    // plain or S256
                    if (!codeChallengeMethod.equals(CodeVerifierUtil.CODE_CHALLENGE_METHOD_S256) &&
                            !codeChallengeMethod.equals(CodeVerifierUtil.CODE_CHALLENGE_METHOD_PLAIN)) {
                        setExchangeStatus(exchange, INVALID_CODE_CHALLENGE_METHOD, codeChallengeMethod);
                        processAudit(exchange);
                        return;
                    }
                } else {
                    // https://tools.ietf.org/html/rfc7636#section-4.3
                    // default code_challenge_method is plain
                    codeChallengeMethod = CodeVerifierUtil.CODE_CHALLENGE_METHOD_PLAIN;
                }
                // validate codeChallenge.
                if(codeChallenge.length() < CodeVerifierUtil.MIN_CODE_VERIFIER_LENGTH) {
                    setExchangeStatus(exchange, CODE_CHALLENGE_TOO_SHORT, codeChallenge);
                    processAudit(exchange);
                    return;
                }
                if(codeChallenge.length() > CodeVerifierUtil.MAX_CODE_VERIFIER_LENGTH) {
                    setExchangeStatus(exchange, CODE_CHALLENGE_TOO_LONG, codeChallenge);
                    processAudit(exchange);
                    return;
                }
                // check the format
                Matcher m = CodeVerifierUtil.VALID_CODE_CHALLENGE_PATTERN.matcher(codeChallenge);
                if(!m.matches()) {
                    setExchangeStatus(exchange, INVALID_CODE_CHALLENGE_FORMAT, codeChallenge);
                    processAudit(exchange);
                    return;
                }
                // put the code challenge and method into the codes map.
                codeMap.put(OAuth2Constants.CODE_CHALLENGE, codeChallenge);
                codeMap.put(OAuth2Constants.CODE_CHALLENGE_METHOD, codeChallengeMethod);
            }

            CacheStartupHookProvider.hz.getMap("codes").set(code, codeMap);
            redirectUri = redirectUri + "?code=" + code;
            String state = params.get("state");
            if(state != null) {
                redirectUri = redirectUri + "&state=" + state;
            }
            if(logger.isDebugEnabled()) logger.debug("redirectUri = " + redirectUri);
            // now redirect here.
            exchange.setStatusCode(StatusCodes.FOUND);
            exchange.getResponseHeaders().put(Headers.LOCATION, redirectUri);
            exchange.endExchange();
            processAudit(exchange);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy