mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
Initial WIP Checkpoint of auth0 userSessions
This commit is contained in:
@ -84,7 +84,7 @@ public class QContext
|
||||
actionStackThreadLocal.get().add(actionInput);
|
||||
}
|
||||
|
||||
if(!qInstance.getHasBeenValidated())
|
||||
if(qInstance != null && !qInstance.getHasBeenValidated())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -60,7 +60,6 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
|
||||
private String auth0ClientSecretField;
|
||||
private Serializable qqqRecordIdField;
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
// fields on the accessToken table //
|
||||
/////////////////////////////////////
|
||||
@ -70,6 +69,14 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
|
||||
private String qqqApiKeyField;
|
||||
private String expiresInSecondsField;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// table for storing user sessions, and field names we work with on that table //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
private String userSessionTableName;
|
||||
private String userSessionUuidField;
|
||||
private String userSessionUserIdField;
|
||||
private String userSessionAccessTokenField;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -33,7 +33,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import com.auth0.client.auth.AuthAPI;
|
||||
import com.auth0.exception.Auth0Exception;
|
||||
import com.auth0.json.auth.TokenHolder;
|
||||
@ -52,6 +51,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
@ -68,11 +68,11 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.model.UserSession;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.SimpleStateKey;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
@ -80,7 +80,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
@ -93,6 +92,18 @@ import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ AuthenticationModule for working with Auth0.
|
||||
**
|
||||
** createSession can be called with the following fields in its context:
|
||||
**
|
||||
** System-User session use-case:
|
||||
** 1: Takes in an "accessToken" (but doesn't store a userSession record).
|
||||
**
|
||||
** Web User session use-cases:
|
||||
** 2: creates a new session (userSession record) by taking an "accessToken"
|
||||
** 3: looks up an existing session (userSession record) by taking a "sessionUUID"
|
||||
** 4: takes an "apiKey" (looked up in metaData.AccessTokenTableName - refreshing accessToken with auth0 if needed).
|
||||
** 5: takes a "basicAuthString" (encoded username:password), which make a new accessToken in auth0
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
@ -104,14 +115,17 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
public static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 1800;
|
||||
|
||||
public static final String AUTH0_ACCESS_TOKEN_KEY = "sessionId";
|
||||
public static final String API_KEY = "apiKey";
|
||||
public static final String BASIC_AUTH_KEY = "basicAuthString";
|
||||
public static final String ACCESS_TOKEN_KEY = "accessToken";
|
||||
public static final String API_KEY = "apiKey"; // todo - look for users of this, see if we can change to use this constant; maybe move constants up?
|
||||
public static final String SESSION_UUID_KEY = "sessionUUID";
|
||||
public static final String BASIC_AUTH_KEY = "basicAuthString"; // todo - look for users of this, see if we can change to use this constant; maybe move constants up?
|
||||
|
||||
public static final String TOKEN_NOT_PROVIDED_ERROR = "Access Token was not provided";
|
||||
public static final String COULD_NOT_DECODE_ERROR = "Unable to decode access token";
|
||||
public static final String EXPIRED_TOKEN_ERROR = "Token has expired";
|
||||
public static final String INVALID_TOKEN_ERROR = "An invalid token was provided";
|
||||
public static final String DO_STORE_USER_SESSION_KEY = "doStoreUserSession";
|
||||
|
||||
static final String TOKEN_NOT_PROVIDED_ERROR = "Access Token was not provided";
|
||||
static final String COULD_NOT_DECODE_ERROR = "Unable to decode access token";
|
||||
static final String EXPIRED_TOKEN_ERROR = "Token has expired";
|
||||
static final String INVALID_TOKEN_ERROR = "An invalid token was provided";
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -149,94 +163,105 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
@Override
|
||||
public QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// check if we are processing a Basic Auth Session first //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(context.containsKey(BASIC_AUTH_KEY))
|
||||
{
|
||||
AuthAPI auth = AuthAPI.newBuilder(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret()).build();
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// decode the credentials from the header auth //
|
||||
/////////////////////////////////////////////////
|
||||
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
|
||||
String accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials);
|
||||
context.put(AUTH0_ACCESS_TOKEN_KEY, accessToken);
|
||||
}
|
||||
catch(Auth0Exception e)
|
||||
{
|
||||
////////////////
|
||||
// ¯\_(ツ)_/¯ //
|
||||
////////////////
|
||||
String message = "Error handling basic authentication: " + e.getMessage();
|
||||
LOG.error(message, e);
|
||||
throw (new QAuthenticationException(message));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// get the jwt id or qqq translated token from the context object //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
String accessToken = context.get(AUTH0_ACCESS_TOKEN_KEY);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check to see if the session id is a UUID, if so, that means we need to look up the 'actual' token in the access_token table //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(accessToken != null && StringUtils.isUUID(accessToken))
|
||||
{
|
||||
accessToken = lookupActualAccessToken(metaData, accessToken);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// if access token is still null, look for an api key //
|
||||
////////////////////////////////////////////////////////
|
||||
if(accessToken == null)
|
||||
{
|
||||
String apiKey = context.get(API_KEY);
|
||||
if(apiKey != null)
|
||||
{
|
||||
accessToken = getAccessTokenFromApiKey(metaData, apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
if(accessToken == null)
|
||||
{
|
||||
LOG.warn(TOKEN_NOT_PROVIDED_ERROR);
|
||||
throw (new QAuthenticationException(TOKEN_NOT_PROVIDED_ERROR));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// decode the token locally to make sure it is valid and to look at when it expires //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the context contains an access token, then create a new session based on that token. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
String accessToken = null;
|
||||
if(CollectionUtils.containsKeyWithNonNullValue(context, ACCESS_TOKEN_KEY))
|
||||
{
|
||||
accessToken = context.get(ACCESS_TOKEN_KEY);
|
||||
QSession qSession = buildAndValidateSession(qInstance, accessToken);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// build & store userSession db record, if requested to do so //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.containsKeyWithNonNullValue(context, DO_STORE_USER_SESSION_KEY))
|
||||
{
|
||||
insertUserSession(qInstance, accessToken, qSession);
|
||||
}
|
||||
|
||||
return (qSession);
|
||||
}
|
||||
else if(CollectionUtils.containsKeyWithNonNullValue(context, BASIC_AUTH_KEY))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Process a basic auth (username:password) //
|
||||
// by getting an access token from auth0 (re-using from state provider if possible) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
AuthAPI auth = AuthAPI.newBuilder(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret()).build();
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// decode the credentials from the header auth //
|
||||
/////////////////////////////////////////////////
|
||||
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
|
||||
accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials);
|
||||
}
|
||||
catch(Auth0Exception e)
|
||||
{
|
||||
////////////////
|
||||
// ¯\_(ツ)_/¯ //
|
||||
////////////////
|
||||
String message = "Error handling basic authentication: " + e.getMessage();
|
||||
LOG.error(message, e);
|
||||
throw (new QAuthenticationException(message));
|
||||
}
|
||||
}
|
||||
else if(CollectionUtils.containsKeyWithNonNullValue(context, API_KEY))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// process an api key - looks up client application token (creating token if needed) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
String apiKey = context.get(API_KEY);
|
||||
if(apiKey != null)
|
||||
{
|
||||
accessToken = getAccessTokenFromApiKey(metaData, apiKey);
|
||||
}
|
||||
}
|
||||
else if(CollectionUtils.containsKeyWithNonNullValue(context, SESSION_UUID_KEY))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// process a sessionUUID - looks up userSession record - cannot create token this way. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
String sessionUUID = context.get(SESSION_UUID_KEY);
|
||||
if(sessionUUID != null)
|
||||
{
|
||||
accessToken = getAccessTokenFromSessionUUID(metaData, sessionUUID);
|
||||
}
|
||||
}
|
||||
|
||||
/* todo confirm this is deprecated
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check to see if the session id is a UUID, if so, that means we need to look up the 'actual' token in the access_token table //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(accessToken != null && StringUtils.isUUID(accessToken))
|
||||
{
|
||||
accessToken = lookupActualAccessToken(metaData, accessToken);
|
||||
}
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////
|
||||
// if token wasn't found by now, give up //
|
||||
///////////////////////////////////////////
|
||||
if(accessToken == null)
|
||||
{
|
||||
LOG.warn(TOKEN_NOT_PROVIDED_ERROR);
|
||||
throw (new QAuthenticationException(TOKEN_NOT_PROVIDED_ERROR));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// try to build session to see if still valid //
|
||||
// then call method to check more session validity //
|
||||
/////////////////////////////////////////////////////
|
||||
QSession qSession = buildQSessionFromToken(accessToken, qInstance);
|
||||
if(isSessionValid(qInstance, qSession))
|
||||
{
|
||||
return (qSession);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we make it here it means we have never validated this token or its been a long //
|
||||
// enough duration so we need to re-verify the token //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
qSession = revalidateTokenAndBuildSession(qInstance, accessToken);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// put now into state so we dont check until next interval passes //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
StateProviderInterface spi = getStateProvider();
|
||||
SimpleStateKey<String> key = new SimpleStateKey<>(qSession.getIdReference());
|
||||
spi.put(key, Instant.now());
|
||||
|
||||
return (qSession);
|
||||
return buildAndValidateSession(qInstance, accessToken);
|
||||
}
|
||||
catch(QAuthenticationException qae)
|
||||
{
|
||||
throw (qae);
|
||||
}
|
||||
catch(JWTDecodeException jde)
|
||||
{
|
||||
@ -272,6 +297,61 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Insert a session as a new record into userSession table
|
||||
*******************************************************************************/
|
||||
private void insertUserSession(QInstance qInstance, String accessToken, QSession qSession) throws QException
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
try
|
||||
{
|
||||
QContext.init(qInstance, null);
|
||||
QContext.setQSession(getChickenAndEggSession());
|
||||
|
||||
UserSession userSession = new UserSession()
|
||||
.withUuid(qSession.getUuid())
|
||||
.withUserId(qSession.getUser().getIdReference())
|
||||
.withAccessToken(accessToken);
|
||||
|
||||
new InsertAction().execute(new InsertInput(UserSession.TABLE_NAME).withRecordEntity(userSession));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSession buildAndValidateSession(QInstance qInstance, String accessToken) throws JwkException
|
||||
{
|
||||
QSession qSession = buildQSessionFromToken(accessToken, qInstance);
|
||||
if(isSessionValid(qInstance, qSession))
|
||||
{
|
||||
return (qSession);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we make it here it means we have never validated this token or it has been a long //
|
||||
// enough duration so we need to re-verify the token //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
qSession = revalidateTokenAndBuildSession(qInstance, accessToken);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// put now into state so we don't check until next interval passes //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
StateProviderInterface spi = getStateProvider();
|
||||
SimpleStateKey<String> key = new SimpleStateKey<>(qSession.getIdReference());
|
||||
spi.put(key, Instant.now());
|
||||
|
||||
return (qSession);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -299,7 +379,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
|
||||
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
|
||||
|
||||
String accessToken = getAccessTokenFromAuth0(metaData, auth, credentials);
|
||||
String accessToken = getAccessTokenForUsernameAndPasswordFromAuth0(metaData, auth, credentials);
|
||||
stateProvider.put(accessTokenStateKey, accessToken);
|
||||
stateProvider.put(timestampStateKey, Instant.now());
|
||||
return (accessToken);
|
||||
@ -310,7 +390,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected String getAccessTokenFromAuth0(Auth0AuthenticationMetaData metaData, AuthAPI auth, String credentials) throws Auth0Exception
|
||||
protected String getAccessTokenForUsernameAndPasswordFromAuth0(Auth0AuthenticationMetaData metaData, AuthAPI auth, String credentials) throws Auth0Exception
|
||||
{
|
||||
/////////////////////////////////////
|
||||
// call auth0 with a login request //
|
||||
@ -620,75 +700,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** create a new auth0 access token
|
||||
** make http request to Auth0 for a new access token for an application - e.g.,
|
||||
** with a clientId and clientSecret as params
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
QSession sessionBefore = QContext.getQSession();
|
||||
Auth0AuthenticationMetaData auth0MetaData = (Auth0AuthenticationMetaData) metaData;
|
||||
|
||||
try
|
||||
{
|
||||
QContext.setQSession(getChickenAndEggSession());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// fetch the application from database, will throw accesstokenexception if not found //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord clientAuth0Application = getClientAuth0Application(auth0MetaData, clientId);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// request access token from auth0 if exception is not thrown, that means 200OK, we want to //
|
||||
// store the actual access token in the database, and return a unique value //
|
||||
// back to the user which will be what they use on subseqeunt requests (because token too big) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JSONObject accessTokenData = requestAccessTokenFromAuth0(auth0MetaData, clientId, clientSecret);
|
||||
|
||||
Integer expiresInSeconds = accessTokenData.getInt("expires_in");
|
||||
String accessToken = accessTokenData.getString("access_token");
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
/////////////////////////////////
|
||||
// store the details in the db //
|
||||
/////////////////////////////////
|
||||
QRecord accessTokenRecord = new QRecord()
|
||||
.withValue(auth0MetaData.getClientAuth0ApplicationIdField(), clientAuth0Application.getValue("id"))
|
||||
.withValue(auth0MetaData.getAuth0AccessTokenField(), accessToken)
|
||||
.withValue(auth0MetaData.getQqqAccessTokenField(), uuid)
|
||||
.withValue(auth0MetaData.getExpiresInSecondsField(), expiresInSeconds);
|
||||
InsertInput input = new InsertInput();
|
||||
input.setTableName(auth0MetaData.getAccessTokenTableName());
|
||||
input.setRecords(List.of(accessTokenRecord));
|
||||
new InsertAction().execute(input);
|
||||
|
||||
//////////////////////////////////
|
||||
// update and send the response //
|
||||
//////////////////////////////////
|
||||
accessTokenData.put("access_token", uuid);
|
||||
accessTokenData.remove("scope");
|
||||
return (accessTokenData.toString());
|
||||
}
|
||||
catch(AccessTokenException ate)
|
||||
{
|
||||
throw (ate);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new AccessTokenException(e.getMessage(), e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.setQSession(sessionBefore);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** make http request to Auth0 for a new access token
|
||||
**
|
||||
*******************************************************************************/
|
||||
public JSONObject requestAccessTokenFromAuth0(Auth0AuthenticationMetaData auth0MetaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
public JSONObject requestAccessTokenForClientIdAndSecretFromAuth0(Auth0AuthenticationMetaData auth0MetaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// make a request to Auth0 using the client_id and client_secret //
|
||||
@ -776,6 +792,63 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look up access_token from session UUID
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getAccessTokenFromSessionUUID(Auth0AuthenticationMetaData metaData, String sessionUUID) throws QAuthenticationException
|
||||
{
|
||||
String accessToken = null;
|
||||
QSession beforeSession = QContext.getQSession();
|
||||
|
||||
try
|
||||
{
|
||||
QContext.setQSession(getChickenAndEggSession());
|
||||
|
||||
///////////////////////////////////////
|
||||
// query for the user session record //
|
||||
///////////////////////////////////////
|
||||
QRecord userSessionRecord = new GetAction().executeForRecord(new GetInput(UserSession.TABLE_NAME)
|
||||
.withUniqueKey(Map.of("uuid", sessionUUID))
|
||||
.withShouldMaskPasswords(false)
|
||||
.withShouldOmitHiddenFields(false));
|
||||
|
||||
if(userSessionRecord != null)
|
||||
{
|
||||
accessToken = userSessionRecord.getValueString("accessToken");
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// decode the accessToken and make sure it is not expired //
|
||||
////////////////////////////////////////////////////////////
|
||||
if(accessToken != null)
|
||||
{
|
||||
DecodedJWT jwt = JWT.decode(accessToken);
|
||||
if(jwt.getExpiresAtAsInstant().isBefore(Instant.now()))
|
||||
{
|
||||
throw (new QAuthenticationException("accessToken is expired"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QAuthenticationException qae)
|
||||
{
|
||||
throw (qae);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error looking up userSession by sessionUUID", e);
|
||||
throw (new QAuthenticationException("Error looking up userSession by sessionUUID", e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.setQSession(beforeSession);
|
||||
}
|
||||
|
||||
return (accessToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look up access_token from api key
|
||||
**
|
||||
@ -841,7 +914,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
// store the actual access token in the database, and return a unique value //
|
||||
// back to the user which will be what they use on subsequent requests (because token too big) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JSONObject accessTokenData = requestAccessTokenFromAuth0(metaData, clientId, clientSecret);
|
||||
JSONObject accessTokenData = requestAccessTokenForClientIdAndSecretFromAuth0(metaData, clientId, clientSecret);
|
||||
|
||||
Integer expiresInSeconds = accessTokenData.getInt("expires_in");
|
||||
accessToken = accessTokenData.getString("access_token");
|
||||
@ -869,28 +942,4 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
return (accessToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look up client_auth0_application record, return if found.
|
||||
**
|
||||
*******************************************************************************/
|
||||
QRecord getClientAuth0Application(Auth0AuthenticationMetaData metaData, String clientId) throws QException
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to look up existing auth0 application from database, insert one if not found //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(metaData.getClientAuth0ApplicationTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(metaData.getAuth0ClientIdField(), QCriteriaOperator.EQUALS, clientId)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
return (queryOutput.getRecords().get(0));
|
||||
}
|
||||
|
||||
throw (new AccessTokenException("This client has not been configured to use the API.", HttpStatus.SC_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.model.UserSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta Data Producer for UserSession
|
||||
*******************************************************************************/
|
||||
public class UserSessionMetaDataProducer extends MetaDataProducer<QTableMetaData>
|
||||
{
|
||||
private final String backendName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UserSessionMetaDataProducer(String backendName)
|
||||
{
|
||||
this.backendName = backendName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = new QTableMetaData()
|
||||
.withName(UserSession.TABLE_NAME)
|
||||
.withBackendName(backendName)
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("id")
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("uuid"))
|
||||
.withFieldsFromEntity(UserSession.class)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE));
|
||||
return tableMetaData;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.authentication.implementations.model;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for UserSession table
|
||||
*******************************************************************************/
|
||||
public class UserSession extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "userSession";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(isEditable = false, isHidden = true, maxLength = 40, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String uuid;
|
||||
|
||||
@QField(isEditable = false, isHidden = true)
|
||||
private String accessToken;
|
||||
|
||||
@QField(isEditable = false, maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String userId;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public UserSession()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public UserSession(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public UserSession withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public UserSession withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return (this.modifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public UserSession withModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for uuid
|
||||
*******************************************************************************/
|
||||
public String getUuid()
|
||||
{
|
||||
return (this.uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for uuid
|
||||
*******************************************************************************/
|
||||
public void setUuid(String uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for uuid
|
||||
*******************************************************************************/
|
||||
public UserSession withUuid(String uuid)
|
||||
{
|
||||
this.uuid = uuid;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for accessToken
|
||||
*******************************************************************************/
|
||||
public String getAccessToken()
|
||||
{
|
||||
return (this.accessToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for accessToken
|
||||
*******************************************************************************/
|
||||
public void setAccessToken(String accessToken)
|
||||
{
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for accessToken
|
||||
*******************************************************************************/
|
||||
public UserSession withAccessToken(String accessToken)
|
||||
{
|
||||
this.accessToken = accessToken;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userId
|
||||
*******************************************************************************/
|
||||
public String getUserId()
|
||||
{
|
||||
return (this.userId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userId
|
||||
*******************************************************************************/
|
||||
public void setUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userId
|
||||
*******************************************************************************/
|
||||
public UserSession withUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -663,4 +663,19 @@ public class CollectionUtils
|
||||
return (output);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <K> boolean containsKeyWithNonNullValue(Map<K, ?> map, K key)
|
||||
{
|
||||
if(map == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (map.containsKey(key) && map.get(key) != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ import com.kingsrook.qqq.backend.core.state.SimpleStateKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.AUTH0_ACCESS_TOKEN_KEY;
|
||||
import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.ACCESS_TOKEN_KEY;
|
||||
import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.BASIC_AUTH_KEY;
|
||||
import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.COULD_NOT_DECODE_ERROR;
|
||||
import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.EXPIRED_TOKEN_ERROR;
|
||||
@ -143,7 +143,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
public void testInvalidToken()
|
||||
{
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put(AUTH0_ACCESS_TOKEN_KEY, INVALID_TOKEN);
|
||||
context.put(ACCESS_TOKEN_KEY, INVALID_TOKEN);
|
||||
|
||||
try
|
||||
{
|
||||
@ -167,7 +167,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
public void testUndecodableToken()
|
||||
{
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put(AUTH0_ACCESS_TOKEN_KEY, UNDECODABLE_TOKEN);
|
||||
context.put(ACCESS_TOKEN_KEY, UNDECODABLE_TOKEN);
|
||||
|
||||
try
|
||||
{
|
||||
@ -191,7 +191,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
public void testProperlyFormattedButExpiredToken()
|
||||
{
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put(AUTH0_ACCESS_TOKEN_KEY, EXPIRED_TOKEN);
|
||||
context.put(ACCESS_TOKEN_KEY, EXPIRED_TOKEN);
|
||||
|
||||
try
|
||||
{
|
||||
@ -236,7 +236,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
public void testNullToken()
|
||||
{
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put(AUTH0_ACCESS_TOKEN_KEY, null);
|
||||
context.put(ACCESS_TOKEN_KEY, null);
|
||||
|
||||
try
|
||||
{
|
||||
@ -267,7 +267,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
auth0Spy.createSession(qInstance, context);
|
||||
auth0Spy.createSession(qInstance, context);
|
||||
auth0Spy.createSession(qInstance, context);
|
||||
verify(auth0Spy, times(1)).getAccessTokenFromAuth0(any(), any(), any());
|
||||
verify(auth0Spy, times(1)).getAccessTokenForUsernameAndPasswordFromAuth0(any(), any(), any());
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,7 +27,6 @@ import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -58,7 +57,6 @@ import com.kingsrook.qqq.api.model.openapi.HttpMethod;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
@ -75,15 +73,12 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -137,11 +132,6 @@ public class QJavalinApiHandler
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
/////////////////////////////
|
||||
// authentication endpoint //
|
||||
/////////////////////////////
|
||||
ApiBuilder.post("/api/oauth/token", QJavalinApiHandler::handleAuthorization);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// static endpoints to support rapidoc pages //
|
||||
///////////////////////////////////////////////
|
||||
@ -583,101 +573,6 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void handleAuthorization(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// clientId & clientSecret may either be provided as formParams, or in an Authorization: Basic header //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
String clientId;
|
||||
String clientSecret;
|
||||
String authorizationHeader = context.header("Authorization");
|
||||
if(authorizationHeader != null && authorizationHeader.startsWith("Basic "))
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] credDecoded = Base64.getDecoder().decode(authorizationHeader.replace("Basic ", ""));
|
||||
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
|
||||
String[] parts = credentials.split(":", 2);
|
||||
clientId = parts[0];
|
||||
clientSecret = parts[1];
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
context.status(HttpStatus.BAD_REQUEST_400);
|
||||
context.result("Could not parse client_id and client_secret from Basic Authorization header.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientId = context.formParam("client_id");
|
||||
if(clientId == null)
|
||||
{
|
||||
context.status(HttpStatus.BAD_REQUEST_400);
|
||||
context.result("'client_id' must be provided.");
|
||||
return;
|
||||
}
|
||||
clientSecret = context.formParam("client_secret");
|
||||
if(clientSecret == null)
|
||||
{
|
||||
context.status(HttpStatus.BAD_REQUEST_400);
|
||||
context.result("'client_secret' must be provided.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// get the auth0 authentication module from qInstance //
|
||||
////////////////////////////////////////////////////////
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make call to get access token data, if no exception thrown, assume 200 OK and return //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.init(qInstance, null); // hmm...
|
||||
String accessToken = authenticationModule.createAccessToken(metaData, clientId, clientSecret);
|
||||
context.status(HttpStatus.Code.OK.getCode());
|
||||
context.result(accessToken);
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
}
|
||||
catch(AccessTokenException aae)
|
||||
{
|
||||
LOG.info("Error getting api access token", aae, logPair("clientId", clientId));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if the exception has a status code, then return that code and message //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
if(aae.getStatusCode() != null)
|
||||
{
|
||||
context.status(aae.getStatusCode());
|
||||
context.result(aae.getMessage());
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// if no code, throw and handle like other exceptions //
|
||||
////////////////////////////////////////////////////////
|
||||
throw (aae);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -113,6 +113,7 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -148,10 +149,10 @@ public class QJavalinImplementation
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QJavalinImplementation.class);
|
||||
|
||||
public static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
public static final String SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
public static final String BASIC_AUTH_NAME = "basicAuthString";
|
||||
public static final String API_KEY_NAME = "apiKey";
|
||||
public static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
public static final String SESSION_ID_COOKIE_NAME = "sessionId";
|
||||
public static final String SESSION_UUID_COOKIE_NAME = "sessionUUID";
|
||||
public static final String API_KEY_NAME = "apiKey";
|
||||
|
||||
static QInstance qInstance;
|
||||
static QJavalinMetaData javalinMetaData;
|
||||
@ -329,6 +330,8 @@ public class QJavalinImplementation
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
post("/manageSession", QJavalinImplementation::manageSession);
|
||||
|
||||
/////////////////////
|
||||
// metadata routes //
|
||||
/////////////////////
|
||||
@ -400,6 +403,36 @@ public class QJavalinImplementation
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void manageSession(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Map<?, ?> map = context.bodyAsClass(Map.class);
|
||||
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
|
||||
Map<String, String> authContext = new HashMap<>();
|
||||
//? authContext.put("uuid", ValueUtils.getValueAsString(map.get("uuid")));
|
||||
authContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, ValueUtils.getValueAsString(map.get("accessToken")));
|
||||
authContext.put(Auth0AuthenticationModule.DO_STORE_USER_SESSION_KEY, "true");
|
||||
|
||||
QSession session = authenticationModule.createSession(qInstance, authContext);
|
||||
|
||||
context.cookie(SESSION_UUID_COOKIE_NAME, session.getUuid(), SESSION_COOKIE_AGE);
|
||||
context.result(JsonUtils.toJson(MapBuilder.of("uuid", session.getUuid())));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -442,10 +475,12 @@ public class QJavalinImplementation
|
||||
{
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
|
||||
String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME);
|
||||
// todo delete String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME);
|
||||
String sessionUuidCookieValue = context.cookie(Auth0AuthenticationModule.SESSION_UUID_KEY);
|
||||
String authorizationHeaderValue = context.header("Authorization");
|
||||
String apiKeyHeaderValue = context.header("x-api-key");
|
||||
|
||||
/* todo - change to sessionUUID.
|
||||
if(StringUtils.hasContent(sessionIdCookieValue))
|
||||
{
|
||||
////////////////////////////////////////
|
||||
@ -453,6 +488,11 @@ public class QJavalinImplementation
|
||||
////////////////////////////////////////
|
||||
authenticationContext.put(SESSION_ID_COOKIE_NAME, sessionIdCookieValue);
|
||||
}
|
||||
else*/
|
||||
if(StringUtils.hasContent(sessionUuidCookieValue))
|
||||
{
|
||||
authenticationContext.put(Auth0AuthenticationModule.SESSION_UUID_KEY, sessionUuidCookieValue);
|
||||
}
|
||||
else if(apiKeyHeaderValue != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
@ -533,12 +573,12 @@ public class QJavalinImplementation
|
||||
if(authorizationHeaderValue.startsWith(basicPrefix))
|
||||
{
|
||||
authorizationHeaderValue = authorizationHeaderValue.replaceFirst(basicPrefix, "");
|
||||
authenticationContext.put(BASIC_AUTH_NAME, authorizationHeaderValue);
|
||||
authenticationContext.put(Auth0AuthenticationModule.BASIC_AUTH_KEY, authorizationHeaderValue);
|
||||
}
|
||||
else if(authorizationHeaderValue.startsWith(bearerPrefix))
|
||||
{
|
||||
authorizationHeaderValue = authorizationHeaderValue.replaceFirst(bearerPrefix, "");
|
||||
authenticationContext.put(SESSION_ID_COOKIE_NAME, authorizationHeaderValue);
|
||||
authenticationContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, authorizationHeaderValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -90,8 +90,6 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.utils.Log;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
@ -292,18 +290,7 @@ public class QPicoCliImplementation
|
||||
}
|
||||
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
if(sessionId == null && authenticationModule instanceof Auth0AuthenticationModule)
|
||||
{
|
||||
LineReader lr = LineReaderBuilder.builder().build();
|
||||
String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete...");
|
||||
dotenv = loadDotEnv();
|
||||
if(dotenv.isPresent())
|
||||
{
|
||||
sessionId = dotenv.get().get("SESSION_ID");
|
||||
}
|
||||
}
|
||||
|
||||
authenticationContext.put("sessionId", sessionId);
|
||||
authenticationContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, sessionId);
|
||||
|
||||
// todo - does this need some per-provider logic actually? mmm...
|
||||
session = authenticationModule.createSession(qInstance, authenticationContext);
|
||||
|
Reference in New Issue
Block a user