Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.networknt.oauth.token.handler.Oauth2TokenPostHandler Maven / Gradle / Ivy
package com.networknt.oauth.token.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hazelcast.map.IMap;
import com.networknt.config.Config;
import com.networknt.handler.LightHttpHandler;
import com.networknt.oauth.auth.Authenticator;
import com.networknt.oauth.auth.DefaultAuth;
import com.networknt.oauth.cache.CacheStartupHookProvider;
import com.networknt.oauth.cache.OAuth2Constants;
import com.networknt.oauth.cache.model.*;
import com.networknt.oauth.security.LightPasswordCredential;
import com.networknt.oauth.token.helper.HttpAuth;
import com.networknt.security.JwtConfig;
import com.networknt.security.JwtIssuer;
import com.networknt.service.SingletonServiceFactory;
import com.networknt.status.Status;
import com.networknt.exception.ApiException;
import com.networknt.utility.CodeVerifierUtil;
import com.networknt.utility.HashUtil;
import com.networknt.utility.Util;
import io.undertow.security.idm.Account;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormParserFactory;
import io.undertow.util.Headers;
import org.jose4j.jwt.JwtClaims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.regex.Matcher;
/**
* This handler will issue the token based on the information about the client and user. The content
* of the token will be depending on the grant type. Also, the format of the token is depending on
* the client type. For public client type, the token is by reference token, other client type will
* issue the JWT token directly. There is an endpoint in this service to dereference token once the
* request comes into the internal network.
*
* We also introduce a new client type called trusted and limited other grant types except authorization
* code and client credentials to be used.
*
* @author Steve Hu
*
*/
public class Oauth2TokenPostHandler extends TokenAuditHandler implements LightHttpHandler {
private static final Logger logger = LoggerFactory.getLogger(Oauth2TokenPostHandler.class);
public static final String CLIENT_TYPE_TRUSTED = "trusted";
public static final String CLIENT_TYPE_EXTERNAL = "external";
public static final int TEN_YEAR_IN_SECOND = 315360000;
private static final String UNABLE_TO_PARSE_FORM_DATA = "ERR12000";
private static final String UNSUPPORTED_GRANT_TYPE = "ERR12001";
private static final String MISSING_AUTHORIZATION_HEADER = "ERR12002";
private static final String INVALID_AUTHORIZATION_HEADER = "ERR12003";
private static final String INVALID_BASIC_CREDENTIALS = "ERR12004";
private static final String JSON_PROCESSING_EXCEPTION = "ERR12005";
private static final String CLIENT_NOT_FOUND = "ERR12014";
private static final String UNAUTHORIZED_CLIENT = "ERR12007";
private static final String INVALID_AUTHORIZATION_CODE = "ERR12008";
private static final String GENERIC_EXCEPTION = "ERR10014";
private static final String RUNTIME_EXCEPTION = "ERR10010";
private static final String USERNAME_REQUIRED = "ERR12022";
private static final String PASSWORD_REQUIRED = "ERR12023";
private static final String INCORRECT_PASSWORD = "ERR12016";
private static final String NOT_TRUSTED_CLIENT = "ERR12024";
private static final String MISSING_REDIRECT_URI = "ERR12025";
private static final String MISMATCH_REDIRECT_URI = "ERR12026";
private static final String MISMATCH_SCOPE = "ERR12027";
private static final String MISMATCH_CLIENT_ID = "ERR12028";
private static final String REFRESH_TOKEN_NOT_FOUND = "ERR12029";
private static final String USER_ID_REQUIRED_FOR_CLIENT_AUTHENTICATED_USER_GRANT_TYPE = "ERR12031";
private static final String USER_TYPE_REQUIRED_FOR_CLIENT_AUTHENTICATED_USER_GRANT_TYPE = "ERR12032";
private static final String INVALID_CODE_VERIFIER = "ERR12037";
private static final String CODE_VERIFIER_TOO_SHORT = "ERR12038";
private static final String CODE_VERIFIER_TOO_LONG = "ERR12039";
private static final String CODE_VERIFIER_MISSING = "ERR12040";
private static final String CODE_VERIFIER_FAILED = "ERR12041";
private static final String INVALID_CODE_CHALLENGE_METHOD = "ERR12033";
private static final String CLIENT_AUTHENTICATE_CLASS_NOT_FOUND = "ERR10043";
private static final String AUTHORIZATION_CODE_NOT_FOUND = "ERR12052";
static JwtConfig config = (JwtConfig)Config.getInstance().getJsonObjectConfig("jwt", JwtConfig.class);
private final static OauthTokenConfig oauthTokenConfig = (OauthTokenConfig) Config.getInstance().getJsonObjectConfig(OauthTokenConfig.CONFIG_NAME, OauthTokenConfig.class);
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
ObjectMapper mapper = Config.getInstance().getMapper();
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
Map formMap = new HashMap<>();
final FormParserFactory parserFactory = FormParserFactory.builder().build();
final FormDataParser parser = parserFactory.createParser(exchange);
try {
FormData data = parser.parseBlocking();
for (String fd : data) {
for (FormData.FormValue val : data.get(fd)) {
//logger.debug("fd = " + fd + " value = " + val.getValue());
formMap.put(fd, val.getValue());
}
}
} catch (Exception e) {
logger.error("Exception:", e);
setExchangeStatus(exchange, UNABLE_TO_PARSE_FORM_DATA, e.getMessage());
processAudit(exchange);
return;
}
try {
String grantType = (String)formMap.remove("grant_type");
if("client_credentials".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleClientCredentials(exchange, formMap)));
} else if("authorization_code".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleAuthorizationCode(exchange, formMap)));
} else if("password".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handlePassword(exchange, formMap)));
} else if("refresh_token".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleRefreshToken(exchange, formMap)));
} else if("client_authenticated_user".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleClientAuthenticatedUser(exchange, formMap)));
} else if("bootstrap_token".equals(grantType)) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleBootstrapToken(exchange, formMap)));
} else {
setExchangeStatus(exchange, UNSUPPORTED_GRANT_TYPE, grantType);
}
} catch (JsonProcessingException e) {
logger.error("JsonProcessingException:", e);
setExchangeStatus(exchange, JSON_PROCESSING_EXCEPTION, e.getMessage());
} catch (ApiException e) {
logger.error("ApiException", e);
exchange.setStatusCode(e.getStatus().getStatusCode());
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
exchange.getResponseSender().send(e.getStatus().toString());
}
processAudit(exchange);
}
@SuppressWarnings("unchecked")
private Map handleBootstrapToken(HttpServerExchange exchange, Map formMap) throws ApiException {
String scope = (String)formMap.remove("scope");
if(logger.isDebugEnabled()) logger.debug("scope = " + scope);
// validate the client_id and client_secret against the configuration.
String clientId = authenticateBootstrapClient(exchange, formMap);
if(clientId != null) {
if(logger.isDebugEnabled()) logger.debug("Passed client_id and client_secret validation.");
// check if the scope is matched from the input to the scope in the config.
if(scope == null) {
scope = oauthTokenConfig.getBootstrapScope();
} else {
// make sure scope is the same as the scope in the config.
if(!matchScope(scope, oauthTokenConfig.getBootstrapScope())) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, oauthTokenConfig.getBootstrapScope()));
}
}
// generate long-lived jwt token.
if(logger.isDebugEnabled()) logger.debug("scope is matched.");
String jwt;
try {
// since we have removed scope, client_id and client_secret from the formMap, the rest of properties are custom claims.
jwt = JwtIssuer.getJwt(mockBsClaims(clientId, scope, formMap));
} catch (Exception e) {
logger.error("Exception:", e);
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", TEN_YEAR_IN_SECOND);
return resMap;
} else {
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
}
@SuppressWarnings("unchecked")
private Map handleClientCredentials(HttpServerExchange exchange, Map formMap) throws ApiException {
String scope = (String)formMap.get("scope");
if(logger.isDebugEnabled()) logger.debug("scope = " + scope);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
if(scope == null) {
scope = client.getScope();
} else {
// make sure scope is in scope defined in client.
if(!matchScope(scope, client.getScope())) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, client.getScope()));
}
}
String jwt;
Map customMap = null;
try {
// assume that the custom_claim is in format of json map string.
String customClaim = client.getCustomClaim();
if(customClaim != null && customClaim.length() > 0) {
customMap = Config.getInstance().getMapper().readValue(customClaim, new TypeReference>(){});
}
jwt = JwtIssuer.getJwt(mockCcClaims(client.getClientId(), scope, customMap));
} catch (Exception e) {
logger.error("Exception:", e);
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
// if the client type is external, save the jwt to reference map and send the reference
if(Client.ClientTypeEnum.EXTERNAL == client.getClientType()) {
jwt = jwtReference(jwt, client.getDerefClientId());
}
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", config.getExpiredInMinutes()*60);
return resMap;
}
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
@SuppressWarnings("unchecked")
private Map handleAuthorizationCode(HttpServerExchange exchange, Map formMap) throws ApiException {
String code = (String)formMap.get("code");
String redirectUri = (String)formMap.get("redirect_uri");
String csrf = (String)formMap.get("csrf");
if(logger.isDebugEnabled()) logger.debug("code = " + code + " redirectUri = " + redirectUri);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// authorization code can only be used once for security reason.
Map codeMap = (Map)CacheStartupHookProvider.hz.getMap("codes").remove(code);
if(codeMap == null) {
// In most cases, it happens with curl tutorial or incorrect implementation in code
throw new ApiException(new Status(AUTHORIZATION_CODE_NOT_FOUND, code));
}
String userId = codeMap.get("userId");
// get userType and roles from code map as there are some authenticators doesn't support user_profile table.
String userType = codeMap.get("userType");
String roles = codeMap.get("roles");
String uri = codeMap.get("redirectUri");
String scope = codeMap.get("scope");
String remember = codeMap.get("remember");
if(logger.isDebugEnabled()) logger.debug("variable from codeMap cache userId = " + userId + " redirectUri = " + redirectUri + " scope = " + scope + " userType = " + userType + " roles = " + roles + " remember = " + remember);
// PKCE
String codeChallenge = codeMap.get(OAuth2Constants.CODE_CHALLENGE);
String codeChallengeMethod = codeMap.get(OAuth2Constants.CODE_CHALLENGE_METHOD);
String codeVerifier = (String)formMap.get(OAuth2Constants.CODE_VERIFIER);
if(userId != null) {
// if uri is not null, redirectUri must not be null and must be identical.
if(uri != null) {
if(redirectUri == null) {
throw new ApiException(new Status(MISSING_REDIRECT_URI, uri));
} else {
if(!uri.equals(redirectUri)) {
throw new ApiException(new Status(MISMATCH_REDIRECT_URI, redirectUri, uri));
}
}
}
// no passed in scope, take the client scope, otherwise, validate the passed in scope
if(scope == null) {
scope = client.getScope();
} else {
// make sure scope is in scope defined in client.
if(!matchScope(scope, client.getScope())) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, client.getScope()));
}
}
// PKCE code verifier check against code challenge
if (codeChallenge != null && codeChallengeMethod != null) {
// based on whether code_challenge has been stored at corresponding authorization code request previously
// decide whether this client(RP) supports PKCE
if(codeVerifier == null || codeVerifier.trim().length() == 0) {
throw new ApiException(new Status(CODE_VERIFIER_MISSING));
}
if(codeVerifier.length() < CodeVerifierUtil.MIN_CODE_VERIFIER_LENGTH) {
throw new ApiException(new Status(CODE_VERIFIER_TOO_SHORT, codeVerifier));
}
if(codeVerifier.length() > CodeVerifierUtil.MAX_CODE_VERIFIER_LENGTH) {
throw new ApiException(new Status(CODE_VERIFIER_TOO_LONG, codeVerifier));
}
Matcher m = CodeVerifierUtil.VALID_CODE_CHALLENGE_PATTERN.matcher(codeVerifier);
if(!m.matches()) {
throw new ApiException(new Status(INVALID_CODE_VERIFIER, codeVerifier));
}
// https://tools.ietf.org/html/rfc7636#section-4.2
// plain or S256
if (codeChallengeMethod.equals(CodeVerifierUtil.CODE_CHALLENGE_METHOD_S256)) {
String s = CodeVerifierUtil.deriveCodeVerifierChallenge(codeVerifier);
if(!codeChallenge.equals(s)) {
throw new ApiException(new Status(CODE_VERIFIER_FAILED));
}
} else if(codeChallengeMethod.equals(CodeVerifierUtil.CODE_CHALLENGE_METHOD_PLAIN)){
if(!codeChallenge.equals(codeVerifier)) {
throw new ApiException(new Status(CODE_VERIFIER_FAILED));
}
} else {
throw new ApiException(new Status(INVALID_CODE_CHALLENGE_METHOD, codeChallengeMethod));
}
}
String jwt;
Map customMap = null;
try {
// assume that the custom_claim is in format of json map string.
String customClaim = client.getCustomClaim();
if(customClaim != null && customClaim.length() > 0) {
customMap = Config.getInstance().getMapper().readValue(customClaim, new TypeReference>(){});
}
jwt = JwtIssuer.getJwt(mockAcClaims(client.getClientId(), scope, userId, userType, roles, csrf, customMap));
} catch (Exception e) {
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
// generate a refresh token and associate it with userId and clientId
String refreshToken = UUID.randomUUID().toString();
RefreshToken token = new RefreshToken();
token.setRefreshToken(refreshToken);
token.setUserId(userId);
token.setUserType(userType);
token.setRoles(roles);
token.setClientId(client.getClientId());
token.setScope(scope);
token.setRemember(remember != null && remember.equals("Y") ? "Y" : "N");
IMap tokens = CacheStartupHookProvider.hz.getMap("tokens");
tokens.set(refreshToken, token);
// if the client type is external, save the jwt to reference map and send the reference
if(Client.ClientTypeEnum.EXTERNAL == client.getClientType()) {
jwt = jwtReference(jwt, client.getDerefClientId());
}
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", config.getExpiredInMinutes()*60);
resMap.put("refresh_token", refreshToken);
if(remember != null) resMap.put("remember", remember); // StatelessAuthHandler will set refresh_token cookie to 90 days if it is 'Y'
return resMap;
} else {
throw new ApiException(new Status(INVALID_AUTHORIZATION_CODE, code));
}
}
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
@SuppressWarnings("unchecked")
private Map handlePassword(HttpServerExchange exchange, Map formMap) throws ApiException {
String userId = (String)formMap.get("username");
String scope = (String)formMap.get("scope");
String userType = (String)formMap.get("user_type");
String roles = (String)formMap.get("roles");
if(logger.isDebugEnabled()) logger.debug("userId = " + userId + " scope = " + scope);
char[] password = null;
if(formMap.get("password") != null) {
password = ((String)formMap.get("password")).toCharArray();
}
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// authenticate user with credentials
if(userId != null) {
if(password != null) {
// make sure that the client is trusted.
if(client.getClientType() == Client.ClientTypeEnum.TRUSTED) {
if (scope == null) {
scope = client.getScope(); // use the default scope defined in client if scope is not passed in
} else {
// make sure scope is in scope defined in client.
if (!matchScope(scope, client.getScope())) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, client.getScope()));
}
}
// authenticate user with different authenticators.
String clientAuthClass = client.getAuthenticateClass();
Class clazz = DefaultAuth.class;
if(clientAuthClass != null && clientAuthClass.trim().length() > 0) {
try {
clazz = Class.forName(clientAuthClass);
} catch (ClassNotFoundException e) {
logger.error("Authenticate Class " + clientAuthClass + " not found.", e);
throw new ApiException(new Status(CLIENT_AUTHENTICATE_CLASS_NOT_FOUND, clientAuthClass));
}
}
Authenticator authenticator = SingletonServiceFactory.getBean(Authenticator.class, clazz);
Account account = authenticator.authenticate(userId, new LightPasswordCredential(password, clientAuthClass, userType, exchange));
if(account == null) {
throw new ApiException(new Status(INCORRECT_PASSWORD));
} else {
try {
Map customMap = null;
// assume that the custom_claim is in format of json map string.
String customClaim = client.getCustomClaim();
if(customClaim != null && customClaim.length() > 0) {
customMap = Config.getInstance().getMapper().readValue(customClaim, new TypeReference>(){});
}
String jwt = JwtIssuer.getJwt(mockAcClaims(client.getClientId(), scope, userId, userType, roles, null, customMap));
// generate a refresh token and associate it with userId and clientId
String refreshToken = UUID.randomUUID().toString();
RefreshToken token = new RefreshToken();
token.setRefreshToken(refreshToken);
token.setUserId(userId);
token.setClientId(client.getClientId());
token.setScope(scope);
token.setRemember("N"); // set this to N by default for password
IMap tokens = CacheStartupHookProvider.hz.getMap("tokens");
tokens.set(refreshToken, token);
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", config.getExpiredInMinutes()*60);
resMap.put("refresh_token", refreshToken);
return resMap;
} catch (Exception e) {
logger.error("Exception:", e);
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
}
} else {
throw new ApiException(new Status(NOT_TRUSTED_CLIENT));
}
} else {
throw new ApiException(new Status(PASSWORD_REQUIRED));
}
} else {
throw new ApiException(new Status(USERNAME_REQUIRED));
}
}
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
@SuppressWarnings("unchecked")
private Map handleRefreshToken(HttpServerExchange exchange, Map formMap) throws ApiException {
String refreshToken = (String)formMap.get("refresh_token");
String scope = (String) formMap.get("scope");
// Get csrf token from the input. every time a new token is generated, a new csrf token will be used.
String csrf = (String)formMap.get("csrf");
if(logger.isDebugEnabled()) logger.debug("refreshToken = " + refreshToken + " scope = " + scope);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// make sure that the refresh token can be found and client_id matches.
IMap tokens = CacheStartupHookProvider.hz.getMap("tokens");
RefreshToken token = tokens.remove(refreshToken);
if(token != null) {
String userId = token.getUserId();
String userType = token.getUserType();
String roles = token.getRoles();
String clientId = token.getClientId();
String oldScope = token.getScope();
String remember = token.getRemember();
if(client.getClientId().equals(clientId)) {
if(scope == null) {
scope = oldScope; // use the previous scope when access token is generated
} else {
// make sure scope is the same as oldScope or contained in oldScope.
if(!matchScope(scope, oldScope)) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, oldScope));
}
}
String jwt;
Map customMap = null;
// assume that the custom_claim is in format of json map string.
String customClaim = client.getCustomClaim();
try {
if(customClaim != null && customClaim.length() > 0) {
customMap = Config.getInstance().getMapper().readValue(customClaim, new TypeReference>(){});
}
jwt = JwtIssuer.getJwt(mockAcClaims(client.getClientId(), scope, userId, userType, roles, csrf, customMap));
} catch (Exception e) {
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
// generate a new refresh token and associate it with userId and clientId
String newRefreshToken = UUID.randomUUID().toString();
RefreshToken newToken = new RefreshToken();
newToken.setRefreshToken(newRefreshToken);
newToken.setUserId(userId);
newToken.setUserType(userType);
newToken.setRoles(roles);
newToken.setClientId(client.getClientId());
newToken.setScope(scope);
newToken.setRemember(remember);
tokens.put(newRefreshToken, newToken);
// if the client type is external, save the jwt to reference map and send the reference
if(Client.ClientTypeEnum.EXTERNAL == client.getClientType()) {
jwt = jwtReference(jwt, client.getDerefClientId());
}
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", config.getExpiredInMinutes()*60);
resMap.put("refresh_token", newRefreshToken);
resMap.put("remember", remember);
return resMap;
} else {
// mismatched client id
throw new ApiException(new Status(MISMATCH_CLIENT_ID, client.getClientId(), clientId));
}
} else {
// refresh token cannot be found.
throw new ApiException(new Status(REFRESH_TOKEN_NOT_FOUND, refreshToken));
}
}
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
/**
* This grant type is custom grant type that assume client has already authenticated the user and only send the user info
* to the authorization server to get the access token. The token is similar with authorization code token. All extra info
* from the formMap will be put into the token as custom claim.
*
* Also, only
*
* @param exchange
* @param formMap
* @return
* @throws ApiException
*/
@SuppressWarnings("unchecked")
private Map handleClientAuthenticatedUser(HttpServerExchange exchange, Map formMap) throws ApiException {
if(logger.isDebugEnabled()) logger.debug("client authenticated user grant formMap = " + formMap);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// make sure that client is trusted
if(Client.ClientTypeEnum.TRUSTED == client.getClientType()) {
String scope = (String)formMap.remove("scope");
if(scope == null) {
scope = client.getScope(); // use the default scope defined in client if scope is not passed in
} else {
// make sure scope is in scope defined in client.
if(!matchScope(scope, client.getScope())) {
throw new ApiException(new Status(MISMATCH_SCOPE, scope, client.getScope()));
}
}
// make sure that userId and userType are passed in the formMap.
String userId = (String)formMap.remove("userId");
if(userId == null) {
throw new ApiException(new Status(USER_ID_REQUIRED_FOR_CLIENT_AUTHENTICATED_USER_GRANT_TYPE));
}
String userType = (String)formMap.remove("userType");
if(userType == null) {
throw new ApiException(new Status(USER_TYPE_REQUIRED_FOR_CLIENT_AUTHENTICATED_USER_GRANT_TYPE));
}
String roles = (String)formMap.remove("roles");
String jwt;
try {
jwt = JwtIssuer.getJwt(mockAcClaims(client.getClientId(), scope, userId, userType, roles, null, formMap));
} catch (Exception e) {
throw new ApiException(new Status(GENERIC_EXCEPTION, e.getMessage()));
}
// generate a refresh token and associate it with userId and clientId
String refreshToken = UUID.randomUUID().toString();
RefreshToken token = new RefreshToken();
token.setRefreshToken(refreshToken);
token.setUserId(userId);
token.setUserType(userType);
token.setRoles(roles);
token.setClientId(client.getClientId());
token.setScope(scope);
token.setRemember("N"); // default to N
IMap tokens = CacheStartupHookProvider.hz.getMap("tokens");
tokens.set(refreshToken, token);
Map resMap = new HashMap<>();
resMap.put("access_token", jwt);
resMap.put("token_type", "bearer");
resMap.put("expires_in", config.getExpiredInMinutes()*60);
resMap.put("refresh_token", refreshToken);
return resMap;
} else {
// not trusted client, this is not allowed.
throw new ApiException(new Status(NOT_TRUSTED_CLIENT));
}
}
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}
private Client authenticateClient(HttpServerExchange exchange, Map formMap) throws ApiException {
HttpAuth httpAuth = new HttpAuth(exchange);
String clientId;
String clientSecret;
if(!httpAuth.isHeaderAvailable()) {
clientId = (String)formMap.remove("client_id");
clientSecret = (String)formMap.remove("client_secret");
} else {
clientId = httpAuth.getClientId();
clientSecret = httpAuth.getClientSecret();
}
if(clientId == null || clientId.trim().isEmpty() || clientSecret == null || clientSecret.trim().isEmpty()) {
if(!httpAuth.isHeaderAvailable()) {
throw new ApiException(new Status(MISSING_AUTHORIZATION_HEADER));
} else if(httpAuth.isInvalidCredentials()) {
throw new ApiException(new Status(INVALID_BASIC_CREDENTIALS, httpAuth.getCredentials()));
} else {
throw new ApiException(new Status(INVALID_AUTHORIZATION_HEADER, httpAuth.getAuth()));
}
}
return validateClientSecret(clientId, clientSecret);
}
private Client validateClientSecret(String clientId, String clientSecret) throws ApiException {
IMap clients = CacheStartupHookProvider.hz.getMap("clients");
Client client = clients.get(clientId);
if(client == null) {
throw new ApiException(new Status(CLIENT_NOT_FOUND, clientId));
} else {
try {
if(HashUtil.validatePassword(clientSecret.toCharArray(), client.getClientSecret())) {
return client;
} else {
throw new ApiException(new Status(UNAUTHORIZED_CLIENT));
}
} catch ( NoSuchAlgorithmException | InvalidKeySpecException e) {
logger.error("Exception:", e);
throw new ApiException(new Status(RUNTIME_EXCEPTION));
}
}
}
private String authenticateBootstrapClient(HttpServerExchange exchange, Map formMap) throws ApiException {
HttpAuth httpAuth = new HttpAuth(exchange);
String clientId;
String clientSecret;
if(!httpAuth.isHeaderAvailable()) {
clientId = (String)formMap.remove("client_id");
clientSecret = (String)formMap.remove("client_secret");
} else {
clientId = httpAuth.getClientId();
clientSecret = httpAuth.getClientSecret();
}
if(clientId == null || clientId.trim().isEmpty() || clientSecret == null || clientSecret.trim().isEmpty()) {
if(!httpAuth.isHeaderAvailable()) {
throw new ApiException(new Status(MISSING_AUTHORIZATION_HEADER));
} else if(httpAuth.isInvalidCredentials()) {
throw new ApiException(new Status(INVALID_BASIC_CREDENTIALS, httpAuth.getCredentials()));
} else {
throw new ApiException(new Status(INVALID_AUTHORIZATION_HEADER, httpAuth.getAuth()));
}
}
return validateBootstrapClientSecret(clientId, clientSecret);
}
private String validateBootstrapClientSecret(String clientId, String clientSecret) throws ApiException {
try {
if(oauthTokenConfig.getBootstrapClientId().equals(clientId) && HashUtil.validatePassword(clientSecret.toCharArray(), oauthTokenConfig.getBootstrapClientSecret())) {
return clientId;
}
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
logger.error("Exception:", e);
throw new ApiException(new Status(RUNTIME_EXCEPTION));
}
return null;
}
private JwtClaims mockCcClaims(String clientId, String scopeString, Map formMap) {
JwtClaims claims = JwtIssuer.getDefaultJwtClaims();
claims.setClaim("client_id", clientId);
List scope = Arrays.asList(scopeString.split("\\s+"));
claims.setStringListClaim("scope", scope); // multi-valued claims work too and will end up as a JSON array
if(formMap != null) {
for(Map.Entry entry : formMap.entrySet()) {
claims.setClaim(entry.getKey(), entry.getValue());
}
}
return claims;
}
private JwtClaims mockBsClaims(String clientId, String scopeString, Map formMap) {
JwtClaims claims = JwtIssuer.getJwtClaimsWithExpiresIn(TEN_YEAR_IN_SECOND); // 10 years in seconds
claims.setClaim("client_id", clientId);
List scope = Arrays.asList(scopeString.split("\\s+"));
claims.setStringListClaim("scope", scope); // multi-valued claims work too and will end up as a JSON array
if(formMap != null) {
for(Map.Entry entry : formMap.entrySet()) {
claims.setClaim(entry.getKey(), entry.getValue());
}
}
return claims;
}
private JwtClaims mockAcClaims(String clientId, String scopeString, String userId, String userType, String roles, String csrf, Map formMap) {
JwtClaims claims = JwtIssuer.getDefaultJwtClaims();
claims.setClaim("user_id", userId);
claims.setClaim("user_type", userType);
claims.setClaim("client_id", clientId);
if(csrf != null) claims.setClaim("csrf", csrf);
if(scopeString != null && scopeString.trim().length() > 0) {
List scope = Arrays.asList(scopeString.split("\\s+"));
claims.setStringListClaim("scope", scope); // multi-valued claims work too and will end up as a JSON array
}
if(roles != null && roles.trim().length() > 0) {
claims.setClaim("roles", roles);
}
if(formMap != null) {
for(Map.Entry entry : formMap.entrySet()) {
claims.setClaim(entry.getKey(), entry.getValue());
}
}
return claims;
}
private static boolean matchScope(String s1, String s2) {
boolean matched = true;
if(s1 == null || s2 == null) {
matched = false;
} else {
if(!s1.equals(s2)) {
String[] split = s1.split("\\s+");
for (String aSplit : split) {
if (!s2.contains(aSplit)) {
matched = false;
break;
}
}
}
}
return matched;
}
private String jwtReference(String jwt, String clientId) {
String uuid = Util.getUUID();
Map referenceMap = new HashMap<>();
referenceMap.put("jwt", jwt);
if(clientId != null) {
referenceMap.put("clientId", clientId);
}
CacheStartupHookProvider.hz.getMap("references").set(uuid, referenceMap);
return uuid;
}
}