diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSession.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSession.java new file mode 100644 index 00000000..b6ae21bd --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSession.java @@ -0,0 +1,125 @@ +/* + * 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.model.session; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType; +import com.kingsrook.qqq.backend.core.utils.StringUtils; + + +/******************************************************************************* + ** Special session, indicating that an action being executed is being done not + ** on behalf of a (human or otherwise) user - but instead, is the application/ + ** system itself. + ** + ** Generally this means, escalated privileges - e.g., permission to all tables, + ** processes etc, and all security keys (e.g., all-access keys). + *******************************************************************************/ +public class QSystemUserSession extends QSession +{ + private static final QLogger LOG = QLogger.getLogger(QSystemUserSession.class); + + private static List allAccessKeyNames = null; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public QSystemUserSession() + { + super(); + + //////////////////////////////////////////////////////// + // always give system user all of the all-access keys // + //////////////////////////////////////////////////////// + for(String allAccessKeyName : getAllAccessKeyNames()) + { + withSecurityKeyValue(allAccessKeyName, true); + } + } + + + + /******************************************************************************* + ** System User Sessions should always have permission to all the things. + *******************************************************************************/ + @Override + public boolean hasPermission(String permissionName) + { + return (true); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private List getAllAccessKeyNames() + { + if(allAccessKeyNames == null) + { + QInstance qInstance = QContext.getQInstance(); + if(qInstance == null) + { + LOG.warn("QInstance was not set in context when creating a QSystemUserSession and trying to prime allAccessKeyNames... This SystemUserSession will NOT have any allAccessKeys."); + return (Collections.emptyList()); + } + + /////////////////////////////////////////////////////////////////////////////////////// + // ideally only 1 thread would do this, but, it's cheap, so don't bother locking. // + // and, if multiple get in, only the last one will assign to the field, so, s/b fine // + /////////////////////////////////////////////////////////////////////////////////////// + List list = new ArrayList<>(); + for(QSecurityKeyType securityKeyType : qInstance.getSecurityKeyTypes().values()) + { + if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName())) + { + list.add(securityKeyType.getAllAccessKeyName()); + } + } + + LOG.info("Initialized allAccessKeyNames for SystemUserSessions as: " + list); + allAccessKeyNames = list; + } + + return (allAccessKeyNames); + } + + + + /******************************************************************************* + ** Meant for use in tests - to explicitly null-out the allAccessKeyNames field. + *******************************************************************************/ + static void unsetAllAccessKeyNames() + { + allAccessKeyNames = null; + } + +} 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 91210020..362c9415 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 @@ -72,6 +72,7 @@ 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.security.QSecurityKeyType; import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; 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; @@ -491,6 +492,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface return (true); } + if(session instanceof QSystemUserSession) + { + return (true); + } + if(session == null) { return (false); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/TableBasedAuthenticationModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/TableBasedAuthenticationModule.java index f047caaf..541b7d69 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/TableBasedAuthenticationModule.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/TableBasedAuthenticationModule.java @@ -51,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.authentication.TableBasedAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; import com.kingsrook.qqq.backend.core.model.session.QUser; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider; @@ -256,6 +257,11 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte return (true); } + if(session instanceof QSystemUserSession) + { + return (true); + } + if(session == null) { return (false); diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSessionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSessionTest.java new file mode 100644 index 00000000..9adf606c --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/session/QSystemUserSessionTest.java @@ -0,0 +1,88 @@ +/* + * 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.model.session; + + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.FullyAnonymousAuthenticationModule; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.MockAuthenticationModule; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.TableBasedAuthenticationModule; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for QSystemUserSession + *******************************************************************************/ +class QSystemUserSessionTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + QSystemUserSession systemUserSession = new QSystemUserSession(); + + assertEquals(List.of(true), systemUserSession.getSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS)); + + assertTrue(new Auth0AuthenticationModule().isSessionValid(QContext.getQInstance(), systemUserSession)); + assertTrue(new TableBasedAuthenticationModule().isSessionValid(QContext.getQInstance(), systemUserSession)); + assertTrue(new MockAuthenticationModule().isSessionValid(QContext.getQInstance(), systemUserSession)); + assertTrue(new FullyAnonymousAuthenticationModule().isSessionValid(QContext.getQInstance(), systemUserSession)); + + assertTrue(systemUserSession.hasPermission(null)); + assertTrue(systemUserSession.hasPermission("")); + assertTrue(systemUserSession.hasPermission("anything")); + assertTrue(systemUserSession.hasPermission(UUID.randomUUID().toString())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testWeDoNotBlowUpIfInstanceIsntInContextWhenPrimingAllAccessKeyNames() + { + QInstance qInstance = QContext.getQInstance(); + QSystemUserSession.unsetAllAccessKeyNames(); + + QContext.clear(); + QSystemUserSession systemUserSession = new QSystemUserSession(); + assertEquals(Collections.emptyList(), systemUserSession.getSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS)); + + QContext.setQInstance(qInstance); + systemUserSession = new QSystemUserSession(); + assertEquals(List.of(true), systemUserSession.getSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS)); + } + +} \ No newline at end of file