Merge remote-tracking branch 'remotes/origin/QQQ-27-build-support-for-auth-0-throughout-qqq' into feature/sprint-7-integration

This commit is contained in:
2022-07-25 10:16:44 -05:00
18 changed files with 772 additions and 34 deletions

View File

@ -25,7 +25,7 @@
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId> <artifactId>qqq-backend-core</artifactId>
<version>0.2.0-SNAPSHOT</version> <version>0.2.0-20220721.162748-8</version>
<scm> <scm>
<connection>scm:git:git@github.com:Kingsrook/qqq-backend-core.git</connection> <connection>scm:git:git@github.com:Kingsrook/qqq-backend-core.git</connection>
@ -53,6 +53,11 @@
<!-- none, this is core. --> <!-- none, this is core. -->
<!-- 3rd party deps specifically for this module --> <!-- 3rd party deps specifically for this module -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>mvc-auth-commons</artifactId>
<version>1.9.2</version>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions; package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
@ -43,7 +44,7 @@ public class ActionHelper
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData()); QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
if(!authenticationModule.isSessionValid(request.getSession())) if(!authenticationModule.isSessionValid(request.getSession()))
{ {
throw new QException("Invalid session in request"); throw new QAuthenticationException("Invalid session in request");
} }
} }

View File

@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.exceptions;
/*******************************************************************************
* Exception thrown doing authentication
*
*******************************************************************************/
public class QAuthenticationException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QAuthenticationException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QAuthenticationException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@ -27,9 +27,9 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus; import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;

View File

@ -0,0 +1,59 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.model.metadata;
/*******************************************************************************
** Enum to define the possible authentication types
**
*******************************************************************************/
@SuppressWarnings("rawtypes")
public enum QAuthenticationType
{
AUTH_0("auth0"),
FULLY_ANONYMOUS("fullyAnonymous"),
MOCK("mock");
private final String name;
/*******************************************************************************
** enum constructor
*******************************************************************************/
QAuthenticationType(String name)
{
this.name = name;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return this.name;
}
}

View File

