QQQ-27: updates to allow Auth0 to be an authentication model in QQQ

This commit is contained in:
Tim Chamberlain
2022-07-19 13:16:13 -05:00
parent f35744cd19
commit 661caaf38c
17 changed files with 785 additions and 32 deletions

View File

@ -53,6 +53,11 @@
<!-- none, this is core. -->
<!-- 3rd party deps specifically for this module -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>mvc-auth-commons</artifactId>
<version>[1.0, 2.0)</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>

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 while doing module-dispatch
*
*******************************************************************************/
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

@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
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.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import org.apache.logging.log4j.LogManager;

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.QStepMetaData;
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;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@ -29,7 +30,7 @@ import java.util.Map;
/*******************************************************************************
**
*******************************************************************************/
public class QSession
public class QSession implements Serializable
{
private String idReference;
private QUser user;

View File

@ -0,0 +1,333 @@
/*
* 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 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 = "qqq.idToken";
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)
{
////////////////////////////////
// could not decode the token //
////////////////////////////////
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, getNow());
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());
if(spi.get(Instant.class, key).isPresent())
{
Instant lastTimeChecked = spi.get(Instant.class, key).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, getNow()).compareTo(Duration.ofSeconds(ID_TOKEN_VALIDATION_INTERVAL_SECONDS)) < 0);
}
return (false);
}
/*******************************************************************************
** public method so that 'now' can be used for testing purposes
** - defaults to real 'now'
*******************************************************************************/
public Instant getNow()
{
if(now == null)
{
now = Instant.now();
}
return (now);
}
/*******************************************************************************
** public method so that 'now' can be set for testing purposes
*******************************************************************************/
public void setNow(Instant now)
{
this.now = now;
}
/*******************************************************************************
** 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.setIdReference(payload.getString("email"));
qUser.setFullName(payload.getString("name"));
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());
}
/*******************************************************************************
**
*******************************************************************************/
private 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.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.QUser;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
/*******************************************************************************
@ -40,7 +40,7 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
**
*******************************************************************************/
@Override
public QSession createSession(Map<String, String> context)
public QSession createSession(QInstance qInstance, Map<String, String> context)
{
QUser qUser = new QUser();
qUser.setIdReference("anonymous");
@ -68,10 +68,6 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
@Override
public boolean isSessionValid(QSession session)
{
if(session == null)
{
return (false);
}
return (true);
return session != null;
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.util.Map;
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.QUser;
import org.apache.logging.log4j.LogManager;
@ -43,7 +44,7 @@ public class MockAuthenticationModule implements QAuthenticationModuleInterface
**
*******************************************************************************/
@Override
public QSession createSession(Map<String, String> context)
public QSession createSession(QInstance qInstance, Map<String, String> context)
{
QUser qUser = new QUser();
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.Map;
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.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
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
{
private Map<String, String> authenticationTypeToModuleClassNameMap;
private final Map<String, String> authenticationTypeToModuleClassNameMap;
@ -48,9 +48,9 @@ public class QAuthenticationModuleDispatcher
public QAuthenticationModuleDispatcher()
{
authenticationTypeToModuleClassNameMap = new HashMap<>();
authenticationTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("fullyAnonymous", "com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("TODO:google", "com.kingsrook.qqq.authentication.module.google.GoogleAuthenticationModule");
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.MOCK.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule");
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.FULLY_ANONYMOUS.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.FullyAnonymousAuthenticationModule");
authenticationTypeToModuleClassNameMap.put(QAuthenticationType.AUTH_0.getName(), "com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule");
// todo - let user define custom type -> classes
}
@ -66,7 +66,7 @@ public class QAuthenticationModuleDispatcher
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 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;
@ -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/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
package com.kingsrook.qqq.backend.core.modules.authentication.metadata;
import java.util.HashMap;
import java.util.Map;
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
{
private String name;
private String type;
private QAuthenticationType type;
@JsonFilter("secretsFilter")
private Map<String, String> values;
@ -120,7 +121,7 @@ public class QAuthenticationMetaData
** Getter for type
**
*******************************************************************************/
public String getType()
public QAuthenticationType getType()
{
return type;
}
@ -131,7 +132,7 @@ public class QAuthenticationMetaData
** Setter for type
**
*******************************************************************************/
public void setType(String type)
public void setType(QAuthenticationType type)
{
this.type = type;
}
@ -141,7 +142,7 @@ public class QAuthenticationMetaData
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withType(String type)
public QAuthenticationMetaData withType(QAuthenticationType type)
{
this.type = type;
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.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.junit.jupiter.api.Assertions.assertTrue;
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 testValidToken() throws QAuthenticationException
{
Map<String, String> context = new HashMap<>();
context.put(AUTH0_ID_TOKEN_KEY, VALID_TOKEN);
//////////////////////////////////////////////////////////
// Tuesday, July 19, 2022 12:40:27.299 PM GMT-05:00 DST //
//////////////////////////////////////////////////////////
Instant now = Instant.ofEpochMilli(1658252427299L);
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.setNow(now);
QSession session = auth0AuthenticationModule.createSession(getQInstance(), context);
assertTrue(session.getUser().getIdReference().equals("tim.chamberlain@kingsrook.com"));
assertTrue(session.getUser().getFullName().equals("Tim Chamberlain"));
}
/*******************************************************************************
** 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);
}
catch(QAuthenticationException qae)
{
assertTrue(qae.getMessage().contains(INVALID_TOKEN_ERROR));
return;
}
fail("Should never get here");
}
/*******************************************************************************
** 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);
}
catch(QAuthenticationException qae)
{
assertTrue(qae.getMessage().contains(COULD_NOT_DECODE_ERROR));
return;
}
fail("Should never get here");
}
/*******************************************************************************
** 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);
}
catch(QAuthenticationException qae)
{
assertTrue(qae.getMessage().contains(EXPIRED_TOKEN_ERROR));
return;
}
fail("Should never get here");
}
/*******************************************************************************
** Test failure case, empty context
**
*******************************************************************************/
@Test
public void testEmptyContext()
{
try
{
Auth0AuthenticationModule auth0AuthenticationModule = new Auth0AuthenticationModule();
auth0AuthenticationModule.createSession(getQInstance(), new HashMap<>());
}
catch(QAuthenticationException qae)
{
assertTrue(qae.getMessage().contains(TOKEN_NOT_PROVIDED_ERROR));
return;
}
fail("Should never get here");
}
/*******************************************************************************
** 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);
}
catch(QAuthenticationException qae)
{
assertTrue(qae.getMessage().contains(TOKEN_NOT_PROVIDED_ERROR));
return;
}
fail("Should never get here");
}
/*******************************************************************************
** 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.modules.authentication.FullyAnonymousAuthenticationModule;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -36,12 +35,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class FullyAnonymousAuthenticationModuleTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test()
{
FullyAnonymousAuthenticationModule fullyAnonymousAuthenticationModule = new FullyAnonymousAuthenticationModule();
QSession session = fullyAnonymousAuthenticationModule.createSession(null);
QSession session = fullyAnonymousAuthenticationModule.createSession(null, null);
assertNotNull(session, "Session 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.model.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.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;

View File

@ -26,8 +26,9 @@ import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
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.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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
@ -109,7 +110,7 @@ public class TestUtils
{
return new QAuthenticationMetaData()
.withName("mock")
.withType("mock");
.withType(QAuthenticationType.MOCK);
}
@ -304,6 +305,6 @@ public class TestUtils
public static QSession getMockSession()
{
MockAuthenticationModule mockAuthenticationModule = new MockAuthenticationModule();
return (mockAuthenticationModule.createSession(null));
return (mockAuthenticationModule.createSession(null, null));
}
}