From 878f374cb577deb1d1b35d4d7670e05bbfeef639 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 26 Feb 2024 14:29:45 -0600 Subject: [PATCH] CE-937 Add concept of customizers to authentication modules; basic implementation within auth0 for customizing session/security keys --- .../core/instances/QInstanceValidator.java | 18 ++ .../QAuthenticationMetaData.java | 33 ++++ ...thenticationModuleCustomizerInterface.java | 55 ++++++ .../Auth0AuthenticationModule.java | 182 ++++++++++++++---- .../instances/QInstanceValidatorTest.java | 21 ++ .../Auth0AuthenticationModuleTest.java | 82 +++++++- 6 files changed, 349 insertions(+), 42 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleCustomizerInterface.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 624c22e3..2e7c5797 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -153,6 +153,7 @@ public class QInstanceValidator try { validateBackends(qInstance); + validateAuthentication(qInstance); validateAutomationProviders(qInstance); validateTables(qInstance, joinGraph); validateProcesses(qInstance); @@ -383,6 +384,23 @@ public class QInstanceValidator + /******************************************************************************* + ** + *******************************************************************************/ + private void validateAuthentication(QInstance qInstance) + { + QAuthenticationMetaData authentication = qInstance.getAuthentication(); + if(authentication != null) + { + if(authentication.getCustomizer() != null) + { + validateSimpleCodeReference("Instance Authentication meta data customizer ", authentication.getCustomizer(), QAuthenticationModuleCustomizerInterface.class); + } + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/QAuthenticationMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/QAuthenticationMetaData.java index cf07846d..14a9fbb6 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/QAuthenticationMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/QAuthenticationMetaData.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonFilter; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; /******************************************************************************* @@ -43,6 +44,7 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface @JsonFilter("secretsFilter") private Map values; + private QCodeReference customizer; /******************************************************************************* @@ -192,4 +194,35 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface qInstance.setAuthentication(this); } + + + /******************************************************************************* + ** Getter for customizer + *******************************************************************************/ + public QCodeReference getCustomizer() + { + return (this.customizer); + } + + + + /******************************************************************************* + ** Setter for customizer + *******************************************************************************/ + public void setCustomizer(QCodeReference customizer) + { + this.customizer = customizer; + } + + + + /******************************************************************************* + ** Fluent setter for customizer + *******************************************************************************/ + public QAuthenticationMetaData withCustomizer(QCodeReference customizer) + { + this.customizer = customizer; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleCustomizerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleCustomizerInterface.java new file mode 100644 index 00000000..fd77cd81 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleCustomizerInterface.java @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.modules.authentication; + + +import java.io.Serializable; +import java.util.Map; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.session.QSession; + + +/******************************************************************************* + ** Interface for customizing behavior of an Authentication module. + *******************************************************************************/ +public interface QAuthenticationModuleCustomizerInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + default void addSecurityKeyValueToSession(QSession session, String keyName, Serializable value) + { + session.withSecurityKeyValue(keyName, value); + } + + /******************************************************************************* + ** + *******************************************************************************/ + default void customizeSession(QInstance qInstance, QSession qSession, Map context) + { + ////////// + // noop // + ////////// + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java index 82cf2ed8..7d1fc57d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java @@ -47,6 +47,7 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.net.Response; +import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; 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; @@ -71,6 +72,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0Authent 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.QAuthenticationModuleCustomizerInterface; 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; @@ -80,6 +82,7 @@ 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 com.kingsrook.qqq.backend.core.utils.memoization.Memoization; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -129,6 +132,19 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface static final String EXPIRED_TOKEN_ERROR = "Token has expired"; static final String INVALID_TOKEN_ERROR = "An invalid token was provided"; + private Auth0AuthenticationMetaData metaData; + + ////////////////////////////////////////////////////////////////////////////////// + // do not use this var directly - rather - always call the getCustomizer method // + ////////////////////////////////////////////////////////////////////////////////// + private QAuthenticationModuleCustomizerInterface _customizer = null; + private boolean customizerHasBeenRequested = false; + + private static boolean mayMemoize = true; + + private static final Memoization getAccessTokenFromSessionUUIDMemoization = new Memoization() + .withTimeout(Duration.of(1, ChronoUnit.MINUTES)) + .withMaxSize(1000); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // this is how we allow the actions within this class to work without themselves having a logged-in user. // @@ -165,9 +181,12 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface @Override public QSession createSession(QInstance qInstance, Map context) throws QAuthenticationException { + QInstance contextInstanceBefore = QContext.getQInstance(); + try { - Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); + QContext.setQInstance(qInstance); + this.metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); String accessToken = null; if(CollectionUtils.containsKeyWithNonNullValue(context, SESSION_UUID_KEY)) @@ -252,16 +271,6 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface } } - /* 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 // /////////////////////////////////////////// @@ -311,6 +320,10 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface LOG.error(message, e); throw (new QAuthenticationException(message)); } + finally + { + QContext.setQInstance(contextInstanceBefore); + } } @@ -346,26 +359,36 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface *******************************************************************************/ private QSession buildAndValidateSession(QInstance qInstance, String accessToken) throws JwkException { - QSession qSession = buildQSessionFromToken(accessToken, qInstance); - if(isSessionValid(qInstance, qSession)) + QSession beforeSession = QContext.getQSession(); + + try { + QContext.setQSession(getChickenAndEggSession()); + 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 key = new SimpleStateKey<>(qSession.getIdReference()); + spi.put(key, Instant.now()); + 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 key = new SimpleStateKey<>(qSession.getIdReference()); - spi.put(key, Instant.now()); - - return (qSession); + finally + { + QContext.setQSession(beforeSession); + } } @@ -509,7 +532,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface *******************************************************************************/ private void validateToken(QInstance qInstance, String tokenString) throws JwkException { - Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); + this.metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); DecodedJWT idToken = JWT.decode(tokenString); JwkProvider provider = new UrlJwkProvider(metaData.getBaseUrl()); @@ -567,10 +590,10 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface qSession.setIdReference(accessToken); qSession.setUser(qUser); - ///////////////////////////////////////////////////////////////////// - // put the user id reference in security key value for usierId key // - ///////////////////////////////////////////////////////////////////// - qSession.withSecurityKeyValue("userId", qUser.getIdReference()); + //////////////////////////////////////////////////////////////////// + // put the user id reference in security key value for userId key // + //////////////////////////////////////////////////////////////////// + addSecurityKeyValueToSession(qSession, "userId", qUser.getIdReference()); ///////////////////////////////////////////////// // set permissions in the session from the JWT // @@ -581,12 +604,43 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface // set security keys in the session from the JWT // /////////////////////////////////////////////////// setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + + ////////////////////////////////////////////////////////////// + // allow customizer to do custom things here, if so desired // + ////////////////////////////////////////////////////////////// + if(getCustomizer() != null) + { + getCustomizer().customizeSession(qInstance, qSession, Map.of("jwtPayloadJsonObject", payload)); + } return (qSession); } + /******************************************************************************* + ** + *******************************************************************************/ + private void addSecurityKeyValueToSession(QSession qSession, String key, String value) + { + if(getCustomizer() == null) + { + /////////////////////////////////////////////////// + // if there's no customizer, do the direct thing // + /////////////////////////////////////////////////// + qSession.withSecurityKeyValue(key, value); + } + else + { + /////////////////////////////////////////////////////////// + // else have the customizer add the value to the session // + /////////////////////////////////////////////////////////// + getCustomizer().addSecurityKeyValueToSession(qSession, key, value); + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -616,7 +670,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface /******************************************************************************* ** *******************************************************************************/ - static void setSecurityKeysInSessionFromJwtPayload(QInstance qInstance, JSONObject payload, QSession qSession) + void setSecurityKeysInSessionFromJwtPayload(QInstance qInstance, JSONObject payload, QSession qSession) { for(String payloadKey : List.of("com.kingsrook.qqq.app_metadata", "com.kingsrook.qqq.client_metadata")) { @@ -668,7 +722,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface /******************************************************************************* ** *******************************************************************************/ - private static void setSecurityKeyValuesFromToken(Set allowedSecurityKeyNames, QSession qSession, String securityKeyName, JSONObject securityKeyValues, String jsonKey) + private void setSecurityKeyValuesFromToken(Set allowedSecurityKeyNames, QSession qSession, String securityKeyName, JSONObject securityKeyValues, String jsonKey) { if(!allowedSecurityKeyNames.contains(securityKeyName)) { @@ -686,7 +740,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface Object optValue = valueArray.opt(i); if(optValue != null) { - qSession.withSecurityKeyValue(securityKeyName, ValueUtils.getValueAsString(optValue)); + addSecurityKeyValueToSession(qSession, securityKeyName, ValueUtils.getValueAsString(optValue)); } } } @@ -697,7 +751,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface { for(String v : values.split(",")) { - qSession.withSecurityKeyValue(securityKeyName, v); + addSecurityKeyValueToSession(qSession, securityKeyName, v); } } } @@ -815,6 +869,25 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface ** *******************************************************************************/ private String getAccessTokenFromSessionUUID(Auth0AuthenticationMetaData metaData, String sessionUUID) throws QAuthenticationException + { + if(mayMemoize) + { + return getAccessTokenFromSessionUUIDMemoization.getResultThrowing(sessionUUID, (String x) -> + doGetAccessTokenFromSessionUUID(sessionUUID) + ).orElse(null); + } + else + { + return (doGetAccessTokenFromSessionUUID(sessionUUID)); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private String doGetAccessTokenFromSessionUUID(String sessionUUID) throws QAuthenticationException { String accessToken = null; QSession beforeSession = QContext.getQSession(); @@ -982,4 +1055,39 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface } } + + + /******************************************************************************* + ** + *******************************************************************************/ + private QAuthenticationModuleCustomizerInterface getCustomizer() + { + try + { + if(!customizerHasBeenRequested) + { + customizerHasBeenRequested = true; + + if(this.metaData == null) + { + this.metaData = (Auth0AuthenticationMetaData) QContext.getQInstance().getAuthentication(); + } + + if(this.metaData.getCustomizer() != null) + { + this._customizer = QCodeLoader.getAdHoc(QAuthenticationModuleCustomizerInterface.class, this.metaData.getCustomizer()); + } + } + + return (this._customizer); + } + catch(Exception e) + { + //////////////////////// + // should this throw? // + //////////////////////// + LOG.warn("Error getting customizer.", e); + return (null); + } + } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index f66dfa16..55b94bb0 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -1813,6 +1813,19 @@ class QInstanceValidatorTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testAuthenticationCustomizer() + { + assertValidationSuccess((qInstance -> qInstance.getAuthentication().withCustomizer(null))); + assertValidationSuccess((qInstance -> qInstance.getAuthentication().withCustomizer(new QCodeReference(ValidAuthCustomizer.class)))); + assertValidationFailureReasons((qInstance -> qInstance.getAuthentication().withCustomizer(new QCodeReference(ArrayList.class))), "not of the expected type"); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -1987,5 +2000,13 @@ class QInstanceValidatorTest extends BaseTest return null; } } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {} + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java index e056889e..fe2fed53 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.modules.authentication.implementations; +import java.io.Serializable; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; @@ -36,7 +37,9 @@ import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; 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.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface; import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider; import com.kingsrook.qqq.backend.core.state.SimpleStateKey; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -354,7 +357,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest "iat": 1673379451 } """); - Auth0AuthenticationModule.setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); assertEquals(List.of("true"), qSession.getSecurityKeyValues("storeAllAccess")); ///////////////////////////////////////////// @@ -373,7 +376,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest "iat": 1673379451 } """); - Auth0AuthenticationModule.setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); assertEquals(List.of("2"), qSession.getSecurityKeyValues("store")); ////////////////////////// @@ -394,7 +397,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest "iat": 1673379451 } """); - Auth0AuthenticationModule.setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); assertEquals(List.of("3", "4", "5"), qSession.getSecurityKeyValues("store")); assertEquals(List.of("internal"), qSession.getSecurityKeyValues("internalOrExternal")); @@ -409,7 +412,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest "iat": 1673379451 } """); - Auth0AuthenticationModule.setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); assertTrue(CollectionUtils.nullSafeIsEmpty(qSession.getSecurityKeyValues())); ///////////////////////////////////////////////////// @@ -428,7 +431,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest "iat": 1673379451 } """); - Auth0AuthenticationModule.setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); assertTrue(CollectionUtils.nullSafeIsEmpty(qSession.getSecurityKeyValues())); } @@ -491,4 +494,73 @@ public class Auth0AuthenticationModuleTest extends BaseTest assertEquals("123456******", maskForLog("1234567890")); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testCustomizer() + { + QInstance qInstance = QContext.getQInstance(); + qInstance.setAuthentication(new Auth0AuthenticationMetaData() + .withCustomizer(new QCodeReference(Customizer.class))); + + { + ///////////////////////////////////////////////////////////////// + // baseline case - value in json becomes value in security key // + ///////////////////////////////////////////////////////////////// + QSession qSession = new QSession(); + JSONObject payload = new JSONObject(""" + { + "com.kingsrook.qqq.app_metadata": { + "securityKeyValues": { + "store": "1" + } + } + } + """); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + assertEquals(List.of("1"), qSession.getSecurityKeyValues("store")); + } + + { + QSession qSession = new QSession(); + JSONObject payload = new JSONObject(""" + { + "com.kingsrook.qqq.app_metadata": { + "securityKeyValues": { + "store": "oddDigits" + } + } + } + """); + new Auth0AuthenticationModule().setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession); + assertEquals(List.of("1", "3", "5", "7", "9"), qSession.getSecurityKeyValues("store")); + } + } + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class Customizer implements QAuthenticationModuleCustomizerInterface + { + @Override + public void addSecurityKeyValueToSession(QSession session, String keyName, Serializable value) + { + if("oddDigits".equals(value)) + { + for(String oddValue : List.of("1", "3", "5", "7", "9")) + { + QAuthenticationModuleCustomizerInterface.super.addSecurityKeyValueToSession(session, keyName, oddValue); + } + } + else + { + QAuthenticationModuleCustomizerInterface.super.addSecurityKeyValueToSession(session, keyName, value); + } + } + } + }