@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
/******************************************************************************* /*******************************************************************************

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.session; package com.kingsrook.qqq.backend.core.model.session;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -29,7 +30,7 @@ import java.util.Map;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class QSession public class QSession implements Serializable
{ {
private String idReference; private String idReference;
private QUser user; private QUser user;

View File

@ -0,0 +1,317 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.metadata.Auth0AuthenticationMetaData;
import com.kingsrook.qqq.backend.core.state.AbstractStateKey;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
/*******************************************************************************
**
*******************************************************************************/
public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
{
private static final Logger logger = LogManager.getLogger(Auth0AuthenticationModule.class);
private static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 300;
public static final String AUTH0_ID_TOKEN_KEY = "sessionId";
public static final String TOKEN_NOT_PROVIDED_ERROR = "Id Token was not provided";
public static final String COULD_NOT_DECODE_ERROR = "Unable to decode id token";
public static final String EXPIRED_TOKEN_ERROR = "Token has expired";
public static final String INVALID_TOKEN_ERROR = "An invalid token was provided";
private Instant now;
/*******************************************************************************
**
*******************************************************************************/
@Override
public QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException
{
//////////////////////////////////////////////////
// get the jwt id token from the context object //
//////////////////////////////////////////////////
String idToken = context.get(AUTH0_ID_TOKEN_KEY);
if(idToken == null)
{
logger.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
{
/////////////////////////////////////////////////////
// try to build session to see if still valid //
// then call method to check more session validity //
/////////////////////////////////////////////////////
QSession qSession = buildQSessionFromToken(idToken);
if(isSessionValid(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 = revalidateToken(qInstance, idToken);
////////////////////////////////////////////////////////////////////
// put now into state so we dont check until next interval passes //
///////////////////////////////////////////////////////////////////
StateProviderInterface spi = getStateProvider();
Auth0StateKey key = new Auth0StateKey(qSession.getIdReference());
spi.put(key, Instant.now());
return (qSession);
}
catch(JWTDecodeException jde)
{
////////////////////////////////
// could not decode the token //
////////////////////////////////
logger.warn(COULD_NOT_DECODE_ERROR, jde);
throw (new QAuthenticationException(COULD_NOT_DECODE_ERROR));
}
catch(TokenExpiredException tee)
{
logger.info(EXPIRED_TOKEN_ERROR, tee);
throw (new QAuthenticationException(EXPIRED_TOKEN_ERROR));
}
catch(JWTVerificationException | JwkException jve)
{
///////////////////////////////////////////
// token had invalid signature or claims //
///////////////////////////////////////////
logger.warn(INVALID_TOKEN_ERROR, jve);
throw (new QAuthenticationException(INVALID_TOKEN_ERROR));
}
catch(Exception e)
{
////////////////
// ¯\_(ツ)_/¯ //
////////////////
String message = "An unknown error occurred";
logger.error(message, e);
throw (new QAuthenticationException(message));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean isSessionValid(QSession session)
{
if(session == null)
{
return (false);
}
StateProviderInterface spi = getStateProvider();
Auth0StateKey key = new Auth0StateKey(session.getIdReference());
Optional<Instant> lastTimeCheckedOptional = spi.get(Instant.class, key);
if(lastTimeCheckedOptional.isPresent())
{
Instant lastTimeChecked = lastTimeCheckedOptional.get();
///////////////////////////////////////////////////////////////////////////////////////////////////
// returns negative int if less than compared duration, 0 if equal, positive int if greater than //
// - so this is basically saying, if the time between the last time we checked the token and //
// right now is more than ID_TOKEN_VALIDATION_INTERVAL_SECTIONS, then session needs revalidated //
///////////////////////////////////////////////////////////////////////////////////////////////////
return (Duration.between(lastTimeChecked, Instant.now()).compareTo(Duration.ofSeconds(ID_TOKEN_VALIDATION_INTERVAL_SECONDS)) < 0);
}
return (false);
}
/*******************************************************************************
** makes request to check if a token is still valid and build new qSession if it is
**
*******************************************************************************/
private QSession revalidateToken(QInstance qInstance, String idToken) throws JwkException
{
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
DecodedJWT jwt = JWT.decode(idToken);
JwkProvider provider = new UrlJwkProvider(metaData.getBaseUrl());
Jwk jwk = provider.get(jwt.getKeyId());
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(metaData.getBaseUrl())
.build();
///////////////////////////////////
// make call to verify the token //
///////////////////////////////////
verifier.verify(idToken);
return (buildQSessionFromToken(idToken));
}
/*******************************************************************************
** extracts info from token creating a QSession
**
*******************************************************************************/
private QSession buildQSessionFromToken(String idToken) throws JwkException
{
////////////////////////////////////
// decode and extract the payload //
////////////////////////////////////
DecodedJWT jwt = JWT.decode(idToken);
Base64.Decoder decoder = Base64.getUrlDecoder();
String payloadString = new String(decoder.decode(jwt.getPayload()));
JSONObject payload = new JSONObject(payloadString);
QUser qUser = new QUser();
qUser.setFullName(payload.getString("name"));
if(payload.has("email"))
{
qUser.setIdReference(payload.getString("email"));
}
else
{
qUser.setIdReference(payload.getString("nickname"));
}
QSession qSession = new QSession();
qSession.setIdReference(idToken);
qSession.setUser(qUser);
return (qSession);
}
/*******************************************************************************
** Load an instance of the appropriate state provider
**
*******************************************************************************/
public static StateProviderInterface getStateProvider()
{
// TODO - read this from somewhere in meta data eh?
return (InMemoryStateProvider.getInstance());
}
/*******************************************************************************
**
*******************************************************************************/
public static class Auth0StateKey extends AbstractStateKey
{
private final String key;
/*******************************************************************************
** Constructor.
**
*******************************************************************************/
Auth0StateKey(String key)
{
this.key = key;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return (this.key);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(o == null || getClass() != o.getClass())
{
return false;
}
Auth0StateKey that = (Auth0StateKey) o;
return key.equals(that.key);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int hashCode()
{
return key.hashCode();
}
}
}

View File

@ -24,9 +24,9 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser; import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
/******************************************************************************* /*******************************************************************************
@ -40,7 +40,7 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public QSession createSession(Map<String, String> context) public QSession createSession(QInstance qInstance, Map<String, String> context)
{ {
QUser qUser = new QUser(); QUser qUser = new QUser();
qUser.setIdReference("anonymous"); qUser.setIdReference("anonymous");
@ -68,10 +68,6 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
@Override @Override
public boolean isSessionValid(QSession session) public boolean isSessionValid(QSession session)
{ {
if(session == null) return session != null;
{
return (false);
}
return (true);
} }
} }

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser; import com.kingsrook.qqq.backend.core.model.session.QUser;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -43,7 +44,7 @@ public class MockAuthenticationModule implements QAuthenticationModuleInterface
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public QSession createSession(Map<String, String> context) public QSession createSession(QInstance qInstance, Map<String, String> context)
{ {
QUser qUser = new QUser(); QUser qUser = new QUser();
qUser.setIdReference("User:" + (System.currentTimeMillis() % USER_ID_MODULO)); qUser.setIdReference("User:" + (System.currentTimeMillis() % USER_ID_MODULO));

View File

@ -25,8 +25,8 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
/******************************************************************************* /*******************************************************************************
@ -38,7 +38,7 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
*******************************************************************************/ *******************************************************************************/
public class QAuthenticationModuleDispatcher public class QAuthenticationModuleDispatcher
{ {
private Map<String, String> authenticationTypeToModuleClassNameMap; private final Map<String, String> authenticationTypeToModuleClassNameMap;
@ -48,9 +48,9 @@ public class QAuthenticationModuleDispatcher
public QAuthenticationModuleDispatcher() public QAuthenticationModuleDispatcher()
{ {
authenticationTypeToModuleClassNameMap = new HashMap<>(); authenticationTypeToModuleClassNameMap = new HashMap<>();
authenticationTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule"); authenticationTypeToModuleClassNameMap.put(QAuthenticationType.MOCK.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("fullyAnonymous", "com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule"); authenticationTypeToModuleClassNameMap.put(QAuthenticationType.FULLY_ANONYMOUS.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("TODO:google", "com.kingsrook.qqq.authentication.module.google.GoogleAuthenticationModule"); authenticationTypeToModuleClassNameMap.put(QAuthenticationType.AUTH_0.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule");
// todo - let user define custom type -> classes // todo - let user define custom type -> classes
} }
@ -66,7 +66,7 @@ public class QAuthenticationModuleDispatcher
throw (new QModuleDispatchException("No authentication meta data defined.")); throw (new QModuleDispatchException("No authentication meta data defined."));
} }
return getQModule(authenticationMetaData.getType()); return getQModule(authenticationMetaData.getType().getName());
} }

View File

@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -35,7 +37,7 @@ public interface QAuthenticationModuleInterface
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
QSession createSession(Map<String, String> context); QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException;
/******************************************************************************* /*******************************************************************************

View File

@ -0,0 +1,79 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.metadata;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
/*******************************************************************************
** Meta-data to provide details of an RDBMS backend (e.g., connection params)
*******************************************************************************/
public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
{
private String baseUrl;
/*******************************************************************************
** Default Constructor.
*******************************************************************************/
public Auth0AuthenticationMetaData()
{
super();
setType(QAuthenticationType.AUTH_0);
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public Auth0AuthenticationMetaData withBaseUrl(String baseUrl)
{
setBaseUrl(baseUrl);
return this;
}
/*******************************************************************************
** Getter for baseUrl
**
*******************************************************************************/
public String getBaseUrl()
{
return baseUrl;
}
/*******************************************************************************
** Setter for baseUrl
**
*******************************************************************************/
public void setBaseUrl(String baseUrl)
{
this.baseUrl = baseUrl;
}
}

View File

@ -19,12 +19,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.kingsrook.qqq.backend.core.model.metadata; package com.kingsrook.qqq.backend.core.modules.authentication.metadata;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
/******************************************************************************* /*******************************************************************************
@ -35,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonFilter;
public class QAuthenticationMetaData public class QAuthenticationMetaData
{ {
private String name; private String name;
private String type; private QAuthenticationType type;
@JsonFilter("secretsFilter") @JsonFilter("secretsFilter")
private Map<String, String> values; private Map<String, String> values;
@ -120,7 +121,7 @@ public class QAuthenticationMetaData
** Getter for type ** Getter for type
** **
*******************************************************************************/ *******************************************************************************/
public String getType() public QAuthenticationType getType()
{ {
return type; return type;
} }
@ -131,7 +132,7 @@ public class QAuthenticationMetaData
** Setter for type ** Setter for type
** **
*******************************************************************************/ *******************************************************************************/
public void setType(String type) public void setType(QAuthenticationType type)
{ {
this.type = type; this.type = type;
} }
@ -141,7 +142,7 @@ public class QAuthenticationMetaData
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public QAuthenticationMetaData withType(String type) public QAuthenticationMetaData withType(QAuthenticationType type)
{ {
this.type = type; this.type = type;
return (this); return (this);

View File

@ -0,0 +1,223 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.Auth0AuthenticationMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule.AUTH0_ID_TOKEN_KEY;
import static com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule.COULD_NOT_DECODE_ERROR;
import static com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule.EXPIRED_TOKEN_ERROR;
import static com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule.INVALID_TOKEN_ERROR;
import static com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule.TOKEN_NOT_PROVIDED_ERROR;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
** Unit test for the FullyAnonymousAuthenticationModule
*******************************************************************************/
public class Auth0AuthenticationModuleTest
{
private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE5VDE2OjI0OjQ1LjgyMloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MjQ3OTAyLCJleHAiOjE2NTgyODM5MDIsIm5vbmNlIjoiZUhOdFMxbEtUR2N5ZG5KS1VVY3RkRTFVT0ZKNmJFNUxVVkEwZEdsRGVXOXZkVkl4UW41eVRrUlJlZz09In0.hib7JR8NDU2kx8Fj1bnzo3IUuabE6Hb-Z7HHZAJPQuF_Zdg3L1KDypn6SY7HAd_dsz2N8RkXfvQto-Y2g2ukuz7FxzNFgcVL99cyEO3YqmyCa6JTOTCrxdeaIE8QZpCEKvC28oeJBv0wO1Dwc--OVJMsK2vSzyxj1WNok64YYjWKLL4c0dFf-nj0KWFr1IU-tMiyWLDDiJw2Sa8M4YxXZYqdlkgNmrBPExgcm9l9SiT2l3Ts3Sgc_IyMVyMrnV8XX50EWdsm6vuCOSUcqf0XhjDQ7urZveoVwVLnYq3GcLhVBcy1Hr9RL8zPdPynOzsbX6uCww2Esrv6iwWrgQ5zBA";
private static final String INVALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE5VDE2OjI0OjQ1LjgyMloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MjQ3OTAyLCJleHAiOjE2NTgyODM5MDIsIm5vbmNlIjoiZUhOdFMxbEtUR2N5ZG5KS1VVY3RkRTFVT0ZKNmJFNUxVVkEwZEdsRGVXOXZkVkl4UW41eVRrUlJlZz09In0.hib7JR8NDU2kx8Fj1bnzo3IUuabE6Hb-Z7HHZAJPQuF_Zdg3L1KDypn6SY7HAd_dsz2N8RkXfvQto-Y2g2ukuz7FxzNFgcVL99cyEO3YqmyCa6JTOTCrxdeaIE8QZpCEKvC28oeJBv0wO1Dwc--OVJMsK2vSzyxj1WNok64YYjWKLL4c0dFf-nj0KWFr1IU-tMiyWLDDiJw2Sa8M4YxXZYqdlkgNmrBPExgcm9l9SiT2l3Ts3Sgc_IyMVyMrnV8XX50EWdsm6vuCOSUcqf0XhjDQ7urZveoVwVLnYq3GcLhVBcy1Hr9RL8zPdPynOzsbX6uCww2Esrv6iwWrgQ5zBA-thismakesinvalid";
private static final String EXPIRED_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE4VDIxOjM4OjE1LjM4NloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MTgwNDc3LCJleHAiOjE2NTgyMTY0NzcsIm5vbmNlIjoiVkZkQlYzWmplR2hvY1cwMk9WZEtabHBLU0c1K1ZXbElhMEV3VkZaeFpVdEJVMDErZUZaT1RtMTNiZz09In0.fU7EwUgNrupOPz_PX_aQKON2xG1-LWD85xVo1Bn41WNEek-iMyJoch8l6NUihi7Bou14BoOfeWIG_sMqsLHqI2Pk7el7l1kigsjURx0wpiXadBt8piMxdIlxdToZEMuZCBzg7eJvXh4sM8tlV5cm0gPa6FT9Ih3VGJajNlXi5BcYS_JRpIvFvHn8-Bxj4KiAlZ5XPPkopjnDgP8kFfc4cMn_nxDkqWYlhj-5TaGW2xCLC9Qr_9UNxX0fm-CkKjYs3Z5ezbiXNkc-bxrCYvxeBeDPf8-T3EqrxCRVqCZSJ85BHdOc_E7UZC_g8bNj0umoplGwlCbzO4XIuOO-KlIaOg";
private static final String UNDECODABLE_TOKEN = "UNDECODABLE";
public static final String AUTH0_BASE_URL = "https://kingsrook.us.auth0.com/";
/*******************************************************************************
** Test a valid token where 'now' is set to a time that would be valid for it
**
*******************************************************************************/
@Test
public void testLastTimeChecked() throws QAuthenticationException
{
//////////////////////////////////////////////////////////
// Tuesday, July 19, 2022 12:40:27.299 PM GMT-05:00 DST //
//////////////////////////////////////////////////////////
Instant now = Instant.now();
/////////////////////////////////////////////////////////
// put the 'now' from the past into the state provider //
/////////////////////////////////////////////////////////
StateProviderInterface spi = InMemoryStateProvider.getInstance();
Auth0AuthenticationModule.Auth0StateKey key = new Auth0AuthenticationModule.Auth0StateKey(VALID_TOKEN);
spi.put(key, now);
//////////////////////
// build up session //
//////////////////////
QSession session = new QSession();
session.setIdReference(VALID_TOKEN);
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
assertEquals(true, auth0AuthenticationModule.isSessionValid(session), "Session should return as still valid.");
}
/*******************************************************************************
** Test failure case, token is invalid
**
*******************************************************************************/
@Test
public void testInvalidToken()
{
Map<String, String> context = new HashMap<>();
context.put(AUTH0_ID_TOKEN_KEY, INVALID_TOKEN);
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), context);
fail("Should never get here");
}
catch(QAuthenticationException qae)
{
assertThat(qae.getMessage()).contains(INVALID_TOKEN_ERROR);
}
}
/*******************************************************************************
** Test failure case, token cant be decoded
**
*******************************************************************************/
@Test
public void testUndecodableToken()
{
Map<String, String> context = new HashMap<>();
context.put(AUTH0_ID_TOKEN_KEY, UNDECODABLE_TOKEN);
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), context);
fail("Should never get here");
}
catch(QAuthenticationException qae)
{
assertThat(qae.getMessage()).contains(COULD_NOT_DECODE_ERROR);
}
}
/*******************************************************************************
** Test failure case, token is expired
**
*******************************************************************************/
@Test
public void testProperlyFormattedButExpiredToken()
{
Map<String, String> context = new HashMap<>();
context.put(AUTH0_ID_TOKEN_KEY, EXPIRED_TOKEN);
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), context);
fail("Should never get here");
}
catch(QAuthenticationException qae)
{
assertThat(qae.getMessage()).contains(EXPIRED_TOKEN_ERROR);
}
}
/*******************************************************************************
** Test failure case, empty context
**
*******************************************************************************/
@Test
public void testEmptyContext()
{
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), new HashMap<>());
fail("Should never get here");
}
catch(QAuthenticationException qae)
{
assertThat(qae.getMessage()).contains(TOKEN_NOT_PROVIDED_ERROR);
}
}
/*******************************************************************************
** Test failure case, null token
**
*******************************************************************************/
@Test
public void testNullToken()
{
Map<String, String> context = new HashMap<>();
context.put(AUTH0_ID_TOKEN_KEY, null);
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), context);
fail("Should never get here");
}
catch(QAuthenticationException qae)
{
assertThat(qae.getMessage()).contains(TOKEN_NOT_PROVIDED_ERROR);
}
}
/*******************************************************************************
** utility method to prime a qInstance for auth0 tests
**
*******************************************************************************/
private QInstance getQInstance()
{
QAuthenticationMetaData authenticationMetaData = new Auth0AuthenticationMetaData()
.withBaseUrl(AUTH0_BASE_URL)
.withName("auth0");
QInstance qInstance = TestUtils.defineInstance();
qInstance.setAuthentication(authenticationMetaData);
return (qInstance);
}
}

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -36,12 +35,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class FullyAnonymousAuthenticationModuleTest public class FullyAnonymousAuthenticationModuleTest
{ {
/*******************************************************************************
**
*******************************************************************************/
@Test @Test
public void test() public void test()
{ {
FullyAnonymousAuthenticationModule fullyAnonymousAuthenticationModule = new FullyAnonymousAuthenticationModule(); FullyAnonymousAuthenticationModule fullyAnonymousAuthenticationModule = new FullyAnonymousAuthenticationModule();
QSession session = fullyAnonymousAuthenticationModule.createSession(null); QSession session = fullyAnonymousAuthenticationModule.createSession(null, null);
assertNotNull(session, "Session should not be null"); assertNotNull(session, "Session should not be null");
assertNotNull(session.getIdReference(), "Session id ref should not be null"); assertNotNull(session.getIdReference(), "Session id ref should not be null");

View File

@ -23,9 +23,7 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
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.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;

View File

@ -31,8 +31,9 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
@ -114,7 +115,7 @@ public class TestUtils
{ {
return new QAuthenticationMetaData() return new QAuthenticationMetaData()
.withName("mock") .withName("mock")
.withType("mock"); .withType(QAuthenticationType.MOCK);
} }
@ -309,7 +310,7 @@ public class TestUtils
public static QSession getMockSession() public static QSession getMockSession()
{ {
MockAuthenticationModule mockAuthenticationModule = new MockAuthenticationModule(); MockAuthenticationModule mockAuthenticationModule = new MockAuthenticationModule();
return (mockAuthenticationModule.createSession(null)); return (mockAuthenticationModule.createSession(null, null));
} }