com.networknt.oauth.code.handler.Oauth2CodeGetHandler Maven / Gradle / Ivy
package com.networknt.oauth.code.handler;
import com.networknt.config.JsonMapper;
import com.networknt.handler.LightHttpHandler;
import com.networknt.monad.Result;
import com.networknt.oauth.cache.ClientUtil;
import com.networknt.oauth.cache.OAuth2Constants;
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");
String remember = params.get("remember");
Result resultClient = ClientUtil.getClientById(clientId);
if(resultClient.isFailure()) {
logger.error("failed to get the client: " + resultClient.getError());
setExchangeStatus(exchange, resultClient.getError());
processAudit(exchange);
return;
}
String client = resultClient.getResult();
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("authCode", code);
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);
}
Map clientMap = JsonMapper.string2Map(client);
codeMap.put("host", clientMap.get("host"));
String redirectUri = params.get("redirect_uri");
if(redirectUri == null) {
redirectUri = (String)clientMap.get("redirectUri");
if(logger.isDebugEnabled()) logger.debug("Get redirectUri from the client " + redirectUri);
}
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);
}
codeMap.put("remember", remember != null ? remember : "N");
ClientUtil.createAuthCode(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);
}
}
}