From da0f8a051654fdbe9b09d696771170b60da108cf Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Sep 2023 16:03:24 +0200 Subject: [PATCH 01/39] [Fix] Fix persisting user after password update --- .../privilege/handler/DefaultPrivilegeHandler.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 0bf3853a3..8676b88bc 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -1754,19 +1754,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PasswordCrypt newPasswordCrypt = this.encryptionHandler.hashPassword(password, salt); // create new user - User newUser = new User(user.getUserId(), user.getUsername(), newPasswordCrypt, user.getFirstname(), + user = new User(user.getUserId(), user.getUsername(), newPasswordCrypt, user.getFirstname(), user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties(), user.isPasswordChangeRequested(), user.getHistory().getClone()); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + this.persistenceHandler.replaceUser(user); - // perform automatic persisting, if enabled - if (this.autoPersistOnUserChangesData) { - this.persistenceHandler.persist(); - } - - logger.info("Updated password for " + newUser.getUsername()); + logger.info("Updated password for " + user.getUsername()); } return user; From e5cd14ac17c9da0a8d9aa1587ad1d25db40821ec Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Sep 2023 16:03:43 +0200 Subject: [PATCH 02/39] [Minor] Persist user model async --- .../handler/DefaultPrivilegeHandler.java | 86 +++++++++++-------- .../handler/XmlPersistenceHandler.java | 79 ++++++++--------- 2 files changed, 86 insertions(+), 79 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 8676b88bc..64a251010 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -147,6 +147,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private ElementLockingHandler lockingHandler; private ScheduledExecutorService executorService; private Future persistSessionsTask; + private Future persistModelTask; @Override public SingleSignOnHandler getSsoHandler() { @@ -395,10 +396,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER); // make sure userId is not set - if (isNotEmpty(userRepParam.getUserId())) { - String msg = "UserId can not be set when adding a new user!"; - throw new PrivilegeModelException(format(msg, userRepParam.getUsername())); - } + if (isNotEmpty(userRepParam.getUserId())) + throw new PrivilegeModelException("UserId can not be set when adding a new user!"); UserRep userRep = userRepParam.getCopy(); @@ -443,6 +442,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addUser(newUser); + persistModelAsync(); logger.info("Created new user " + newUser.getUsername()); @@ -527,6 +527,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler toCreate.forEach(user -> this.persistenceHandler.addUser(user)); toUpdate.forEach(user -> this.persistenceHandler.replaceUser(user)); + persistModelAsync(); logger.info("Created " + toCreate.size() + " users"); logger.info("Updated " + toUpdate.size() + " users"); @@ -590,6 +591,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); logger.info("Replaced user " + newUser.getUsername()); @@ -686,6 +688,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); logger.info("Updated user " + newUser.getUsername()); @@ -719,6 +722,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user removal to persistence handler invalidSessionsFor(existingUser); this.persistenceHandler.removeUser(username); + persistModelAsync(); logger.info("Removed user " + username); @@ -772,6 +776,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); logger.info("Added role " + roleName + " to " + newUser.getUsername()); @@ -819,6 +824,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); logger.info("Removed role " + roleName + " from " + newUser.getUsername()); @@ -858,11 +864,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); - - // perform automatic persisting, if enabled - if (this.autoPersistOnUserChangesData) { - this.persistenceHandler.persist(); - } + persistModelAsync(); logger.info("Set locale to " + locale + " for " + newUser.getUsername()); @@ -896,11 +898,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); - - // perform automatic persisting, if enabled - if (this.autoPersistOnUserChangesData) { - this.persistenceHandler.persist(); - } + persistModelAsync(); logger.info("Requiring user " + newUser.getUsername() + " to change their password on next login."); } @@ -956,15 +954,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); - // perform automatic persisting, if enabled - if (this.autoPersistOnUserChangesData) { - this.persistenceHandler.persist(); - } - - if (certificate.getUsage() == Usage.SET_PASSWORD) { + if (certificate.getUsage() == Usage.SET_PASSWORD) invalidate(certificate); - } if (password == null) logger.info("Cleared password for " + newUser.getUsername()); @@ -1004,6 +997,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + persistModelAsync(); logger.info("Set state of user " + newUser.getUsername() + " to " + state); @@ -1042,6 +1036,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addRole(newRole); + persistModelAsync(); logger.info("Added new role " + newRole.getName()); @@ -1084,6 +1079,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.replaceRole(newRole); + persistModelAsync(); logger.info("Replaced role " + newRole.getName()); @@ -1127,6 +1123,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role removal to persistence handler this.persistenceHandler.removeRole(roleName); + persistModelAsync(); logger.info("Removed role " + roleName); @@ -1189,6 +1186,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + persistModelAsync(); logger.info("Added/replaced privilege " + privilegeRep.getName() + " to " + roleName); @@ -1239,6 +1237,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + persistModelAsync(); logger.info("Removed privilege " + privilegeName + " from " + roleName); @@ -1264,6 +1263,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(cert.getSessionId(), privilegeContext); } } + + persistSessionsAsync(); } /** @@ -1286,6 +1287,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(cert.getSessionId(), privilegeContext); } } + + persistSessionsAsync(); } private void invalidSessionsFor(User user) { @@ -1360,7 +1363,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { " to " + source); } - persistSessions(); + persistSessionsAsync(); logger.info(format("Challenge validated for user {0} with usage {1}", username, usage)); return certificate; @@ -1418,15 +1421,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); - persistSessions(); + persistSessionsAsync(); // save last login if (user.getHistory().isFirstLoginEmpty()) user.getHistory().setFirstLogin(ZonedDateTime.now()); user.getHistory().setLastLogin(ZonedDateTime.now()); this.persistenceHandler.replaceUser(user); - if (this.autoPersistOnUserChangesData) - this.persistenceHandler.persist(); + persistModelAsync(); // log logger.info(format("User {0} authenticated: {1}", username, certificate)); @@ -1478,9 +1480,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getHistory().setFirstLogin(internalUser.getHistory().getFirstLogin()); this.persistenceHandler.replaceUser(user); } - - if (this.autoPersistOnUserChangesData) - this.persistenceHandler.persist(); + persistModelAsync(); // get 2 auth tokens String authToken = this.encryptionHandler.nextToken(); @@ -1495,7 +1495,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); - persistSessions(); + persistSessionsAsync(); // log logger.info(format("User {0} authenticated: {1}", user.getUsername(), certificate)); @@ -1546,8 +1546,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // invalidate the previous session invalidate(certificate); - persistSessions(); - // log logger.info(format("User {0} refreshed session: {1}", user.getUsername(), refreshedCert)); @@ -1573,7 +1571,24 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLocale(), userRoles, new HashMap<>(user.getProperties())); } - private boolean persistSessions() { + private synchronized void persistModelAsync() { + if (!this.autoPersistOnUserChangesData) + return; + + // async execution, max. once per second + if (this.persistModelTask != null) + this.persistModelTask.cancel(true); + this.persistModelTask = this.executorService.schedule( + () -> this.lockingHandler.lockedExecute("persist-model", () -> { + try { + this.persistenceHandler.persist(); + } catch (Exception e) { + logger.error("Failed to persist model!", e); + } + }), 1, TimeUnit.SECONDS); + } + + private synchronized boolean persistSessionsAsync() { if (!this.persistSessions) return false; @@ -1759,8 +1774,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.isPasswordChangeRequested(), user.getHistory().getClone()); // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); this.persistenceHandler.replaceUser(user); + persistModelAsync(); logger.info("Updated password for " + user.getUsername()); } @@ -1854,7 +1869,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // persist sessions if (privilegeContext != null) - persistSessions(); + persistSessionsAsync(); // return true if object was really removed boolean loggedOut = privilegeContext != null; @@ -1970,6 +1985,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext prvCtx = validate(certificate); prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST)); + // persist non async return this.persistenceHandler.persist(); } @@ -1980,7 +1996,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext prvCtx = validate(certificate); prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST_SESSIONS)); - return persistSessions(); + return persistSessionsAsync(); } @Override diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 88bd6b827..b0bb92f76 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -15,17 +15,6 @@ */ package li.strolch.privilege.handler; -import static java.text.MessageFormat.format; -import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_CASE_INSENSITIVE_USERNAME; -import static li.strolch.privilege.helper.XmlConstants.*; -import static li.strolch.utils.helper.StringHelper.formatNanoDuration; - -import java.io.File; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.helper.XmlConstants; import li.strolch.privilege.model.internal.Role; @@ -34,11 +23,22 @@ import li.strolch.privilege.xml.PrivilegeRolesDomWriter; import li.strolch.privilege.xml.PrivilegeRolesSaxReader; import li.strolch.privilege.xml.PrivilegeUsersDomWriter; import li.strolch.privilege.xml.PrivilegeUsersSaxReader; -import li.strolch.utils.helper.StringHelper; import li.strolch.utils.helper.XmlHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static java.text.MessageFormat.format; +import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_CASE_INSENSITIVE_USERNAME; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.utils.helper.StringHelper.formatNanoDuration; +import static li.strolch.utils.helper.StringHelper.isEmpty; + /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed * in {@link #initialize(Map)} @@ -167,39 +167,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { throw new PrivilegeException(msg); } - // get users file name - String usersFileName = this.parameterMap.get(XML_PARAM_USERS_FILE); - if (StringHelper.isEmpty(usersFileName)) { - String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_USERS_FILE); - throw new PrivilegeException(msg); - } + // get users file path + File usersPath = getFile(basePath, XML_PARAM_USERS_FILE); - // get roles file name - String rolesFileName = this.parameterMap.get(XML_PARAM_ROLES_FILE); - if (StringHelper.isEmpty(rolesFileName)) { - String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_ROLES_FILE); - throw new PrivilegeException(msg); - } - - // validate users file exists - String usersPathS = basePath + "/" + usersFileName; - File usersPath = new File(usersPathS); - if (!usersPath.exists()) { - String msg = "[{0}] Defined parameter {1} is invalid as users file does not exist at path {2}"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_USERS_FILE, usersPath.getAbsolutePath()); - throw new PrivilegeException(msg); - } - - // validate roles file exists - String rolesPathS = basePath + "/" + rolesFileName; - File rolesPath = new File(rolesPathS); - if (!rolesPath.exists()) { - String msg = "[{0}] Defined parameter {1} is invalid as roles file does not exist at path {2}"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_ROLES_FILE, rolesPath.getAbsolutePath()); - throw new PrivilegeException(msg); - } + // get roles file path + File rolesPath = getFile(basePath, XML_PARAM_ROLES_FILE); // save path to model this.usersPath = usersPath; @@ -211,6 +183,25 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info("Privilege Data loaded."); } + private File getFile(String basePath, String param) { + String fileName = this.parameterMap.get(param); + if (isEmpty(fileName)) { + String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; + msg = format(msg, PersistenceHandler.class.getName(), param); + throw new PrivilegeException(msg); + } + + String path = basePath + "/" + fileName; + File file = new File(path); + if (!file.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as file does not exist at path {2}"; + msg = format(msg, PersistenceHandler.class.getName(), param, file.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + return file; + } + /** * Reads the XML configuration files which contain the model. Which configuration files are parsed was defined in * the while calling {@link #initialize(Map)} From 57d61887e36e31e6c6d44c102ede94e6a8a2332a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 13:39:46 +0200 Subject: [PATCH 03/39] [Minor] Set default value for privilegeConflictResolution=MERGE --- .../li/strolch/privilege/handler/DefaultPrivilegeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 64a251010..d65e43d04 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -2133,7 +2133,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private void handleConflictResolutionParam(Map parameterMap) { String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION); if (privilegeConflictResolutionS == null) { - this.privilegeConflictResolution = PrivilegeConflictResolution.STRICT; + this.privilegeConflictResolution = PrivilegeConflictResolution.MERGE; String msg = "No {0} parameter defined. Using {1}"; msg = format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, this.privilegeConflictResolution); logger.info(msg); From 080e2daacc562eebb055ada5b834b900c27fd559 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 13:42:12 +0200 Subject: [PATCH 04/39] [Project] Remove default privilegeConflictResolution value --- agent/src/test/resources/emptytest/config/PrivilegeConfig.xml | 1 - agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml | 1 - agent/src/test/resources/realmtest/config/PrivilegeConfig.xml | 1 - .../src/test/resources/transienttest/config/PrivilegeConfig.xml | 1 - .../src/test/resources/versioningtest/config/PrivilegeConfig.xml | 1 - .../test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml | 1 - .../cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml | 1 - .../src/test/resources/cachedRuntime/config/PrivilegeConfig.xml | 1 - .../resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml | 1 - .../src/test/resources/realmtest/config/PrivilegeConfig.xml | 1 - .../src/test/resources/cachedRuntime/config/PrivilegeConfig.xml | 1 - .../test/resources/existingDbRuntime/config/PrivilegeConfig.xml | 1 - .../src/test/resources/executiontest/config/PrivilegeConfig.xml | 1 - .../src/test/resources/migrationstest/config/PrivilegeConfig.xml | 1 - service/src/test/resources/reporttest/config/PrivilegeConfig.xml | 1 - service/src/test/resources/svctest/config/PrivilegeConfig.xml | 1 - .../src/test/resources/transienttest/config/PrivilegeConfig.xml | 1 - .../resources/withPrivilegeRuntime/config/PrivilegeConfig.xml | 1 - .../src/runtime_postgresql/config/PrivilegeConfig.xml | 1 - .../src/runtime_postgresql_json/config/PrivilegeConfig.xml | 1 - .../src/runtime_postgresql_versioning/config/PrivilegeConfig.xml | 1 - .../src/runtime_transient/config/PrivilegeConfig.xml | 1 - test-performance/src/runtime_xml/config/PrivilegeConfig.xml | 1 - .../resources/withPrivilegeRuntime/config/PrivilegeConfig.xml | 1 - 24 files changed, 24 deletions(-) diff --git a/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml b/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml b/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml b/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml b/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml b/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml index 4acca2bcd..5cc637ba8 100644 --- a/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml b/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml +++ b/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml b/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml +++ b/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/executiontest/config/PrivilegeConfig.xml b/service/src/test/resources/executiontest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/executiontest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/executiontest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml b/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/reporttest/config/PrivilegeConfig.xml b/service/src/test/resources/reporttest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/reporttest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/reporttest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/svctest/config/PrivilegeConfig.xml b/service/src/test/resources/svctest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/svctest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/svctest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/transienttest/config/PrivilegeConfig.xml b/service/src/test/resources/transienttest/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/test-performance/src/runtime_postgresql/config/PrivilegeConfig.xml b/test-performance/src/runtime_postgresql/config/PrivilegeConfig.xml index 9cdb99fa5..31f6561d6 100644 --- a/test-performance/src/runtime_postgresql/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_postgresql/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/test-performance/src/runtime_postgresql_json/config/PrivilegeConfig.xml b/test-performance/src/runtime_postgresql_json/config/PrivilegeConfig.xml index 9cdb99fa5..31f6561d6 100644 --- a/test-performance/src/runtime_postgresql_json/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_postgresql_json/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml b/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml index afa9daf94..a67d6c0a7 100644 --- a/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/test-performance/src/runtime_transient/config/PrivilegeConfig.xml b/test-performance/src/runtime_transient/config/PrivilegeConfig.xml index 9cdb99fa5..31f6561d6 100644 --- a/test-performance/src/runtime_transient/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_transient/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/test-performance/src/runtime_xml/config/PrivilegeConfig.xml b/test-performance/src/runtime_xml/config/PrivilegeConfig.xml index 9cdb99fa5..31f6561d6 100644 --- a/test-performance/src/runtime_xml/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_xml/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - diff --git a/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index d04da193e..0b193071f 100644 --- a/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -8,7 +8,6 @@ - From 17d9ffb245d195dd0df3a3145db50be851d755bf Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 13:45:49 +0200 Subject: [PATCH 05/39] [Minor] Set default value for caseInsensitiveUsername=true --- .../li/strolch/privilege/handler/XmlPersistenceHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index b0bb92f76..1df0bdd06 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static java.lang.Boolean.*; import static java.text.MessageFormat.format; import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_CASE_INSENSITIVE_USERNAME; import static li.strolch.privilege.helper.XmlConstants.*; @@ -177,7 +178,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.usersPath = usersPath; this.rolesPath = rolesPath; - this.caseInsensitiveUsername = Boolean.parseBoolean(this.parameterMap.get(PARAM_CASE_INSENSITIVE_USERNAME)); + this.caseInsensitiveUsername = !this.parameterMap.containsKey(PARAM_CASE_INSENSITIVE_USERNAME) || + parseBoolean(this.parameterMap.get(PARAM_CASE_INSENSITIVE_USERNAME)); if (reload()) logger.info("Privilege Data loaded."); From 2f864936718a419fdd936894f0bb6eff68df2b9a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 13:57:39 +0200 Subject: [PATCH 06/39] [Project] Using default values for PrivilegeUsers.xml and PrivilegeRoles.xml --- .../privilege/handler/XmlPersistenceHandler.java | 13 +++++++------ .../li/strolch/privilege/helper/XmlConstants.java | 10 ++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 1df0bdd06..8e3f9cd44 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -169,10 +169,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get users file path - File usersPath = getFile(basePath, XML_PARAM_USERS_FILE); + File usersPath = getFile(basePath, XML_PARAM_USERS_FILE, XML_PARAM_USERS_FILE_DEF); // get roles file path - File rolesPath = getFile(basePath, XML_PARAM_ROLES_FILE); + File rolesPath = getFile(basePath, XML_PARAM_ROLES_FILE, XML_PARAM_ROLES_FILE_DEF); // save path to model this.usersPath = usersPath; @@ -185,12 +185,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info("Privilege Data loaded."); } - private File getFile(String basePath, String param) { + private File getFile(String basePath, String param, String defaultValue) { String fileName = this.parameterMap.get(param); if (isEmpty(fileName)) { - String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; - msg = format(msg, PersistenceHandler.class.getName(), param); - throw new PrivilegeException(msg); + fileName = defaultValue; + String msg = "[{0}] Parameter {1} is not defined, using default {2}!"; + msg = format(msg, PersistenceHandler.class.getName(), param, defaultValue); + logger.warn(msg); } String path = basePath + "/" + fileName; diff --git a/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java b/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java index 31ba2c3c9..71d6a620c 100644 --- a/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java +++ b/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java @@ -308,11 +308,21 @@ public class XmlConstants { */ public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + /** + * XML_PARAM_USERS_FILE_DEF = "PrivilegeUsers.xml" : + */ + public static final String XML_PARAM_USERS_FILE_DEF = "PrivilegeUsers.xml"; + /** * XML_PARAM_ROLES_FILE = "rolesXmlFile" : */ public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + /** + * XML_PARAM_ROLES_FILE_DEF = "PrivilegeRoles.xml" : + */ + public static final String XML_PARAM_ROLES_FILE_DEF = "PrivilegeRoles.xml"; + /** * XML_PARAM_BASE_PATH = "basePath" : */ From 7cea2fd6f3642282123814da5b901da145eb2a9e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 14:05:18 +0200 Subject: [PATCH 07/39] [Project] Updated PrivilegeConfig.xml with default values --- .../emptytest/config/PrivilegeConfig.xml | 27 ++------------ .../minimaltest/config/PrivilegeConfig.xml | 27 ++------------ .../realmtest/config/PrivilegeConfig.xml | 27 ++------------ .../transienttest/config/PrivilegeConfig.xml | 27 ++------------ .../versioningtest/config/PrivilegeConfig.xml | 27 ++------------ .../config/PrivilegeConfig.xml | 35 ++++-------------- .../config/PrivilegeConfig.xml | 27 ++------------ .../cachedRuntime/config/PrivilegeConfig.xml | 27 ++------------ .../config/PrivilegeConfig.xml | 27 ++------------ .../realmtest/config/PrivilegeConfig.xml | 27 ++------------ .../cachedRuntime/config/PrivilegeConfig.xml | 27 ++------------ .../config/PrivilegeConfig.xml | 27 ++------------ .../executiontest/config/PrivilegeConfig.xml | 27 ++------------ .../migrationstest/config/PrivilegeConfig.xml | 27 ++------------ .../reporttest/config/PrivilegeConfig.xml | 27 ++------------ .../svctest/config/PrivilegeConfig.xml | 27 ++------------ .../transienttest/config/PrivilegeConfig.xml | 27 ++------------ .../config/PrivilegeConfig.xml | 27 ++------------ .../config/PrivilegeConfig.xml | 37 +++++-------------- .../config/PrivilegeConfig.xml | 27 ++------------ 20 files changed, 89 insertions(+), 469 deletions(-) diff --git a/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml b/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/emptytest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml b/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml b/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml b/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml b/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml +++ b/agent/src/test/resources/versioningtest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml index 5cc637ba8..b2a9b4bf6 100644 --- a/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedJsonRuntime/config/PrivilegeConfig.xml @@ -1,45 +1,26 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - - - - - + + + + - \ No newline at end of file diff --git a/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedJsonRuntimeVersioning/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/cachedRuntimeVersioning/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml b/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/persistence-postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml b/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml +++ b/persistence-xml/src/test/resources/cachedRuntime/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml b/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml +++ b/persistence-xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/executiontest/config/PrivilegeConfig.xml b/service/src/test/resources/executiontest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/executiontest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/executiontest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml b/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/migrationstest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/reporttest/config/PrivilegeConfig.xml b/service/src/test/resources/reporttest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/reporttest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/reporttest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/svctest/config/PrivilegeConfig.xml b/service/src/test/resources/svctest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/svctest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/svctest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/transienttest/config/PrivilegeConfig.xml b/service/src/test/resources/transienttest/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/service/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file diff --git a/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml b/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml index a67d6c0a7..b2a9b4bf6 100644 --- a/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml +++ b/test-performance/src/runtime_postgresql_versioning/config/PrivilegeConfig.xml @@ -1,45 +1,26 @@ - - - - - - + + + - - - - - - - - - - - - - - - - + + - - - - - + + + + - \ No newline at end of file diff --git a/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index 0b193071f..b2a9b4bf6 100644 --- a/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/web-rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -1,39 +1,21 @@ - - - - - + + - - - - - - - - - - - - - - - - + + - @@ -41,5 +23,4 @@ - \ No newline at end of file From 189c97a1118d0e9e23dbf310e18031734c3bef9f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 14:56:48 +0200 Subject: [PATCH 08/39] [Fix] Fixed wrong check on users/roles config property --- .../handler/XmlPersistenceHandler.java | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 8e3f9cd44..08300b20f 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -32,8 +32,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; -import static java.lang.Boolean.*; +import static java.lang.Boolean.parseBoolean; import static java.text.MessageFormat.format; import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_CASE_INSENSITIVE_USERNAME; import static li.strolch.privilege.helper.XmlConstants.*; @@ -259,28 +260,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public boolean persist() { - long start = System.nanoTime(); - - // get users file name - String usersFileName = this.parameterMap.get(XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - String msg = "[{0}] Defined parameter {1} is invalid"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_USERS_FILE); - throw new PrivilegeException(msg); - } - - // get roles file name - String rolesFileName = this.parameterMap.get(XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - String msg = "[{0}] Defined parameter {1} is invalid"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_ROLES_FILE); - throw new PrivilegeException(msg); - } - boolean saved = false; - // get users file + // write users file if (this.userMapDirty) { // delegate writing PrivilegeUsersDomWriter modelWriter = new PrivilegeUsersDomWriter(getAllUsers(), this.usersPath); @@ -290,7 +273,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { saved = true; } - // get roles file + // write roles file if (this.roleMapDirty) { // delegate writing PrivilegeRolesDomWriter modelWriter = new PrivilegeRolesDomWriter(getAllRoles(), this.rolesPath); From d9daad36f2a5650bac3905e7825f4714f1f511bc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Sep 2023 14:57:18 +0200 Subject: [PATCH 09/39] [Minor] Only show privilege duration if > 100ms --- .../li/strolch/privilege/handler/XmlPersistenceHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 08300b20f..93158a985 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -283,7 +283,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { saved = true; } - logger.info("Persist took " + (formatNanoDuration(System.nanoTime() - start))); + long tookNanos = System.nanoTime() - start; + if (TimeUnit.NANOSECONDS.toMillis(tookNanos) > 100) + logger.warn("Persist took " + (formatNanoDuration(tookNanos))); return saved; } } From f1dde73da16987ba762ebed7787253e1ef456f38 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 19 Sep 2023 09:48:20 +0200 Subject: [PATCH 10/39] [Minor] Logger is not error --- .../java/li/strolch/execution/EventBasedExecutionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java b/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java index 9452a99dc..3506558bb 100644 --- a/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java +++ b/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java @@ -157,7 +157,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { synchronized (this.controllers) { Map map = this.controllers.getMap(realm); if (map == null) { - logger.error("No controllers for realm " + realm); + logger.info("No controllers for realm " + realm); return; } From 96b24ca03d3b7d0725ea5cfc613d8e861d4643a8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 19 Sep 2023 09:48:37 +0200 Subject: [PATCH 11/39] [Minor] code cleanup --- .../li/strolch/utils/helper/XmlHelper.java | 124 +++++++----------- 1 file changed, 46 insertions(+), 78 deletions(-) diff --git a/utils/src/main/java/li/strolch/utils/helper/XmlHelper.java b/utils/src/main/java/li/strolch/utils/helper/XmlHelper.java index 76d9fbb93..06a6f48ba 100644 --- a/utils/src/main/java/li/strolch/utils/helper/XmlHelper.java +++ b/utils/src/main/java/li/strolch/utils/helper/XmlHelper.java @@ -15,12 +15,21 @@ */ package li.strolch.utils.helper; -import static li.strolch.utils.helper.FileHelper.getTempFile; - import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.Marshaller; import jakarta.xml.bind.Unmarshaller; import jakarta.xml.bind.annotation.XmlRootElement; +import li.strolch.utils.RemoveCRFilterWriter; +import li.strolch.utils.exceptions.XmlException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + import javax.xml.parsers.*; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; @@ -33,16 +42,7 @@ import java.nio.file.Files; import java.text.MessageFormat; import java.util.Set; -import li.strolch.utils.RemoveCRFilterWriter; -import li.strolch.utils.exceptions.XmlException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.DOMException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; +import static li.strolch.utils.helper.FileHelper.getTempFile; /** * Helper class for performing XML based tasks @@ -71,8 +71,7 @@ public class XmlHelper { /** * Parses an XML file on the file system and returns the resulting {@link Document} object * - * @param xmlFile - * the {@link File} which has the path to the XML file to read + * @param xmlFile the {@link File} which has the path to the XML file to read */ public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { @@ -92,8 +91,7 @@ public class XmlHelper { /** * Parses an XML file on the file system and returns the resulting {@link Document} object * - * @param xmlFileInputStream - * the XML {@link InputStream} which is to be parsed + * @param xmlFileInputStream the XML {@link InputStream} which is to be parsed */ public static void parseDocument(InputStream xmlFileInputStream, DefaultHandler xmlHandler) { @@ -116,8 +114,7 @@ public class XmlHelper { /** * Parses an XML file on the file system and returns the resulting {@link Document} object * - * @param xmlInputSource - * the XML {@link InputSource} which is to be parsed + * @param xmlInputSource the XML {@link InputSource} which is to be parsed */ public static void parseDocument(InputSource xmlInputSource, DefaultHandler xmlHandler) { parseDocument(xmlInputSource, xmlHandler, false); @@ -126,10 +123,8 @@ public class XmlHelper { /** * Parses an XML file on the file system and returns the resulting {@link Document} object * - * @param xmlInputSource - * the XML {@link InputSource} which is to be parsed - * @param nsAware - * if true, then calls {@link SAXParserFactory#setNamespaceAware(boolean)} + * @param xmlInputSource the XML {@link InputSource} which is to be parsed + * @param nsAware if true, then calls {@link SAXParserFactory#setNamespaceAware(boolean)} */ public static void parseDocument(InputSource xmlInputSource, DefaultHandler xmlHandler, boolean nsAware) { @@ -156,12 +151,9 @@ public class XmlHelper { * *

Note: The passed class must have the {@link XmlRootElement} annotation!

* - * @param file - * the file to parse - * @param clazz - * the class for the returning object type - * @param - * the type of object to return + * @param file the file to parse + * @param clazz the class for the returning object type + * @param the type of object to return * * @return the parsed object */ @@ -171,8 +163,7 @@ public class XmlHelper { JAXBContext jaxbContext = JAXBContext.newInstance(clazz); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - @SuppressWarnings("unchecked") - T o = (T) unmarshaller.unmarshal(fin); + @SuppressWarnings("unchecked") T o = (T) unmarshaller.unmarshal(fin); return o; @@ -185,13 +176,11 @@ public class XmlHelper { /** * Writes an {@link Element} to an XML file on the file system * - * @param rootElement - * the {@link Element} to write to the file system - * @param file - * the {@link File} describing the path on the file system where the XML file should be written to + * @param rootElement the {@link Element} to write to the file system + * @param file the {@link File} describing the path on the file system where the XML file should be written + * to * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static void writeElement(Element rootElement, File file) throws RuntimeException { Document document = createDocument(); @@ -202,13 +191,10 @@ public class XmlHelper { /** * Writes a {@link Document} to an XML file on the file system * - * @param document - * the {@link Document} to write to the file system - * @param file - * the {@link File} describing the path on the file system where the XML file should be written to + * @param document the {@link Document} to write to the file system + * @param file the {@link File} describing the path on the file system where the XML file should be written to * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static void writeDocument(Document document, File file) throws RuntimeException { writeDocument(document, file, DEFAULT_ENCODING); @@ -217,15 +203,11 @@ public class XmlHelper { /** * Writes a {@link Document} to an XML file on the file system * - * @param document - * the {@link Document} to write to the file system - * @param file - * the {@link File} describing the path on the file system where the XML file should be written to - * @param encoding - * encoding to use to write the file + * @param document the {@link Document} to write to the file system + * @param file the {@link File} describing the path on the file system where the XML file should be written to + * @param encoding encoding to use to write the file * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static void writeDocument(Document document, File file, String encoding) throws RuntimeException { try (FileOutputStream out = new FileOutputStream(file)) { @@ -239,13 +221,10 @@ public class XmlHelper { /** * Writes a {@link Document} to an XML file on the file system * - * @param document - * the {@link Document} to write to the file system - * @param out - * stream to write document to + * @param document the {@link Document} to write to the file system + * @param out stream to write document to * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static void writeDocument(Document document, OutputStream out) throws RuntimeException { writeDocument(document, new StreamResult(new RemoveCRFilterWriter(new OutputStreamWriter(out))), @@ -255,11 +234,9 @@ public class XmlHelper { /** * Writes a {@link Document} to an XML file on the file system * - * @param document - * the {@link Document} to write to the file system + * @param document the {@link Document} to write to the file system * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static String writeToString(Document document) throws RuntimeException { try { @@ -275,15 +252,11 @@ public class XmlHelper { /** * Writes a {@link Document} to an XML file on the file system * - * @param document - * the {@link Document} to write to the file system - * @param streamResult - * the destination - * @param encoding - * encoding to use to write the file + * @param document the {@link Document} to write to the file system + * @param streamResult the destination + * @param encoding encoding to use to write the file * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element + * @throws RuntimeException if something went wrong while creating the XML configuration, or writing the element */ public static void writeDocument(Document document, StreamResult streamResult, String encoding) throws RuntimeException { @@ -307,8 +280,7 @@ public class XmlHelper { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.ENCODING, docEncoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", - "2"); //$NON-NLS-2$ + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-2$ // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); // Transform to file @@ -330,8 +302,7 @@ public class XmlHelper { * * @return a new document instance * - * @throws RuntimeException - * if something went wrong while creating the XML configuration + * @throws RuntimeException if something went wrong while creating the XML configuration */ public static Document createDocument() throws RuntimeException { try { @@ -354,13 +325,10 @@ public class XmlHelper { /** * Marshalls the given element annotated with xml annotations to the given destination file * - * @param dstFile - * the destination to marshall the object - * @param object - * the object to marshall + * @param dstFile the destination to marshall the object + * @param object the object to marshall * - * @throws Exception - * if the marshalling fails for any reason + * @throws Exception if the marshalling fails for any reason */ public static void marshall(File dstFile, Object object) throws Exception { From bcba0264e43049145f368e548054c793356b5a20 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 19 Sep 2023 09:49:35 +0200 Subject: [PATCH 12/39] [Major] Rewrote privilege XML to use SAX for writing and parsing --- .../handler/BaseLdapPrivilegeHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 16 +- .../privilege/handler/PersistenceHandler.java | 5 +- .../handler/XmlPersistenceHandler.java | 12 +- .../helper/WriteRolesFileHelper.java | 16 +- .../strolch/privilege/helper/XmlHelper.java | 67 +++++++ .../xml/CertificateStubsDomWriter.java | 87 --------- .../xml/CertificateStubsSaxReader.java | 68 ++++---- .../xml/CertificateStubsSaxWriter.java | 94 ++++++++++ .../strolch/privilege/xml/ElementParser.java | 1 - .../xml/PrivilegeConfigDomWriter.java | 129 -------------- .../xml/PrivilegeConfigSaxReader.java | 110 ++++++------ .../xml/PrivilegeConfigSaxWriter.java | 113 ++++++++++++ .../xml/PrivilegeRolesDomWriter.java | 87 --------- .../xml/PrivilegeRolesSaxReader.java | 96 +++++----- .../xml/PrivilegeRolesSaxWriter.java | 86 +++++++++ .../xml/PrivilegeUsersDomWriter.java | 165 ------------------ .../xml/PrivilegeUsersSaxReader.java | 39 +++-- .../xml/PrivilegeUsersSaxWriter.java | 144 +++++++++++++++ .../test/WriteRolesFileHelperTest.java | 7 +- .../li/strolch/privilege/test/XmlTest.java | 54 ++++-- 21 files changed, 738 insertions(+), 660 deletions(-) create mode 100644 privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 5075f8bf3..ed881f9bd 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -104,7 +104,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { this.persistenceHandler.replaceUser(user); if (this.autoPersistOnUserChangesData) - this.persistenceHandler.persist(); + persistModelAsync(); return user; diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index d65e43d04..538992091 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -19,9 +19,9 @@ import li.strolch.privilege.base.*; import li.strolch.privilege.model.*; import li.strolch.privilege.model.internal.*; import li.strolch.privilege.policy.PrivilegePolicy; -import li.strolch.privilege.xml.CertificateStubsDomWriter; import li.strolch.privilege.xml.CertificateStubsSaxReader; import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub; +import li.strolch.privilege.xml.CertificateStubsSaxWriter; import li.strolch.utils.collections.Tuple; import li.strolch.utils.concurrent.ElementLockingHandler; import li.strolch.utils.dbc.DBC; @@ -31,7 +31,9 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXParseException; import javax.crypto.SecretKey; +import javax.xml.stream.XMLStreamException; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; @@ -46,6 +48,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static java.text.MessageFormat.format; +import static java.util.Comparator.comparing; import static li.strolch.utils.helper.ExceptionHelper.getRootCause; import static li.strolch.utils.helper.StringHelper.*; @@ -336,6 +339,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { result.add(user.asUserRep()); } + result.sort(comparing(UserRep::getUsername)); return result; } @@ -1571,7 +1575,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLocale(), userRoles, new HashMap<>(user.getProperties())); } - private synchronized void persistModelAsync() { + protected synchronized void persistModelAsync() { if (!this.autoPersistOnUserChangesData) return; @@ -1605,7 +1609,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { try (OutputStream out = Files.newOutputStream(this.persistSessionsPath.toPath()); OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, out)) { - CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream); + CertificateStubsSaxWriter writer = new CertificateStubsSaxWriter(sessions, outputStream); writer.write(); outputStream.flush(); @@ -1986,7 +1990,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST)); // persist non async - return this.persistenceHandler.persist(); + try { + return this.persistenceHandler.persist(); + } catch (XMLStreamException | IOException e) { + throw new IllegalStateException("Failed to persist model", e); + } } @Override diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java index 6ae88ce01..d8318620a 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java @@ -15,6 +15,7 @@ */ package li.strolch.privilege.handler; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -24,6 +25,8 @@ import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.policy.PrivilegePolicy; +import javax.xml.stream.XMLStreamException; + /** *

* The {@link PersistenceHandler} takes care of retrieving and persisting model objects to the underlying database. This @@ -131,7 +134,7 @@ public interface PersistenceHandler { * * @return true if changes were persisted successfully, false if nothing needed to be persisted */ - boolean persist(); + boolean persist() throws XMLStreamException, IOException; /** * Informs this {@link PersistenceHandler} to reload the data from the backend diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 93158a985..c159ef6bf 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -19,15 +19,17 @@ import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.helper.XmlConstants; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; -import li.strolch.privilege.xml.PrivilegeRolesDomWriter; import li.strolch.privilege.xml.PrivilegeRolesSaxReader; -import li.strolch.privilege.xml.PrivilegeUsersDomWriter; +import li.strolch.privilege.xml.PrivilegeRolesSaxWriter; import li.strolch.privilege.xml.PrivilegeUsersSaxReader; +import li.strolch.privilege.xml.PrivilegeUsersSaxWriter; import li.strolch.utils.helper.XmlHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.xml.stream.XMLStreamException; import java.io.File; +import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -259,14 +261,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { * Writes the model to the XML files. Where the files are written to was defined in the {@link #initialize(Map)} */ @Override - public boolean persist() { + public boolean persist() throws XMLStreamException, IOException { long start = System.nanoTime(); boolean saved = false; // write users file if (this.userMapDirty) { // delegate writing - PrivilegeUsersDomWriter modelWriter = new PrivilegeUsersDomWriter(getAllUsers(), this.usersPath); + PrivilegeUsersSaxWriter modelWriter = new PrivilegeUsersSaxWriter(getAllUsers(), this.usersPath); modelWriter.write(); this.userMapDirty = false; @@ -276,7 +278,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write roles file if (this.roleMapDirty) { // delegate writing - PrivilegeRolesDomWriter modelWriter = new PrivilegeRolesDomWriter(getAllRoles(), this.rolesPath); + PrivilegeRolesSaxWriter modelWriter = new PrivilegeRolesSaxWriter(getAllRoles(), this.rolesPath); modelWriter.write(); this.roleMapDirty = false; diff --git a/privilege/src/main/java/li/strolch/privilege/helper/WriteRolesFileHelper.java b/privilege/src/main/java/li/strolch/privilege/helper/WriteRolesFileHelper.java index 941576dc4..342ae26f5 100644 --- a/privilege/src/main/java/li/strolch/privilege/helper/WriteRolesFileHelper.java +++ b/privilege/src/main/java/li/strolch/privilege/helper/WriteRolesFileHelper.java @@ -1,18 +1,20 @@ package li.strolch.privilege.helper; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.xml.PrivilegeRolesSaxReader; +import li.strolch.privilege.xml.PrivilegeRolesSaxWriter; +import li.strolch.utils.helper.XmlHelper; + +import javax.xml.stream.XMLStreamException; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import li.strolch.privilege.model.internal.Role; -import li.strolch.privilege.xml.PrivilegeRolesDomWriter; -import li.strolch.privilege.xml.PrivilegeRolesSaxReader; -import li.strolch.utils.helper.XmlHelper; - public class WriteRolesFileHelper { - public static void main(String[] args) { + public static void main(String[] args) throws XMLStreamException, IOException { if (args.length != 2) throw new IllegalStateException("Usage: "); @@ -31,7 +33,7 @@ public class WriteRolesFileHelper { Map rolesMap = xmlHandler.getRoles(); List roles = new ArrayList<>(rolesMap.values()); - PrivilegeRolesDomWriter configSaxWriter = new PrivilegeRolesDomWriter(roles, dst); + PrivilegeRolesSaxWriter configSaxWriter = new PrivilegeRolesSaxWriter(roles, dst); configSaxWriter.write(); } } diff --git a/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java b/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java new file mode 100644 index 000000000..d2425f7cf --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java @@ -0,0 +1,67 @@ +package li.strolch.privilege.helper; + +import javanet.staxutils.IndentingXMLStreamWriter; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static li.strolch.privilege.helper.XmlConstants.*; + +public class XmlHelper { + + public static void writeStringMapElement(XMLStreamWriter xmlWriter, Map parameterMap, + String elementName, String valueElementName) throws XMLStreamException { + writeStringMapElement(xmlWriter, parameterMap, elementName, valueElementName, XML_ATTR_VALUE); + } + + public static void writeStringMapElement(XMLStreamWriter xmlWriter, Map parameterMap, + String elementName, String valueElementName, String valueAttrName) throws XMLStreamException { + if (parameterMap == null || parameterMap.isEmpty()) + return; + + xmlWriter.writeStartElement(elementName); + + List propertyKeys = new ArrayList<>(parameterMap.keySet()); + propertyKeys.sort(null); + for (String propertyKey : propertyKeys) { + xmlWriter.writeEmptyElement(valueElementName); + xmlWriter.writeAttribute(XML_ATTR_NAME, propertyKey); + xmlWriter.writeAttribute(valueAttrName, parameterMap.get(propertyKey)); + } + + xmlWriter.writeEndElement(); + } + + public static void writeStringList(IndentingXMLStreamWriter xmlWriter, String elementName, Set values) + throws XMLStreamException { + List denyList = new ArrayList<>(values); + denyList.sort(null); + for (String value : denyList) { + writeStringElement(xmlWriter, elementName, value); + } + } + + public static void writeStringElement(IndentingXMLStreamWriter xmlWriter, String elementName, String value) + throws XMLStreamException { + xmlWriter.writeStartElement(elementName); + xmlWriter.writeCharacters(value); + xmlWriter.writeEndElement(); + } + + public static IndentingXMLStreamWriter openXmlStreamWriterDocument(Writer ioWriter) throws XMLStreamException { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + IndentingXMLStreamWriter xmlWriter = new IndentingXMLStreamWriter(factory.createXMLStreamWriter(ioWriter)); + xmlWriter.setIndent(" "); + + // create document root + xmlWriter.writeStartDocument(StandardCharsets.UTF_8.name(), "1.0"); + return xmlWriter; + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java deleted file mode 100644 index 293386de4..000000000 --- a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.xml; - -import static java.util.Comparator.comparing; -import static li.strolch.privilege.helper.XmlConstants.*; - -import java.io.OutputStream; -import java.util.List; - -import li.strolch.privilege.model.Certificate; -import li.strolch.utils.helper.XmlHelper; -import li.strolch.utils.iso8601.ISO8601; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -/** - * @author Robert von Burg - */ -public class CertificateStubsDomWriter { - - private final List certificates; - private final OutputStream outputStream; - - public CertificateStubsDomWriter(List certificates, OutputStream outputStream) { - this.certificates = certificates; - this.outputStream = outputStream; - } - - public void write() { - - // create document root - Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XML_ROOT_CERTIFICATES); - doc.appendChild(rootElement); - - this.certificates.stream().sorted(comparing(Certificate::getSessionId)).forEach(cert -> { - - // create the certificate element - Element certElement = doc.createElement(XML_CERTIFICATE); - rootElement.appendChild(certElement); - - // sessionId; - certElement.setAttribute(XML_ATTR_SESSION_ID, cert.getSessionId()); - - // usage; - certElement.setAttribute(XML_ATTR_USAGE, cert.getUsage().name()); - - // username; - certElement.setAttribute(XML_ATTR_USERNAME, cert.getUsername()); - - // authToken; - certElement.setAttribute(XML_ATTR_AUTH_TOKEN, cert.getAuthToken()); - - // source; - certElement.setAttribute(XML_ATTR_SOURCE, cert.getSource()); - - // locale; - certElement.setAttribute(XML_ATTR_LOCALE, cert.getLocale().toLanguageTag()); - - // loginTime; - certElement.setAttribute(XML_ATTR_LOGIN_TIME, ISO8601.toString(cert.getLoginTime())); - - // lastAccess; - certElement.setAttribute(XML_ATTR_LAST_ACCESS, ISO8601.toString(cert.getLastAccess())); - - // keepAlive; - certElement.setAttribute(XML_ATTR_KEEP_ALIVE, String.valueOf(cert.isKeepAlive())); - }); - - // write the container file to disk - XmlHelper.writeDocument(doc, this.outputStream); - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java index 5f7a3975b..e5f0c92f8 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java @@ -15,16 +15,6 @@ */ package li.strolch.privilege.xml; -import static li.strolch.privilege.handler.DefaultPrivilegeHandler.SOURCE_UNKNOWN; -import static li.strolch.privilege.helper.XmlConstants.*; -import static li.strolch.utils.helper.StringHelper.isEmpty; - -import java.io.InputStream; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.Usage; import li.strolch.utils.dbc.DBC; @@ -33,6 +23,16 @@ import li.strolch.utils.iso8601.ISO8601; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; +import java.io.InputStream; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static li.strolch.privilege.handler.DefaultPrivilegeHandler.SOURCE_UNKNOWN; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.utils.helper.StringHelper.isEmpty; + /** * @author Robert von Burg */ @@ -55,33 +55,27 @@ public class CertificateStubsSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (qName) { - case XML_ROOT_CERTIFICATES: - break; - case XML_CERTIFICATE: - - CertificateStub stub = new CertificateStub(); - stub.usage = Usage.valueOf(attributes.getValue(XML_ATTR_USAGE).trim()); - stub.sessionId = attributes.getValue(XML_ATTR_SESSION_ID).trim(); - stub.username = attributes.getValue(XML_ATTR_USERNAME).trim(); - stub.authToken = attributes.getValue(XML_ATTR_AUTH_TOKEN).trim(); - stub.source = attributes.getValue(XML_ATTR_SOURCE).trim(); - stub.locale = Locale.forLanguageTag(attributes.getValue(XML_ATTR_LOCALE).trim()); - stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME).trim()); - stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS).trim()); - stub.keepAlive = Boolean.parseBoolean(attributes.getValue(XML_ATTR_KEEP_ALIVE).trim()); - - DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId); - DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username); - DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken); - - if (isEmpty(stub.source)) - stub.source = SOURCE_UNKNOWN; - - this.stubs.add(stub); - break; - - default: - throw new PrivilegeException("Unhandled tag " + qName); + case XML_ROOT_CERTIFICATES -> { + } + case XML_CERTIFICATE -> { + CertificateStub stub = new CertificateStub(); + stub.usage = Usage.valueOf(attributes.getValue(XML_ATTR_USAGE).trim()); + stub.sessionId = attributes.getValue(XML_ATTR_SESSION_ID).trim(); + stub.username = attributes.getValue(XML_ATTR_USERNAME).trim(); + stub.authToken = attributes.getValue(XML_ATTR_AUTH_TOKEN).trim(); + stub.source = attributes.getValue(XML_ATTR_SOURCE).trim(); + stub.locale = Locale.forLanguageTag(attributes.getValue(XML_ATTR_LOCALE).trim()); + stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME).trim()); + stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS).trim()); + stub.keepAlive = Boolean.parseBoolean(attributes.getValue(XML_ATTR_KEEP_ALIVE).trim()); + DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId); + DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username); + DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken); + if (isEmpty(stub.source)) + stub.source = SOURCE_UNKNOWN; + this.stubs.add(stub); + } + default -> throw new PrivilegeException("Unhandled tag " + qName); } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java new file mode 100644 index 000000000..29aabba4c --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.iso8601.ISO8601; + +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Comparator.comparing; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.privilege.helper.XmlHelper.openXmlStreamWriterDocument; + +/** + * @author Robert von Burg + */ +public class CertificateStubsSaxWriter { + + private final List certificates; + private final OutputStream outputStream; + + public CertificateStubsSaxWriter(List certificates, OutputStream outputStream) { + this.certificates = certificates; + this.outputStream = outputStream; + } + + public void write() throws IOException, XMLStreamException { + + Writer ioWriter = new OutputStreamWriter(this.outputStream, StandardCharsets.UTF_8); + + IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); + xmlWriter.writeStartElement(XML_ROOT_CERTIFICATES); + + List certificates = new ArrayList<>(this.certificates); + certificates.sort(comparing(Certificate::getSessionId)); + for (Certificate cert : certificates) { + + // create the certificate element + xmlWriter.writeStartElement(XML_CERTIFICATE); + + // sessionId; + xmlWriter.writeAttribute(XML_ATTR_SESSION_ID, cert.getSessionId()); + + // usage; + xmlWriter.writeAttribute(XML_ATTR_USAGE, cert.getUsage().name()); + + // username; + xmlWriter.writeAttribute(XML_ATTR_USERNAME, cert.getUsername()); + + // authToken; + xmlWriter.writeAttribute(XML_ATTR_AUTH_TOKEN, cert.getAuthToken()); + + // source; + xmlWriter.writeAttribute(XML_ATTR_SOURCE, cert.getSource()); + + // locale; + xmlWriter.writeAttribute(XML_ATTR_LOCALE, cert.getLocale().toLanguageTag()); + + // loginTime; + xmlWriter.writeAttribute(XML_ATTR_LOGIN_TIME, ISO8601.toString(cert.getLoginTime())); + + // lastAccess; + xmlWriter.writeAttribute(XML_ATTR_LAST_ACCESS, ISO8601.toString(cert.getLastAccess())); + + // keepAlive; + xmlWriter.writeAttribute(XML_ATTR_KEEP_ALIVE, String.valueOf(cert.isKeepAlive())); + } + + // and now end + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java b/privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java index 1d66d3a7c..2b1c3b3e9 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java @@ -17,7 +17,6 @@ package li.strolch.privilege.xml; import org.xml.sax.Attributes; -// TODO write JavaDoc... public interface ElementParser { void startElement(String uri, String localName, String qName, Attributes attributes); diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java deleted file mode 100644 index eca2a0135..000000000 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.xml; - -import static li.strolch.privilege.helper.XmlConstants.*; - -import java.io.File; -import java.util.Map; -import java.util.Map.Entry; - -import li.strolch.privilege.model.internal.PrivilegeContainerModel; -import li.strolch.utils.helper.XmlHelper; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -/** - * @author Robert von Burg - */ -public class PrivilegeConfigDomWriter { - - private final PrivilegeContainerModel containerModel; - private final File configFile; - - /** - * - */ - public PrivilegeConfigDomWriter(PrivilegeContainerModel containerModel, File configFile) { - this.containerModel = containerModel; - this.configFile = configFile; - } - - /** - * - */ - public void write() { - - // create document root - Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XML_ROOT_PRIVILEGE); - doc.appendChild(rootElement); - - Element containerElement = doc.createElement(XML_CONTAINER); - rootElement.appendChild(containerElement); - - // create EncryptionHandler - Element encryptionHandlerElem = doc.createElement(XML_HANDLER_ENCRYPTION); - containerElement.appendChild(encryptionHandlerElem); - encryptionHandlerElem.setAttribute(XML_ATTR_CLASS, this.containerModel.getEncryptionHandlerClassName()); - // Parameters - fillParameterMap(doc, encryptionHandlerElem, this.containerModel.getEncryptionHandlerParameterMap()); - - // create PersistenceHandler - Element persistenceHandlerElem = doc.createElement(XML_HANDLER_PERSISTENCE); - containerElement.appendChild(persistenceHandlerElem); - persistenceHandlerElem.setAttribute(XML_ATTR_CLASS, this.containerModel.getPersistenceHandlerClassName()); - // Parameters - fillParameterMap(doc, persistenceHandlerElem, this.containerModel.getPersistenceHandlerParameterMap()); - - // Parameters - fillParameterMap(doc, containerElement, this.containerModel.getParameterMap()); - - // create UserChallengeHandler - Element userChallengeHandlerElem = doc.createElement(XML_HANDLER_USER_CHALLENGE); - containerElement.appendChild(userChallengeHandlerElem); - userChallengeHandlerElem.setAttribute(XML_ATTR_CLASS, this.containerModel.getUserChallengeHandlerClassName()); - // Parameters - fillParameterMap(doc, userChallengeHandlerElem, this.containerModel.getUserChallengeHandlerParameterMap()); - - // create SingleSignOnHandler - if (this.containerModel.getSsoHandlerClassName() != null) { - Element ssoHandlerElem = doc.createElement(XML_HANDLER_SSO); - containerElement.appendChild(ssoHandlerElem); - ssoHandlerElem.setAttribute(XML_ATTR_CLASS, this.containerModel.getSsoHandlerClassName()); - // Parameters - fillParameterMap(doc, ssoHandlerElem, this.containerModel.getSsoHandlerParameterMap()); - } - - // create PrivilegeHandler - if (this.containerModel.getSsoHandlerClassName() != null) { - Element privilegeHandlerElem = doc.createElement(XML_HANDLER_PRIVILEGE); - containerElement.appendChild(privilegeHandlerElem); - privilegeHandlerElem.setAttribute(XML_ATTR_CLASS, this.containerModel.getPrivilegeHandlerClassName()); - // Parameters - fillParameterMap(doc, privilegeHandlerElem, this.containerModel.getPrivilegeHandlerParameterMap()); - } - - // Policies - Element policiesElem = doc.createElement(XML_POLICIES); - rootElement.appendChild(policiesElem); - this.containerModel.getPolicies().entrySet().stream().sorted(Entry.comparingByKey()) - .forEach(entry -> { - Element policyElem = doc.createElement(XML_POLICY); - policyElem.setAttribute(XML_ATTR_NAME, entry.getKey()); - policyElem.setAttribute(XML_ATTR_CLASS, entry.getValue().getName()); - policiesElem.appendChild(policyElem); - }); - - // write the container file to disk - XmlHelper.writeDocument(doc, this.configFile); - } - - private void fillParameterMap(Document doc, Element parent, Map parameterMap) { - - if (parameterMap != null && !parameterMap.isEmpty()) { - Element parametersElement = doc.createElement(XML_PARAMETERS); - for (Entry entry : parameterMap.entrySet()) { - Element parameterElement = doc.createElement(XML_PARAMETER); - parameterElement.setAttribute(XML_ATTR_NAME, entry.getKey()); - parameterElement.setAttribute(XML_ATTR_VALUE, entry.getValue()); - parametersElement.appendChild(parameterElement); - } - - parent.appendChild(parametersElement); - } - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java index ef4ae6a3c..3e25295e8 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java @@ -15,18 +15,17 @@ */ package li.strolch.privilege.xml; -import static li.strolch.privilege.helper.XmlConstants.*; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.Map; -import li.strolch.privilege.helper.XmlConstants; -import li.strolch.privilege.model.internal.PrivilegeContainerModel; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; +import static li.strolch.privilege.helper.XmlConstants.*; /** * @author Robert von Burg @@ -48,12 +47,12 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { switch (qName) { - case XML_CONTAINER -> this.buildersStack.push(new ContainerParser()); - case XML_PARAMETERS -> this.buildersStack.push(new ParametersParser()); - case XML_POLICIES -> this.buildersStack.push(new PoliciesParser()); - default -> { - // nothing to do, probably handle on stack - } + case XML_CONTAINER -> this.buildersStack.push(new ContainerParser()); + case XML_PARAMETERS -> this.buildersStack.push(new ParametersParser()); + case XML_POLICIES -> this.buildersStack.push(new PoliciesParser()); + default -> { + // nothing to do, probably handle on stack + } } if (!this.buildersStack.isEmpty()) @@ -89,38 +88,38 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (qName) { - case XML_CONTAINER -> this.currentElement = qName; - case XML_HANDLER_ENCRYPTION -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setEncryptionHandlerClassName(className); - } - case XML_HANDLER_PASSWORD_STRENGTH -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setPasswordStrengthHandlerClassName(className); - } - case XML_HANDLER_PERSISTENCE -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setPersistenceHandlerClassName(className); - } - case XML_HANDLER_USER_CHALLENGE -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setUserChallengeHandlerClassName(className); - } - case XML_HANDLER_SSO -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setSsoHandlerClassName(className); - } - case XML_HANDLER_PRIVILEGE -> { - this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); - getContainerModel().setPrivilegeHandlerClassName(className); - } - default -> throw new IllegalStateException("Unexpected value: " + qName); + case XML_CONTAINER -> this.currentElement = qName; + case XML_HANDLER_PRIVILEGE -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setPrivilegeHandlerClassName(className); + } + case XML_HANDLER_ENCRYPTION -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setEncryptionHandlerClassName(className); + } + case XML_HANDLER_PASSWORD_STRENGTH -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setPasswordStrengthHandlerClassName(className); + } + case XML_HANDLER_PERSISTENCE -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setPersistenceHandlerClassName(className); + } + case XML_HANDLER_USER_CHALLENGE -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setUserChallengeHandlerClassName(className); + } + case XML_HANDLER_SSO -> { + this.currentElement = qName; + String className = attributes.getValue(XML_ATTR_CLASS).trim(); + getContainerModel().setSsoHandlerClassName(className); + } + default -> throw new IllegalStateException("Unexpected value: " + qName); } } @@ -129,20 +128,17 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { if (!(child instanceof ParametersParser parametersChild)) return; + final Map params = parametersChild.getParameterMap(); switch (this.currentElement) { - case XML_CONTAINER -> getContainerModel().setParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_ENCRYPTION -> - getContainerModel().setEncryptionHandlerParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_PASSWORD_STRENGTH -> - getContainerModel().setPasswordStrengthHandlerParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_PERSISTENCE -> - getContainerModel().setPersistenceHandlerParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_USER_CHALLENGE -> - getContainerModel().setUserChallengeHandlerParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_SSO -> getContainerModel().setSsoHandlerParameterMap(parametersChild.getParameterMap()); - case XML_HANDLER_PRIVILEGE -> - getContainerModel().setPrivilegeHandlerParameterMap(parametersChild.getParameterMap()); - default -> throw new IllegalStateException("Unexpected value: " + this.currentElement); + case XML_CONTAINER -> getContainerModel().setParameterMap(params); + case XML_HANDLER_PRIVILEGE -> getContainerModel().setPrivilegeHandlerParameterMap(params); + case XML_HANDLER_ENCRYPTION -> getContainerModel().setEncryptionHandlerParameterMap(params); + case XML_HANDLER_PASSWORD_STRENGTH -> + getContainerModel().setPasswordStrengthHandlerParameterMap(params); + case XML_HANDLER_PERSISTENCE -> getContainerModel().setPersistenceHandlerParameterMap(params); + case XML_HANDLER_USER_CHALLENGE -> getContainerModel().setUserChallengeHandlerParameterMap(params); + case XML_HANDLER_SSO -> getContainerModel().setSsoHandlerParameterMap(params); + default -> throw new IllegalStateException("Unexpected value: " + this.currentElement); } } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java new file mode 100644 index 000000000..4daff831c --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; + +import javax.xml.stream.XMLStreamException; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.privilege.helper.XmlHelper.openXmlStreamWriterDocument; +import static li.strolch.privilege.helper.XmlHelper.writeStringMapElement; + +/** + * @author Robert von Burg + */ +public class PrivilegeConfigSaxWriter { + + private final PrivilegeContainerModel containerModel; + private final File configFile; + + public PrivilegeConfigSaxWriter(PrivilegeContainerModel containerModel, File configFile) { + this.containerModel = containerModel; + this.configFile = configFile; + } + + public void write() throws IOException, XMLStreamException { + + try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.configFile), StandardCharsets.UTF_8)) { + + IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); + xmlWriter.writeStartElement(XML_ROOT_PRIVILEGE); + + // write container element + xmlWriter.writeStartElement(XML_CONTAINER); + writeStringMapElement(xmlWriter, this.containerModel.getParameterMap(), XML_PARAMETERS, XML_PARAMETER); + + { + // write PrivilegeHandler + if (this.containerModel.getPrivilegeHandlerClassName() != null) + writeHandler(xmlWriter, XML_HANDLER_PRIVILEGE, this.containerModel.getPrivilegeHandlerClassName(), + this.containerModel.getPrivilegeHandlerParameterMap()); + + // write EncryptionHandler + writeHandler(xmlWriter, XML_HANDLER_ENCRYPTION, this.containerModel.getEncryptionHandlerClassName(), + this.containerModel.getEncryptionHandlerParameterMap()); + + // write PersistenceHandler + writeHandler(xmlWriter, XML_HANDLER_PERSISTENCE, this.containerModel.getPersistenceHandlerClassName(), + this.containerModel.getPersistenceHandlerParameterMap()); + + // write PasswordStrengthHandler + if (this.containerModel.getPasswordStrengthHandlerClassName() != null) + writeHandler(xmlWriter, XML_HANDLER_PASSWORD_STRENGTH, + this.containerModel.getPasswordStrengthHandlerClassName(), + this.containerModel.getPasswordStrengthHandlerParameterMap()); + + // write UserChallengeHandler + if (this.containerModel.getUserChallengeHandlerClassName() != null) + writeHandler(xmlWriter, XML_HANDLER_USER_CHALLENGE, + this.containerModel.getUserChallengeHandlerClassName(), + this.containerModel.getUserChallengeHandlerParameterMap()); + + // write SingleSignOnHandler + if (this.containerModel.getSsoHandlerClassName() != null) + writeHandler(xmlWriter, XML_HANDLER_SSO, this.containerModel.getSsoHandlerClassName(), + this.containerModel.getSsoHandlerParameterMap()); + } + + xmlWriter.writeEndElement(); + + // Policies + Map policies = new HashMap<>(); + this.containerModel.getPolicies().forEach((key, value) -> policies.put(key, value.getName())); + writeStringMapElement(xmlWriter, policies, XML_POLICIES, XML_POLICY, XML_ATTR_CLASS); + + // and now end + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + } + } + + private void writeHandler(IndentingXMLStreamWriter xmlWriter, String handleName, String className, + Map parameters) throws XMLStreamException { + if (parameters.isEmpty()) + xmlWriter.writeEmptyElement(handleName); + else + xmlWriter.writeStartElement(handleName); + xmlWriter.writeAttribute(XML_ATTR_CLASS, className); + + writeStringMapElement(xmlWriter, parameters, XML_PARAMETERS, XML_PARAMETER); + + if (!parameters.isEmpty()) + xmlWriter.writeEndElement(); + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java deleted file mode 100644 index f88b2e283..000000000 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.xml; - -import static java.util.Comparator.comparing; - -import java.io.File; -import java.util.List; -import java.util.Locale; - -import li.strolch.privilege.helper.XmlConstants; -import li.strolch.privilege.model.internal.Role; -import li.strolch.utils.helper.XmlHelper; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -/** - * @author Robert von Burg - */ -public class PrivilegeRolesDomWriter { - - private final List roles; - private final File modelFile; - - public PrivilegeRolesDomWriter(List roles, File modelFile) { - this.roles = roles; - this.modelFile = modelFile; - } - - public void write() { - - // create document root - Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XmlConstants.XML_ROLES); - doc.appendChild(rootElement); - - this.roles.stream().sorted(comparing(role1 -> role1.getName().toLowerCase(Locale.ROOT))).forEach(role -> { - - // create the role element - Element roleElement = doc.createElement(XmlConstants.XML_ROLE); - rootElement.appendChild(roleElement); - - roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - role.getPrivilegeNames().stream().sorted().map(role::getPrivilege).forEach(privilege -> { - Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); - roleElement.appendChild(privilegeElement); - privilegeElement.setAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - if (privilege.isAllAllowed()) { - Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.appendChild(allAllowedElement); - } - - privilege.getDenyList().stream().sorted().forEach(denyValue -> { - Element denyValueElement = doc.createElement(XmlConstants.XML_DENY); - denyValueElement.setTextContent(denyValue); - privilegeElement.appendChild(denyValueElement); - }); - - privilege.getAllowList().stream().sorted().forEach(allowValue -> { - Element allowValueElement = doc.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setTextContent(allowValue); - privilegeElement.appendChild(allowValueElement); - }); - }); - }); - - // write the container file to disk - XmlHelper.writeDocument(doc, this.modelFile); - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java index 3bf062544..e91030cdd 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java @@ -15,10 +15,6 @@ */ package li.strolch.privilege.xml; -import java.text.MessageFormat; -import java.util.*; - -import li.strolch.privilege.helper.XmlConstants; import li.strolch.privilege.model.IPrivilege; import li.strolch.privilege.model.internal.PrivilegeImpl; import li.strolch.privilege.model.internal.Role; @@ -29,6 +25,11 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import java.text.MessageFormat; +import java.util.*; + +import static li.strolch.privilege.helper.XmlConstants.*; + /** * @author Robert von Burg */ @@ -51,9 +52,13 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_ROLE)) { + if (qName.equals(XML_ROLE)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(RoleParser.class))) + throw new IllegalArgumentException("Previous Role not closed!"); this.buildersStack.push(new RoleParser()); - } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + } else if (qName.equals(XML_PROPERTIES)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(PropertyParser.class))) + throw new IllegalArgumentException("Previous Properties not closed!"); this.buildersStack.push(new PropertyParser()); } @@ -74,9 +79,9 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_ROLE)) { + if (qName.equals(XML_ROLE)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + } else if (qName.equals(XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); } @@ -134,20 +139,15 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { this.text = new StringBuilder(); switch (qName) { - case XmlConstants.XML_ROLE: - this.roleName = attributes.getValue(XmlConstants.XML_ATTR_NAME).trim(); - break; - case XmlConstants.XML_PRIVILEGE: - this.privilegeName = attributes.getValue(XmlConstants.XML_ATTR_NAME).trim(); - this.privilegePolicy = attributes.getValue(XmlConstants.XML_ATTR_POLICY).trim(); - break; - case XmlConstants.XML_ALLOW: - case XmlConstants.XML_DENY: - case XmlConstants.XML_ALL_ALLOWED: + case XML_ROLE -> this.roleName = attributes.getValue(XML_ATTR_NAME).trim(); + case XML_PRIVILEGE -> { + this.privilegeName = attributes.getValue(XML_ATTR_NAME).trim(); + this.privilegePolicy = attributes.getValue(XML_ATTR_POLICY).trim(); + } + case XML_ALLOW, XML_DENY, XML_ALL_ALLOWED -> { + } // no-op - break; - default: - throw new IllegalArgumentException("Unhandled tag " + qName); + default -> throw new IllegalArgumentException("Unhandled tag " + qName); } } @@ -159,31 +159,33 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) { - switch (qName) { - case XmlConstants.XML_ALL_ALLOWED -> - this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); - case XmlConstants.XML_ALLOW -> this.allowList.add(this.text.toString().trim()); - case XmlConstants.XML_DENY -> this.denyList.add(this.text.toString().trim()); - case XmlConstants.XML_PRIVILEGE -> { - IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, - this.denyList, this.allowList); - this.privileges.put(this.privilegeName, privilege); - this.privilegeName = null; - this.privilegePolicy = null; - this.allAllowed = false; - this.denyList = new HashSet<>(); - this.allowList = new HashSet<>(); - } - case XmlConstants.XML_ROLE -> { - Role role = new Role(this.roleName, this.privileges); - roles.put(role.getName(), role); - logger.info(MessageFormat.format("New Role: {0}", role)); - init(); - } - default -> throw new IllegalStateException("Unexpected value: " + qName); + case XML_ALL_ALLOWED -> this.allAllowed = StringHelper.parseBoolean(getText()); + case XML_ALLOW -> this.allowList.add(getText()); + case XML_DENY -> this.denyList.add(getText()); + case XML_PRIVILEGE -> { + IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, + this.denyList, this.allowList); + this.privileges.put(this.privilegeName, privilege); + this.privilegeName = null; + this.privilegePolicy = null; + this.allAllowed = false; + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); + } + case XML_ROLE -> { + Role role = new Role(this.roleName, this.privileges); + roles.put(role.getName(), role); + logger.info(MessageFormat.format("New Role: {0}", role)); + init(); + } + default -> throw new IllegalStateException("Unexpected value: " + qName); } } + + private String getText() { + return this.text.toString().trim(); + } } static class PropertyParser extends ElementParserAdapter { @@ -194,11 +196,11 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - if (qName.equals(XmlConstants.XML_PROPERTY)) { - String key = attributes.getValue(XmlConstants.XML_ATTR_NAME).trim(); - String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE).trim(); + if (qName.equals(XML_PROPERTY)) { + String key = attributes.getValue(XML_ATTR_NAME).trim(); + String value = attributes.getValue(XML_ATTR_VALUE).trim(); this.parameterMap.put(key, value); - } else if (!qName.equals(XmlConstants.XML_PROPERTIES)) { + } else if (!qName.equals(XML_PROPERTIES)) { throw new IllegalArgumentException("Unhandled tag " + qName); } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java new file mode 100644 index 000000000..904b7d38e --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.internal.Role; + +import javax.xml.stream.XMLStreamException; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static java.util.Comparator.comparing; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.privilege.helper.XmlHelper.*; + +/** + * @author Robert von Burg + */ +public class PrivilegeRolesSaxWriter { + + private final List roles; + private final File modelFile; + + public PrivilegeRolesSaxWriter(List roles, File modelFile) { + this.roles = roles; + this.modelFile = modelFile; + } + + public void write() throws IOException, XMLStreamException { + + try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.modelFile), StandardCharsets.UTF_8)) { + + IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); + xmlWriter.writeStartElement(XML_ROLES); + + List roles = new ArrayList<>(this.roles); + roles.sort(comparing(role1 -> role1.getName().toLowerCase(Locale.ROOT))); + for (Role role : roles) { + + // start the role element + xmlWriter.writeStartElement(XML_ROLE); + xmlWriter.writeAttribute(XML_ATTR_NAME, role.getName()); + + List privilegeNames = new ArrayList<>(role.getPrivilegeNames()); + privilegeNames.sort(null); + for (String privilegeName : privilegeNames) { + IPrivilege privilege = role.getPrivilege(privilegeName); + + xmlWriter.writeStartElement(XML_PRIVILEGE); + xmlWriter.writeAttribute(XML_ATTR_NAME, privilege.getName()); + xmlWriter.writeAttribute(XML_ATTR_POLICY, privilege.getPolicy()); + + if (privilege.isAllAllowed()) + writeStringElement(xmlWriter, XML_ALL_ALLOWED, "true"); + writeStringList(xmlWriter, XML_DENY, privilege.getDenyList()); + writeStringList(xmlWriter, XML_ALLOW, privilege.getAllowList()); + + xmlWriter.writeEndElement(); + } + + xmlWriter.writeEndElement(); + } + + // and now end + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + } + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java deleted file mode 100644 index 3621c5d58..000000000 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.xml; - -import li.strolch.privilege.helper.XmlConstants; -import li.strolch.privilege.model.internal.PasswordCrypt; -import li.strolch.privilege.model.internal.User; -import li.strolch.privilege.model.internal.UserHistory; -import li.strolch.utils.helper.StringHelper; -import li.strolch.utils.helper.XmlHelper; -import li.strolch.utils.iso8601.ISO8601; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.io.File; -import java.util.List; -import java.util.Map; - -import static java.util.Comparator.comparing; -import static li.strolch.privilege.helper.XmlConstants.*; -import static li.strolch.privilege.model.internal.PasswordCrypt.buildPasswordString; -import static li.strolch.utils.helper.StringHelper.*; - -/** - * @author Robert von Burg - */ -public class PrivilegeUsersDomWriter { - - private final List users; - private final File modelFile; - - public PrivilegeUsersDomWriter(List users, File modelFile) { - this.users = users; - this.modelFile = modelFile; - - this.users.sort(comparing(User::getUsername)); - } - - public void write() { - - // create document root - Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XML_USERS); - doc.appendChild(rootElement); - - this.users.forEach(user -> { - - // create the user element - Element userElement = doc.createElement(XML_USER); - rootElement.appendChild(userElement); - - userElement.setAttribute(XML_ATTR_USER_ID, user.getUserId()); - userElement.setAttribute(XML_ATTR_USERNAME, user.getUsername()); - writePassword(user, userElement); - - // add first name element - if (isNotEmpty(user.getFirstname())) { - Element firstnameElement = doc.createElement(XML_FIRSTNAME); - firstnameElement.setTextContent(user.getFirstname()); - userElement.appendChild(firstnameElement); - } - - // add last name element - if (isNotEmpty(user.getLastname())) { - Element lastnameElement = doc.createElement(XML_LASTNAME); - lastnameElement.setTextContent(user.getLastname()); - userElement.appendChild(lastnameElement); - } - - // add state element - Element stateElement = doc.createElement(XML_STATE); - stateElement.setTextContent(user.getUserState().toString()); - userElement.appendChild(stateElement); - - // add locale element - Element localeElement = doc.createElement(XML_LOCALE); - localeElement.setTextContent(user.getLocale().toLanguageTag()); - userElement.appendChild(localeElement); - - // add password change requested element - if (user.isPasswordChangeRequested()) { - Element passwordChangeRequestedElement = doc.createElement(XML_PASSWORD_CHANGE_REQUESTED); - passwordChangeRequestedElement.setTextContent(Boolean.toString(true)); - userElement.appendChild(passwordChangeRequestedElement); - } - - // add all the role elements - Element rolesElement = doc.createElement(XML_ROLES); - userElement.appendChild(rolesElement); - user.getRoles().stream().sorted().forEach(roleName -> { - Element roleElement = doc.createElement(XML_ROLE); - roleElement.setTextContent(roleName); - rolesElement.appendChild(roleElement); - }); - - // add the parameters - if (!user.getProperties().isEmpty()) { - Element parametersElement = doc.createElement(XML_PROPERTIES); - userElement.appendChild(parametersElement); - user.getProperties().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - Element paramElement = doc.createElement(XML_PROPERTY); - paramElement.setAttribute(XML_ATTR_NAME, entry.getKey()); - paramElement.setAttribute(XML_ATTR_VALUE, entry.getValue()); - parametersElement.appendChild(paramElement); - }); - } - - if (!user.isHistoryEmpty()) { - UserHistory history = user.getHistory(); - Element historyElement = doc.createElement(XML_HISTORY); - userElement.appendChild(historyElement); - - if (!history.isFirstLoginEmpty()) { - Element element = doc.createElement(XML_FIRST_LOGIN); - element.setTextContent(ISO8601.toString(history.getFirstLogin())); - historyElement.appendChild(element); - } - - if (!history.isLastLoginEmpty()) { - Element element = doc.createElement(XML_LAST_LOGIN); - element.setTextContent(ISO8601.toString(history.getLastLogin())); - historyElement.appendChild(element); - } - - if (!history.isLastPasswordChangeEmpty()) { - Element element = doc.createElement(XML_LAST_PASSWORD_CHANGE); - element.setTextContent(ISO8601.toString(history.getLastPasswordChange())); - historyElement.appendChild(element); - } - } - }); - - // write the container file to disk - XmlHelper.writeDocument(doc, this.modelFile); - } - - private void writePassword(User user, Element userElement) { - PasswordCrypt passwordCrypt = user.getPasswordCrypt(); - if (passwordCrypt == null) - return; - - String passwordString = passwordCrypt.buildPasswordString(); - if (passwordString != null) { - userElement.setAttribute(XML_ATTR_PASSWORD, passwordString); - } else { - if (passwordCrypt.getPassword() != null) - userElement.setAttribute(XML_ATTR_PASSWORD, toHexString(passwordCrypt.getPassword())); - if (passwordCrypt.getSalt() != null) - userElement.setAttribute(XML_ATTR_SALT, toHexString(passwordCrypt.getSalt())); - } - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java index 2bfb2c8ff..80a5a7c46 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java @@ -15,16 +15,10 @@ */ package li.strolch.privilege.xml; -import static li.strolch.privilege.helper.XmlConstants.*; - -import java.text.MessageFormat; -import java.util.*; - import li.strolch.privilege.model.UserState; import li.strolch.privilege.model.internal.PasswordCrypt; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; -import li.strolch.utils.helper.StringHelper; import li.strolch.utils.iso8601.ISO8601; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +26,11 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import java.text.MessageFormat; +import java.util.*; + +import static li.strolch.privilege.helper.XmlConstants.*; + /** * @author Robert von Burg */ @@ -59,8 +58,12 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(XML_USER)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(UserParser.class))) + throw new IllegalArgumentException("Previous User not closed!"); this.buildersStack.push(new UserParser()); } else if (qName.equals(XML_PROPERTIES)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(PropertyParser.class))) + throw new IllegalArgumentException("Previous Properties not closed!"); this.buildersStack.push(new PropertyParser()); } @@ -157,17 +160,15 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { public void endElement(String uri, String localName, String qName) { switch (qName) { - case XML_FIRSTNAME -> this.firstName = this.text.toString().trim(); - case XML_LASTNAME -> this.lastname = this.text.toString().trim(); - case XML_STATE -> this.userState = UserState.valueOf(this.text.toString().trim()); - case XML_LOCALE -> this.locale = Locale.forLanguageTag(this.text.toString().trim()); - case XML_PASSWORD_CHANGE_REQUESTED -> - this.passwordChangeRequested = Boolean.parseBoolean(this.text.toString().trim()); - case XML_FIRST_LOGIN -> this.history.setFirstLogin(ISO8601.parseToZdt(this.text.toString().trim())); - case XML_LAST_LOGIN -> this.history.setLastLogin(ISO8601.parseToZdt(this.text.toString().trim())); - case XML_LAST_PASSWORD_CHANGE -> - this.history.setLastPasswordChange(ISO8601.parseToZdt(this.text.toString().trim())); - case XML_ROLE -> this.userRoles.add(this.text.toString().trim()); + case XML_FIRSTNAME -> this.firstName = getText(); + case XML_LASTNAME -> this.lastname = getText(); + case XML_STATE -> this.userState = UserState.valueOf(getText()); + case XML_LOCALE -> this.locale = Locale.forLanguageTag(getText()); + case XML_PASSWORD_CHANGE_REQUESTED -> this.passwordChangeRequested = Boolean.parseBoolean(getText()); + case XML_FIRST_LOGIN -> this.history.setFirstLogin(ISO8601.parseToZdt(getText())); + case XML_LAST_LOGIN -> this.history.setLastLogin(ISO8601.parseToZdt(getText())); + case XML_LAST_PASSWORD_CHANGE -> this.history.setLastPasswordChange(ISO8601.parseToZdt(getText())); + case XML_ROLE -> this.userRoles.add(getText()); case XML_USER -> { if (this.history == null) this.history = new UserHistory(); @@ -191,6 +192,10 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { } } + private String getText() { + return this.text.toString().trim(); + } + @Override public void notifyChild(ElementParser child) { if (child instanceof PropertyParser) { diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java new file mode 100644 index 000000000..1d758366f --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java @@ -0,0 +1,144 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.privilege.model.internal.PasswordCrypt; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.model.internal.UserHistory; +import li.strolch.utils.iso8601.ISO8601; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.util.Comparator.comparing; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.privilege.helper.XmlHelper.*; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; +import static li.strolch.utils.helper.StringHelper.toHexString; + +/** + * @author Robert von Burg + */ +public class PrivilegeUsersSaxWriter { + + private final List users; + private final File modelFile; + + public PrivilegeUsersSaxWriter(List users, File modelFile) { + this.users = users; + this.modelFile = modelFile; + + this.users.sort(comparing(User::getUsername)); + } + + public void write() throws IOException, XMLStreamException { + + try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.modelFile), StandardCharsets.UTF_8)) { + + IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); + xmlWriter.writeStartElement(XML_USERS); + + List users = new ArrayList<>(this.users); + users.sort(comparing(u -> u.getUsername().toLowerCase(Locale.ROOT))); + for (User user : this.users) { + + // start the user element + xmlWriter.writeStartElement(XML_USER); + + xmlWriter.writeAttribute(XML_ATTR_USER_ID, user.getUserId()); + xmlWriter.writeAttribute(XML_ATTR_USERNAME, user.getUsername()); + writePassword(user, xmlWriter); + + // add first name element + if (isNotEmpty(user.getFirstname())) + writeStringElement(xmlWriter, XML_FIRSTNAME, user.getFirstname()); + + // add last name element + if (isNotEmpty(user.getLastname())) + writeStringElement(xmlWriter, XML_LASTNAME, user.getLastname()); + + // add state element + writeStringElement(xmlWriter, XML_STATE, user.getUserState().toString()); + + // add locale element + writeStringElement(xmlWriter, XML_LOCALE, user.getLocale().toLanguageTag()); + + // add password change requested element + if (user.isPasswordChangeRequested()) + writeStringElement(xmlWriter, XML_PASSWORD_CHANGE_REQUESTED, "true"); + + // add all the role elements + if (!user.getRoles().isEmpty()) { + xmlWriter.writeStartElement(XML_ROLES); + writeStringList(xmlWriter, XML_ROLE, user.getRoles()); + xmlWriter.writeEndElement(); + } + + // add the parameters + Map properties = user.getProperties(); + if (!properties.isEmpty()) { + writeStringMapElement(xmlWriter, properties, XML_PROPERTIES, XML_PROPERTY); + } + + if (!user.isHistoryEmpty()) { + UserHistory history = user.getHistory(); + xmlWriter.writeStartElement(XML_HISTORY); + + if (!history.isFirstLoginEmpty()) + writeStringElement(xmlWriter, XML_FIRST_LOGIN, ISO8601.toString(history.getFirstLogin())); + + if (!history.isLastLoginEmpty()) + writeStringElement(xmlWriter, XML_LAST_LOGIN, ISO8601.toString(history.getLastLogin())); + + if (!history.isLastPasswordChangeEmpty()) + writeStringElement(xmlWriter, XML_LAST_PASSWORD_CHANGE, + ISO8601.toString(history.getLastPasswordChange())); + + xmlWriter.writeEndElement(); + } + + xmlWriter.writeEndElement(); + } + + // and now end + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + } + } + + private void writePassword(User user, XMLStreamWriter xmlStreamWriter) throws XMLStreamException { + PasswordCrypt passwordCrypt = user.getPasswordCrypt(); + if (passwordCrypt == null) + return; + + String passwordString = passwordCrypt.buildPasswordString(); + if (passwordString != null) { + xmlStreamWriter.writeAttribute(XML_ATTR_PASSWORD, passwordString); + } else { + if (passwordCrypt.getPassword() != null) + xmlStreamWriter.writeAttribute(XML_ATTR_PASSWORD, toHexString(passwordCrypt.getPassword())); + if (passwordCrypt.getSalt() != null) + xmlStreamWriter.writeAttribute(XML_ATTR_SALT, toHexString(passwordCrypt.getSalt())); + } + } +} diff --git a/privilege/src/test/java/li/strolch/privilege/test/WriteRolesFileHelperTest.java b/privilege/src/test/java/li/strolch/privilege/test/WriteRolesFileHelperTest.java index 5a63375a4..15859469e 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/WriteRolesFileHelperTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/WriteRolesFileHelperTest.java @@ -4,14 +4,17 @@ import static li.strolch.privilege.test.XmlTest.SRC_TEST; import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.IOException; import li.strolch.privilege.helper.WriteRolesFileHelper; import org.junit.Test; +import javax.xml.stream.XMLStreamException; + public class WriteRolesFileHelperTest { @Test - public void shouldReadAndWriteRolesFile() { + public void shouldReadAndWriteRolesFile() throws XMLStreamException, IOException { String src = SRC_TEST + "PrivilegeRoles.xml"; String dst = "target/WriteRolesFileHelperTest_roles.xml"; @@ -19,7 +22,7 @@ public class WriteRolesFileHelperTest { if (new File(dst).exists() && !new File(dst).delete()) throw new IllegalStateException("Could not delete file " + dst); - WriteRolesFileHelper.main(new String[] { src, dst }); + WriteRolesFileHelper.main(new String[]{src, dst}); assertTrue(new File(dst).exists()); } diff --git a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index e8c1a4745..15c006690 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -15,10 +15,7 @@ */ package li.strolch.privilege.test; -import li.strolch.privilege.handler.DefaultEncryptionHandler; -import li.strolch.privilege.handler.MailUserChallengeHandler; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.handler.XmlPersistenceHandler; +import li.strolch.privilege.handler.*; import li.strolch.privilege.model.IPrivilege; import li.strolch.privilege.model.UserState; import li.strolch.privilege.model.internal.*; @@ -33,7 +30,10 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.xml.stream.XMLStreamException; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -115,7 +115,7 @@ public class XmlTest { } @Test - public void canWriteConfig() { + public void canWriteConfig() throws XMLStreamException, IOException { Map parameterMap = new HashMap<>(); Map encryptionHandlerParameterMap = new HashMap<>(); @@ -128,6 +128,7 @@ public class XmlTest { PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); containerModel.setParameterMap(parameterMap); + containerModel.setPrivilegeHandlerClassName(DefaultPrivilegeHandler.class.getName()); containerModel.setEncryptionHandlerClassName(DefaultEncryptionHandler.class.getName()); containerModel.setEncryptionHandlerParameterMap(encryptionHandlerParameterMap); containerModel.setPersistenceHandlerClassName(XmlPersistenceHandler.class.getName()); @@ -138,11 +139,38 @@ public class XmlTest { containerModel.addPolicy("DefaultPrivilege", "li.strolch.privilege.policy.DefaultPrivilege"); File configFile = new File(TARGET_TEST + "PrivilegeTest.xml"); - PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + PrivilegeConfigSaxWriter configSaxWriter = new PrivilegeConfigSaxWriter(containerModel, configFile); configSaxWriter.write(); - String fileHash = StringHelper.toHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("dcb6b3ed7198e0a7c88bf5c61c0bd6f0d684415f2a2f29429879edc6bc795f06", fileHash); + String expected = """ + + + + + + + + + + + + + + + + + + + + + + + + + + """; + + assertEquals(expected, Files.readString(configFile.toPath())); } @Test @@ -295,7 +323,7 @@ public class XmlTest { } @Test - public void canWriteUsers() { + public void canWriteUsers() throws XMLStreamException, IOException { Map propertyMap; Set userRoles; @@ -324,7 +352,7 @@ public class XmlTest { users.add(user2); File modelFile = new File(TARGET_TEST + "PrivilegeUsersTest.xml"); - PrivilegeUsersDomWriter configSaxWriter = new PrivilegeUsersDomWriter(users, modelFile); + PrivilegeUsersSaxWriter configSaxWriter = new PrivilegeUsersSaxWriter(users, modelFile); configSaxWriter.write(); PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(true); @@ -361,7 +389,7 @@ public class XmlTest { } @Test - public void canWriteRoles() { + public void canWriteRoles() throws XMLStreamException, IOException { Map privilegeMap; List roles = new ArrayList<>(); @@ -381,8 +409,8 @@ public class XmlTest { roles.add(role2); File modelFile = new File(TARGET_TEST + "PrivilegeRolesTest.xml"); - PrivilegeRolesDomWriter configSaxWriter = new PrivilegeRolesDomWriter(roles, modelFile); - configSaxWriter.write(); + PrivilegeRolesSaxWriter writer = new PrivilegeRolesSaxWriter(roles, modelFile); + writer.write(); PrivilegeRolesSaxReader xmlHandler = new PrivilegeRolesSaxReader(); XmlHelper.parseDocument(modelFile, xmlHandler); From 648553409c755da9e5929e9807d1b4e40762844e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Sep 2023 16:33:30 +0200 Subject: [PATCH 13/39] [Minor] Code cleanup --- .../handler/BasicPasswordStrengthHandler.java | 4 +- .../JsonConfigLdapPrivilegeHandler.java | 44 +++++++++---------- .../handler/MailUserChallengeHandler.java | 6 +-- .../handler/PasswordStrengthHandler.java | 9 ++-- .../handler/SimpleLdapPrivilegeHandler.java | 3 +- 5 files changed, 30 insertions(+), 36 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java index 5a4cf4cf7..2e2bf0a13 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java @@ -37,8 +37,8 @@ public class BasicPasswordStrengthHandler implements PasswordStrengthHandler { String description; if (this.maxLength < 100) - description = MessageFormat - .format(getString(locale, "Privilege.passwordLengthBetween"), this.minLength, this.maxLength); + description = MessageFormat.format(getString(locale, "Privilege.passwordLengthBetween"), this.minLength, + this.maxLength); else description = MessageFormat.format(getString(locale, "Privilege.passwordLengthAtLeast"), this.minLength); diff --git a/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java index 0d3c4b842..5e9eb52fc 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java @@ -42,15 +42,14 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { DBC.PRE.assertNotEmpty("realm must be set!", realm); this.defaultLocale = parameterMap.containsKey("defaultLocale") ? - Locale.forLanguageTag(parameterMap.get("defaultLocale")) : - Locale.getDefault(); + Locale.forLanguageTag(parameterMap.get("defaultLocale")) : Locale.getDefault(); String configFileS = parameterMap.get("configFile"); DBC.PRE.assertNotEmpty("configFile param must be set!", configFileS); File configFile = new File(configFileS); if (!configFile.exists() || !configFile.isFile() || !configFile.canRead()) - throw new IllegalStateException("configFile does not exist, is not a file, or can not be read at path " - + configFile.getAbsolutePath()); + throw new IllegalStateException("configFile does not exist, is not a file, or can not be read at path " + + configFile.getAbsolutePath()); // parse the configuration file JsonObject configJ; @@ -82,14 +81,14 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { // validate the configuration for (String name : this.ldapGroupNames) { JsonObject config = ldapGroupConfigs.get(name).getAsJsonObject(); - if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray() - || config.get(LOCATION).getAsJsonArray().size() == 0) - throw new IllegalStateException("LDAP Group " + name - + " is missing a location attribute, or it is not an array or the array is empty"); - if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray() - || config.get(LOCATION).getAsJsonArray().size() == 0) - throw new IllegalStateException("LDAP Group " + name - + " is missing a roles attribute, or it is not an array or the array is empty"); + if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray() || + config.get(LOCATION).getAsJsonArray().isEmpty()) + throw new IllegalStateException("LDAP Group " + name + + " is missing a location attribute, or it is not an array or the array is empty"); + if (!config.has(LOCATION) || !config.get(LOCATION).isJsonArray() || + config.get(LOCATION).getAsJsonArray().isEmpty()) + throw new IllegalStateException("LDAP Group " + name + + " is missing a roles attribute, or it is not an array or the array is empty"); } this.userLdapGroupOverrides = new HashMap<>(); @@ -133,17 +132,16 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { logger.info("Overriding LDAP group for user " + username + " to " + overrideGroup); } - Set relevantLdapGroups = ldapGroups.stream() - .filter(s -> this.ldapGroupNames.contains(s)) + Set relevantLdapGroups = ldapGroups.stream().filter(s -> this.ldapGroupNames.contains(s)) .collect(toSet()); if (relevantLdapGroups.isEmpty()) - throw new IllegalStateException("User " + username - + " can not login, as none of their LDAP Groups have mappings to Strolch Roles!"); + throw new IllegalStateException("User " + username + + " can not login, as none of their LDAP Groups have mappings to Strolch Roles!"); if (relevantLdapGroups.size() > 1) { logger.warn( - "User " + username + " has multiple relevant LDAP Groups which will lead to undefined behaviour: " - + join(",", relevantLdapGroups)); + "User " + username + " has multiple relevant LDAP Groups which will lead to undefined behaviour: " + + join(",", relevantLdapGroups)); } return relevantLdapGroups; @@ -194,9 +192,8 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { } else { String location = primaryLocationJ.getAsString(); if (!secondaryLocations.contains(location)) { - logger.warn( - "Primary location already set by previous LDAP Group config for LDAP Group " + ldapGroup - + ", adding to secondary locations."); + logger.warn("Primary location already set by previous LDAP Group config for LDAP Group " + + ldapGroup + ", adding to secondary locations."); secondaryLocations.add(location); } } @@ -210,9 +207,8 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { else secondaryLocationsJ.getAsJsonArray().forEach(s -> secondaryLocations.add(s.getAsString())); } else { - logger.warn( - "Secondary locations already set by previous LDAP Group config for LDAP Group " + ldapGroup - + ", adding additional"); + logger.warn("Secondary locations already set by previous LDAP Group config for LDAP Group " + + ldapGroup + ", adding additional"); if (secondaryLocationsJ.isJsonPrimitive()) secondaryLocations.add(secondaryLocationsJ.getAsString()); else diff --git a/privilege/src/main/java/li/strolch/privilege/handler/MailUserChallengeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/MailUserChallengeHandler.java index 2b3aef89e..8fa91bf89 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/MailUserChallengeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/MailUserChallengeHandler.java @@ -16,9 +16,9 @@ public class MailUserChallengeHandler extends UserChallengeHandler { String subject = "Mail TAN"; - String text = "Hello " + user.getFirstname() + " " + user.getLastname() + "\n\n" - + "You have requested an action which requires you to respond to a challenge.\n\n" - + "Please use the following code to response to the challenge:\n\n" + challenge; + String text = "Hello " + user.getFirstname() + " " + user.getLastname() + "\n\n" + + "You have requested an action which requires you to respond to a challenge.\n\n" + + "Please use the following code to response to the challenge:\n\n" + challenge; String recipient = user.getEmail(); if (StringHelper.isEmpty(recipient)) { String msg = "User {0} has no property {1}, so can not initiate challenge!"; diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java index 5ae4e146b..e5ae925df 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java @@ -12,8 +12,7 @@ public interface PasswordStrengthHandler { * Initialize the concrete {@link PasswordStrengthHandler}. The passed parameter map contains any configuration the * concrete {@link PasswordStrengthHandler} might need * - * @param parameterMap - * a map containing configuration properties + * @param parameterMap a map containing configuration properties */ void initialize(Map parameterMap); @@ -21,16 +20,16 @@ public interface PasswordStrengthHandler { * Returns a description what a password must contain in order to be regarded as strong for this concrete * implementation * + * @param locale the locale in which to return the description + * * @return a description of a strong password - * @param locale */ String getDescription(Locale locale); /** * Performs the validation of the given password * - * @param password - * the password to validate + * @param password the password to validate * * @return true if the password meets the criteria for a strong password */ diff --git a/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java index f1b4c93e5..4e335db7a 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java @@ -39,8 +39,7 @@ public class SimpleLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { this.realm = parameterMap.getOrDefault(REALM, ""); this.defaultLocale = parameterMap.containsKey("defaultLocale") ? - Locale.forLanguageTag(parameterMap.get("defaultLocale")) : - Locale.getDefault(); + Locale.forLanguageTag(parameterMap.get("defaultLocale")) : Locale.getDefault(); this.adminUsers = parameterMap.get("adminUsers"); this.rolesForLdapGroups = getLdapGroupToRolesMappingFromConfig(parameterMap); From 48d121882e9a1e9fc34e350e128d56117798463c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 25 Sep 2023 10:47:53 +0200 Subject: [PATCH 14/39] [Major] Implemented groups in privilege. Refactored nearly everything --- .../DefaultStrolchPrivilegeHandler.java | 4 +- .../runtime/privilege/ModelPrivilege.java | 12 +- .../json/PrivilegeElementFromJsonVisitor.java | 33 +- .../privilege/base/PrivilegeConstants.java | 1 + .../handler/BaseLdapPrivilegeHandler.java | 23 +- .../handler/DefaultEncryptionHandler.java | 21 +- .../handler/DefaultPrivilegeHandler.java | 1555 ++--------------- .../JsonConfigLdapPrivilegeHandler.java | 38 +- .../privilege/handler/PersistenceHandler.java | 78 +- .../handler/PrivilegeContextBuilder.java | 204 +++ .../handler/PrivilegeCrudHandler.java | 1130 ++++++++++++ .../privilege/handler/PrivilegeHandler.java | 2 +- .../handler/SimpleLdapPrivilegeHandler.java | 127 -- .../handler/XmlPersistenceHandler.java | 120 +- .../privilege/helper/PasswordCreator.java | 15 +- .../privilege/helper/XmlConstants.java | 369 +--- .../strolch/privilege/helper/XmlHelper.java | 4 +- .../strolch/privilege/model/Certificate.java | 106 +- .../strolch/privilege/model/IPrivilege.java | 85 - .../li/strolch/privilege/model/Privilege.java | 140 ++ .../privilege/model/PrivilegeContext.java | 127 +- .../strolch/privilege/model/PrivilegeRep.java | 91 +- .../strolch/privilege/model/Restrictable.java | 6 +- .../li/strolch/privilege/model/RoleRep.java | 62 +- .../privilege/model/SimpleRestrictable.java | 6 +- .../li/strolch/privilege/model/UserRep.java | 222 ++- .../privilege/model/internal/Group.java | 92 + .../model/internal/PasswordCrypt.java | 83 +- .../model/internal/PrivilegeImpl.java | 223 --- .../privilege/model/internal/Role.java | 78 +- .../privilege/model/internal/User.java | 150 +- .../model/internal/UserChallenge.java | 11 +- .../privilege/model/internal/UserHistory.java | 54 +- .../privilege/policy/DefaultPrivilege.java | 10 +- .../privilege/policy/PrivilegePolicy.java | 16 +- .../policy/PrivilegePolicyHelper.java | 10 +- .../privilege/policy/RoleAccessPrivilege.java | 8 +- .../privilege/policy/UserAccessPrivilege.java | 8 +- ...erAccessWithSameOrganisationPrivilege.java | 8 +- .../UsernameFromCertificatePrivilege.java | 8 +- ...tificateWithSameOrganisationPrivilege.java | 8 +- .../xml/CertificateStubsSaxReader.java | 22 +- .../xml/CertificateStubsSaxWriter.java | 22 +- .../xml/PrivilegeConfigSaxReader.java | 60 +- .../xml/PrivilegeConfigSaxWriter.java | 24 +- .../xml/PrivilegeGroupsSaxReader.java | 174 ++ .../xml/PrivilegeGroupsSaxWriter.java | 82 + .../xml/PrivilegeRolesSaxReader.java | 43 +- .../xml/PrivilegeRolesSaxWriter.java | 24 +- .../xml/PrivilegeUsersSaxReader.java | 87 +- .../xml/PrivilegeUsersSaxWriter.java | 54 +- .../strolch/privilege/xml/PropertyParser.java | 33 + .../resources/PrivilegeMessages.properties | 1 + .../privilege/test/AbstractPrivilegeTest.java | 19 +- .../li/strolch/privilege/test/CryptTest.java | 12 +- .../privilege/test/PersistSessionsTest.java | 2 +- .../test/PrivilegeConflictMergeTest.java | 81 +- .../strolch/privilege/test/PrivilegeTest.java | 71 +- .../privilege/test/SsoHandlerTest.java | 3 +- .../li/strolch/privilege/test/XmlTest.java | 143 +- .../privilege/test/model/DummySsoHandler.java | 3 +- .../resources/config/PrivilegeConfigMerge.xml | 1 + .../test/resources/config/PrivilegeGroups.xml | 13 + .../resources/config/PrivilegeGroupsMerge.xml | 37 + .../test/resources/config/PrivilegeUsers.xml | 7 +- .../resources/config/PrivilegeUsersMerge.xml | 22 + .../java/li/strolch/report/ReportSearch.java | 4 +- .../users/PrivilegeAddUserCommandTest.java | 16 +- .../users/PrivilegeAddUserServiceTest.java | 4 +- .../li/strolch/service/test/ServiceTest.java | 10 +- .../svctest/config/PrivilegeGroups.xml | 8 + .../svctest/config/PrivilegeRoles.xml | 343 ++-- .../svctest/config/PrivilegeUsers.xml | 50 +- .../rest/endpoint/AuthenticationService.java | 4 +- .../rest/endpoint/StrolchJobsResource.java | 4 +- 75 files changed, 3549 insertions(+), 3282 deletions(-) create mode 100644 privilege/src/main/java/li/strolch/privilege/handler/PrivilegeContextBuilder.java create mode 100644 privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java create mode 100644 privilege/src/main/java/li/strolch/privilege/model/Privilege.java create mode 100644 privilege/src/main/java/li/strolch/privilege/model/internal/Group.java delete mode 100644 privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxReader.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxWriter.java create mode 100644 privilege/src/main/java/li/strolch/privilege/xml/PropertyParser.java create mode 100644 privilege/src/test/resources/config/PrivilegeGroups.xml create mode 100644 privilege/src/test/resources/config/PrivilegeGroupsMerge.xml create mode 100644 service/src/test/resources/svctest/config/PrivilegeGroups.xml diff --git a/agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java b/agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java index 99f80526f..96661e4e9 100644 --- a/agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java +++ b/agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java @@ -19,7 +19,7 @@ import static java.lang.Boolean.parseBoolean; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS; import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH; -import static li.strolch.privilege.helper.XmlConstants.XML_PARAM_BASE_PATH; +import static li.strolch.privilege.helper.XmlConstants.PARAM_BASE_PATH; import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.*; import java.io.File; @@ -126,7 +126,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements if (containerModel.getPersistenceHandlerClassName().equals(XmlPersistenceHandler.class.getName())) { Map xmlParams = containerModel.getPersistenceHandlerParameterMap(); File configPath = runtimeConfig.getConfigPath(); - xmlParams.put(XML_PARAM_BASE_PATH, configPath.getPath()); + xmlParams.put(PARAM_BASE_PATH, configPath.getPath()); } return new PrivilegeInitializer(getScheduledExecutor(getName())).initializeFromXml(containerModel); diff --git a/agent/src/main/java/li/strolch/runtime/privilege/ModelPrivilege.java b/agent/src/main/java/li/strolch/runtime/privilege/ModelPrivilege.java index 44bb6a7ff..8c523bd63 100644 --- a/agent/src/main/java/li/strolch/runtime/privilege/ModelPrivilege.java +++ b/agent/src/main/java/li/strolch/runtime/privilege/ModelPrivilege.java @@ -9,7 +9,7 @@ import li.strolch.model.StrolchRootElement; import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.Role; @@ -20,10 +20,10 @@ public class ModelPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, IPrivilege, Restrictable) + * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Privilege, Restrictable) */ @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @@ -31,15 +31,15 @@ public class ModelPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, IPrivilege, Restrictable) + * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Privilege, Restrictable) */ @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } - protected boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + protected boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { preValidate(privilege, restrictable); diff --git a/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java b/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java index be7c8d54d..66ecf363f 100644 --- a/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java +++ b/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java @@ -1,7 +1,5 @@ package li.strolch.model.json; -import java.util.*; - import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -11,6 +9,8 @@ import li.strolch.privilege.model.RoleRep; import li.strolch.privilege.model.UserRep; import li.strolch.privilege.model.UserState; +import java.util.*; + public class PrivilegeElementFromJsonVisitor { public UserRep userRepFromJson(String string) { @@ -82,6 +82,7 @@ public class PrivilegeElementFromJsonVisitor { JsonElement lastNameE = jsonObject.get("lastname"); JsonElement userStateE = jsonObject.get("userState"); JsonElement localeE = jsonObject.get("locale"); + JsonElement groupsE = jsonObject.get("groups"); JsonElement rolesE = jsonObject.get("roles"); JsonElement propertiesE = jsonObject.get("properties"); @@ -90,16 +91,11 @@ public class PrivilegeElementFromJsonVisitor { String firstname = firstNameE == null ? null : firstNameE.getAsString().trim(); String lastname = lastNameE == null ? null : lastNameE.getAsString().trim(); UserState userState = userStateE == null ? null : UserState.valueOf(userStateE.getAsString().trim()); - Locale locale = localeE == null ? null : new Locale(localeE.getAsString().trim()); + Locale locale = localeE == null ? null : Locale.forLanguageTag(localeE.getAsString().trim()); - Set roles = null; - if (rolesE != null) { - roles = new HashSet<>(); - JsonArray rolesArr = rolesE.getAsJsonArray(); - for (JsonElement role : rolesArr) { - roles.add(role.getAsString().trim()); - } - } + Set groups = jsonArrayToSet(groupsE); + + Set roles = jsonArrayToSet(rolesE); Map properties = null; if (propertiesE != null) { @@ -111,6 +107,19 @@ public class PrivilegeElementFromJsonVisitor { } } - return new UserRep(userId, username, firstname, lastname, userState, roles, locale, properties, null); + return new UserRep(userId, username, firstname, lastname, userState, groups, roles, locale, properties, null); + } + + private Set jsonArrayToSet(JsonElement array) { + if (array == null) + return Set.of(); + + Set result = new HashSet<>(); + JsonArray rolesArr = array.getAsJsonArray(); + for (JsonElement role : rolesArr) { + result.add(role.getAsString().trim()); + } + + return result; } } diff --git a/privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java b/privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java index 83444dfb8..7e6c109d7 100644 --- a/privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java +++ b/privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java @@ -14,6 +14,7 @@ public class PrivilegeConstants { public static final String PRIMARY_LOCATION = "primaryLocation"; public static final String SECONDARY_LOCATIONS = "secondaryLocations"; public static final String ROLES = "roles"; + public static final String GROUPS = "groups"; public static final String EMAIL = "email"; public static final String ROLE_STROLCH_ADMIN = "StrolchAdmin"; diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index ed881f9bd..1fc8c6c35 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -1,5 +1,13 @@ package li.strolch.privilege.handler; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.model.internal.UserHistory; +import li.strolch.privilege.policy.PrivilegePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; @@ -10,14 +18,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; -import li.strolch.privilege.base.AccessDeniedException; -import li.strolch.privilege.model.UserState; -import li.strolch.privilege.model.internal.User; -import li.strolch.privilege.model.internal.UserHistory; -import li.strolch.privilege.policy.PrivilegePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(BaseLdapPrivilegeHandler.class); @@ -135,12 +135,13 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { Set ldapGroups = getLdapGroups(username, attrs); logger.info("User " + username + " is member of the following LDAP groups: "); ldapGroups.forEach(s -> logger.info("- " + s)); + Set strolchGroups = mapToStrolchGroups(username, ldapGroups); Set strolchRoles = mapToStrolchRoles(username, ldapGroups); Map properties = buildProperties(username, attrs, ldapGroups, strolchRoles); - return new User(username, username, null, firstName, lastName, UserState.REMOTE, strolchRoles, locale, - properties, false, new UserHistory()); + return new User(username, username, null, firstName, lastName, UserState.REMOTE, strolchGroups, strolchRoles, + locale, properties, false, UserHistory.EMPTY); } protected abstract Map buildProperties(String username, Attributes attrs, Set ldapGroups, @@ -168,5 +169,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { protected abstract Set getLdapGroups(String username, Attributes attrs) throws NamingException; + protected abstract Set mapToStrolchGroups(String username, Set ldapGroups); + protected abstract Set mapToStrolchRoles(String username, Set ldapGroups); } diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java index a0e351b4b..358aadfd9 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java @@ -44,7 +44,7 @@ import static li.strolch.privilege.helper.XmlConstants.*; *

* Required parameters: *

    - *
  • {@link XmlConstants#XML_PARAM_HASH_ALGORITHM}
  • + *
  • {@link XmlConstants#PARAM_HASH_ALGORITHM}
  • *
* * @author Robert von Burg @@ -121,7 +121,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { try { MessageDigest digest = MessageDigest.getInstance(this.nonSaltAlgorithm); - return new PasswordCrypt(digest.digest(new String(password).getBytes()), null); + return PasswordCrypt.of(digest.digest(new String(password).getBytes()), null); } catch (NoSuchAlgorithmException e) { throw new PrivilegeException(MessageFormat.format("Algorithm {0} was not found!", nonSaltAlgorithm), @@ -151,9 +151,8 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public boolean isPasswordCryptOutdated(PasswordCrypt passwordCrypt) { - return passwordCrypt.getSalt() == null || passwordCrypt.getHashAlgorithm() == null || - passwordCrypt.getHashIterations() != this.iterations || - passwordCrypt.getHashKeyLength() != this.keyLength; + return passwordCrypt.salt() == null || passwordCrypt.hashAlgorithm() == null || + passwordCrypt.hashIterations() != this.iterations || passwordCrypt.hashKeyLength() != this.keyLength; } @Override @@ -163,13 +162,13 @@ public class DefaultEncryptionHandler implements EncryptionHandler { this.secureRandom = new SecureRandom(); // get hash algorithm parameters - this.algorithm = parameterMap.getOrDefault(XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM); - this.nonSaltAlgorithm = parameterMap.getOrDefault(XML_PARAM_HASH_ALGORITHM_NON_SALT, + this.algorithm = parameterMap.getOrDefault(PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM); + this.nonSaltAlgorithm = parameterMap.getOrDefault(PARAM_HASH_ALGORITHM_NON_SALT, DEFAULT_ALGORITHM_NON_SALT); this.iterations = Integer.parseInt( - parameterMap.getOrDefault(XML_PARAM_HASH_ITERATIONS, valueOf(DEFAULT_ITERATIONS))); + parameterMap.getOrDefault(PARAM_HASH_ITERATIONS, valueOf(DEFAULT_ITERATIONS))); this.keyLength = Integer.parseInt( - parameterMap.getOrDefault(XML_PARAM_HASH_KEY_LENGTH, valueOf(DEFAULT_KEY_LENGTH))); + parameterMap.getOrDefault(PARAM_HASH_KEY_LENGTH, valueOf(DEFAULT_KEY_LENGTH))); // test non-salt hash algorithm try { @@ -178,7 +177,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { MessageFormat.format("Using non-salt hashing algorithm {0}", this.nonSaltAlgorithm)); } catch (Exception e) { String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; - msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XML_PARAM_HASH_ALGORITHM_NON_SALT, + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), PARAM_HASH_ALGORITHM_NON_SALT, e.getLocalizedMessage()); throw new PrivilegeException(msg, e); } @@ -189,7 +188,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { DefaultEncryptionHandler.logger.info(MessageFormat.format("Using hashing algorithm {0}", this.algorithm)); } catch (Exception e) { String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; - msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XML_PARAM_HASH_ALGORITHM, + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), PARAM_HASH_ALGORITHM, e.getLocalizedMessage()); throw new PrivilegeException(msg, e); } diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 538992091..c0dbb3e01 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -22,7 +22,6 @@ import li.strolch.privilege.policy.PrivilegePolicy; import li.strolch.privilege.xml.CertificateStubsSaxReader; import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub; import li.strolch.privilege.xml.CertificateStubsSaxWriter; -import li.strolch.utils.collections.Tuple; import li.strolch.utils.concurrent.ElementLockingHandler; import li.strolch.utils.dbc.DBC; import li.strolch.utils.helper.AesCryptoHelper; @@ -39,18 +38,19 @@ import java.io.OutputStream; import java.nio.file.Files; import java.time.ZonedDateTime; import java.util.*; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import static java.text.MessageFormat.format; -import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; +import static li.strolch.privilege.handler.PrivilegeCrudHandler.clearPassword; import static li.strolch.utils.helper.ExceptionHelper.getRootCause; -import static li.strolch.utils.helper.StringHelper.*; +import static li.strolch.utils.helper.StringHelper.isEmpty; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; /** *

@@ -74,9 +74,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); public static final String SOURCE_UNKNOWN = "unknown"; + private PrivilegeCrudHandler crudHandler; /** - * Map keeping a reference to all active sessions + * Reference to all active sessions */ protected Map privilegeContextMap; @@ -143,7 +144,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { protected boolean disallowSourceChange; protected PrivilegeConflictResolution privilegeConflictResolution; - private String identifier; private Map parameterMap; @@ -189,47 +189,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public RoleRep getRole(Certificate certificate, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); - - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) - return null; - - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); - - return role.asRoleRep(); + return crudHandler.getRole(certificate, roleName); } @Override public UserRep getUser(Certificate certificate, String username) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); - - User user = this.persistenceHandler.getUser(username); - if (user == null) - return null; - - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); - return user.asUserRep(); + return crudHandler.getUser(certificate, username); } @Override public Map getPolicyDefs(Certificate certificate) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_POLICIES)); - - Map policyDef = new HashMap<>(this.policyMap.size()); - for (Entry> entry : this.policyMap.entrySet()) { - policyDef.put(entry.getKey(), entry.getValue().getName()); - } - return policyDef; + return crudHandler.getPolicyDefs(certificate); } @Override @@ -239,1063 +209,119 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext prvCtx = validate(certificate); prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_CERTIFICATES)); - return this.privilegeContextMap.values().stream().map(PrivilegeContext::getCertificate) - .collect(Collectors.toList()); + return this.privilegeContextMap.values().stream().map(PrivilegeContext::getCertificate).collect(toList()); } @Override public List getRoles(Certificate certificate) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); - - Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); - - // validate access to each role - rolesStream = rolesStream.filter( - role -> prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role)))); - - return rolesStream.map(Role::asRoleRep).collect(Collectors.toList()); + return crudHandler.getRoles(certificate); } @Override public List getUsers(Certificate certificate) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); - - Stream usersStream = this.persistenceHandler.getAllUsers().stream(); - - // validate access to each user - usersStream = usersStream.filter( - user -> prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)))); - - return usersStream.map(User::asUserRep).collect(Collectors.toList()); + return crudHandler.getUsers(certificate); } @Override public List queryUsers(Certificate certificate, UserRep selectorRep) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); - - String selUserId = selectorRep.getUserId(); - String selUsername = selectorRep.getUsername(); - String selFirstName = selectorRep.getFirstname(); - String selLastName = selectorRep.getLastname(); - UserState selUserState = selectorRep.getUserState(); - Locale selLocale = selectorRep.getLocale(); - Set selRoles = selectorRep.getRoles(); - Map selPropertyMap = selectorRep.getProperties(); - - List result = new ArrayList<>(); - List allUsers = this.persistenceHandler.getAllUsers(); - for (User user : allUsers) { - - if (!prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)))) - continue; - - // selections - boolean userIdSelected; - boolean usernameSelected; - boolean firstNameSelected; - boolean lastNameSelected; - boolean userStateSelected; - boolean localeSelected; - boolean roleSelected; - boolean propertySelected; - - // userId - userIdSelected = isEmpty(selUserId) || selUserId.equals(user.getUserId()); - - // username - usernameSelected = isEmpty(selUsername) || selUsername.equals(user.getUsername()); - - // firstname - firstNameSelected = isEmpty(selFirstName) || selFirstName.equals(user.getFirstname()); - - // lastname - lastNameSelected = isEmpty(selLastName) || selLastName.equals(user.getLastname()); - - // user state - userStateSelected = selUserState == null || selUserState.equals(user.getUserState()); - - // locale - localeSelected = selLocale == null || selLocale.equals(user.getLocale()); - - // roles - roleSelected = isSelectedByRole(selRoles, user.getRoles()); - - // properties - propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); - - boolean selected = userIdSelected && usernameSelected && firstNameSelected && lastNameSelected && - userStateSelected && localeSelected && roleSelected && propertySelected; - - if (selected) - result.add(user.asUserRep()); - } - - result.sort(comparing(UserRep::getUsername)); - return result; - } - - /** - * Checks if the given properties contains values which are contained in the selectionMap. If the selectionMap is - * null or empty, then true is returned. If a key/value pair from the selectionMap is not in the properties, then - * false is returned - * - * @param selectionMap the map defining the expected properties - * @param properties the properties which must be a sub set of selectionMap to have this method return true - * - * @return If the selectionMap is null or empty, then true is returned. If a key/value pair from the selectionMap is - * not in the properties, then false is returned - */ - private boolean isSelectedByProperty(Map selectionMap, Map properties) { - - if (selectionMap == null) - return true; - - if (selectionMap.isEmpty() && properties.isEmpty()) - return true; - - for (String selKey : selectionMap.keySet()) { - - String value = properties.get(selKey); - if (value == null || !value.equals(selectionMap.get(selKey))) - return false; - } - - return true; - } - - /** - * Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is null or - * empty, then true is returned, otherwise false - * - * @param selectionRoles the required roles - * @param roles the roles to check if they contain the selectionRoles - * - * @return Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is - * null or empty, then true is returned, otherwise false - */ - private boolean isSelectedByRole(Set selectionRoles, Set roles) { - return selectionRoles == null || roles.containsAll(selectionRoles); + return crudHandler.queryUsers(certificate, selectorRep); } @Override public UserRep addUser(Certificate certificate, UserRep userRepParam, char[] password) { return this.lockingHandler.lockedExecuteWithResult(userRepParam.getUsername(), - () -> internalAddUser(certificate, userRepParam, password)); - } - - public UserRep internalAddUser(Certificate certificate, UserRep userRepParam, char[] password) { - try { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER); - - // make sure userId is not set - if (isNotEmpty(userRepParam.getUserId())) - throw new PrivilegeModelException("UserId can not be set when adding a new user!"); - - UserRep userRep = userRepParam.getCopy(); - - // set userId - userRep.setUserId(getUniqueId()); - - // first validate user - userRep.validate(); - - validateRolesExist(userRep); - - // validate user does not already exist - if (this.persistenceHandler.getUser(userRep.getUsername()) != null) { - String msg = "User {0} can not be added as it already exists!"; - throw new PrivilegeModelException(format(msg, userRep.getUsername())); - } - - UserHistory history = new UserHistory(); - PasswordCrypt passwordCrypt = null; - if (password != null) { - - // validate password meets basic requirements - validatePassword(certificate.getLocale(), password); - - // get new salt for user - byte[] salt = this.encryptionHandler.nextSalt(); - - // hash password - passwordCrypt = this.encryptionHandler.hashPassword(password, salt); - - history.setLastPasswordChange(ZonedDateTime.now()); - } - - // create new user - User newUser = createUser(userRep, history, passwordCrypt, false); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // validate this user may create such a user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, newUser))); - - // delegate to persistence handler - this.persistenceHandler.addUser(newUser); - persistModelAsync(); - - logger.info("Created new user " + newUser.getUsername()); - - return newUser.asUserRep(); - - } finally { - clearPassword(password); - } + () -> crudHandler.addUser(certificate, userRepParam, password)); } @Override public void addOrUpdateUsers(Certificate certificate, List userReps) throws PrivilegeException { this.lockingHandler.lockedExecute(PrivilegeHandler.class.getSimpleName(), - () -> internalAddOrUpdateUsers(certificate, userReps)); - } - - private void internalAddOrUpdateUsers(Certificate certificate, List userReps) throws PrivilegeException { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER); - - List toCreate = new ArrayList<>(); - List toUpdate = new ArrayList<>(); - - for (UserRep e : userReps) { - UserRep userRep = e.getCopy(); - - User user; - User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); - - if (existingUser == null) { - - // add user - - // make sure userId is not set - if (isNotEmpty(userRep.getUserId())) - throw new PrivilegeModelException("UserId can not be set when adding a new user!"); - - // set userId - userRep.setUserId(getUniqueId()); - - // first validate user - userRep.validate(); - - validateRolesExist(userRep); - - // create new user - user = createUser(userRep, new UserHistory(), null, false); - - // detect privilege conflicts - assertNoPrivilegeConflict(user); - - // validate this user may create such a user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, user))); - - toCreate.add(user); - logger.info("Creating new user " + user.getUsername()); - - } else { - - // update user - - if (userRep.getUserId() == null) - userRep.setUserId(existingUser.getUserId()); - - UserHistory history = existingUser.getHistory().getClone(); - user = createUser(userRep, history, existingUser.getPasswordCrypt(), - existingUser.isPasswordChangeRequested()); - - // detect privilege conflicts - assertNoPrivilegeConflict(user); - - // validate this user may modify this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, user))); - - toUpdate.add(user); - logger.info("Updating existing user " + user.getUsername()); - } - } - - // delegate to persistence handler - toCreate.forEach(user -> this.persistenceHandler.addUser(user)); - toUpdate.forEach(user -> this.persistenceHandler.replaceUser(user)); - persistModelAsync(); - - logger.info("Created " + toCreate.size() + " users"); - logger.info("Updated " + toUpdate.size() + " users"); + () -> crudHandler.addOrUpdateUsers(certificate, userReps)); } @Override public UserRep replaceUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { return this.lockingHandler.lockedExecuteWithResult(userRep.getUsername(), - () -> internalReplaceUser(certificate, userRep, password)); - } - - private UserRep internalReplaceUser(Certificate certificate, UserRep userRep, char[] password) { - try { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - - // first validate user - userRep.validate(); - - validateRolesExist(userRep); - - // validate user exists - User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); - if (existingUser == null) { - String msg = "User {0} can not be replaced as it does not exist!"; - throw new PrivilegeModelException(format(msg, userRep.getUsername())); - } - - // validate same userId - if (!existingUser.getUserId().equals(userRep.getUserId())) { - String msg = "UserId of existing user {0} does not match userRep {1}"; - msg = format(msg, existingUser.getUserId(), userRep.getUserId()); - throw new PrivilegeModelException(format(msg, userRep.getUsername())); - } - - UserHistory history = existingUser.getHistory().getClone(); - PasswordCrypt passwordCrypt = null; - if (password != null) { - - // validate password meets basic requirements - validatePassword(certificate.getLocale(), password); - - // get new salt for user - byte[] salt = this.encryptionHandler.nextSalt(); - - // hash password - passwordCrypt = this.encryptionHandler.hashPassword(password, salt); - - history.setLastPasswordChange(ZonedDateTime.now()); - } - - User newUser = createUser(userRep, history, passwordCrypt, existingUser.isPasswordChangeRequested()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // validate this user may modify this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); - - // delegate to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Replaced user " + newUser.getUsername()); - - return newUser.asUserRep(); - - } finally { - clearPassword(password); - } - } - - private void validateRolesExist(UserRep userRep) { - // validate all roles exist - for (String role : userRep.getRoles()) { - if (this.persistenceHandler.getRole(role) == null) { - String msg = "Can not add user {0} as role {1} does not exist!"; - msg = format(msg, userRep.getUsername(), role); - throw new PrivilegeModelException(msg); - } - } - } - - private User createUser(UserRep userRep, UserHistory history, PasswordCrypt passwordCrypt, - boolean passwordChangeRequested) { - String userId = userRep.getUserId(); - String userName = userRep.getUsername(); - String firstName = userRep.getFirstname(); - String lastName = userRep.getLastname(); - UserState state = userRep.getUserState(); - Set roles = userRep.getRoles(); - Locale locale = userRep.getLocale(); - Map properties = userRep.getProperties(); - return new User(userId, userName, passwordCrypt, firstName, lastName, state, roles, locale, properties, - passwordChangeRequested, history); + () -> crudHandler.replaceUser(certificate, userRep, password)); } @Override public UserRep updateUser(Certificate certificate, UserRep userRep) throws PrivilegeException { return this.lockingHandler.lockedExecuteWithResult(userRep.getUsername(), - () -> internalUpdateUser(certificate, userRep)); - } - - public UserRep internalUpdateUser(Certificate certificate, UserRep userRep) throws PrivilegeException { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - - // get existing user - User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", userRep.getUsername())); - - // if nothing to do, then stop - if (isEmpty(userRep.getFirstname()) && isEmpty(userRep.getLastname()) && userRep.getLocale() == null && - (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { - throw new PrivilegeModelException( - format("All updateable fields are empty for update of user {0}", userRep.getUsername())); - } - - String userId = existingUser.getUserId(); - String username = existingUser.getUsername(); - PasswordCrypt passwordCrypt = existingUser.getPasswordCrypt(); - String firstName = existingUser.getFirstname(); - String lastName = existingUser.getLastname(); - UserState userState = existingUser.getUserState(); - Set roles = existingUser.getRoles(); - Locale locale = existingUser.getLocale(); - Map propertyMap = existingUser.getProperties(); - - // get updated fields - if (isNotEmpty(userRep.getFirstname())) - firstName = userRep.getFirstname(); - if (isNotEmpty(userRep.getLastname())) - lastName = userRep.getLastname(); - if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) - propertyMap = userRep.getProperties(); - - if (userRep.getLocale() != null) - locale = userRep.getLocale(); - if (userRep.getUserState() != null) - userState = userRep.getUserState(); - if (userRep.getRoles() != null && !userRep.getRoles().equals(roles)) - roles = userRep.getRoles(); - - // create new user - User newUser = new User(userId, username, passwordCrypt, firstName, lastName, userState, roles, locale, - propertyMap, existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // validate this user may modify this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); - - // delegate to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Updated user " + newUser.getUsername()); - - // update any existing sessions for this user - updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); + () -> crudHandler.updateUser(certificate, userRep)); } @Override public UserRep removeUser(Certificate certificate, String username) { - return this.lockingHandler.lockedExecuteWithResult(username, () -> internalRemoveUser(certificate, username)); - } - - private UserRep internalRemoveUser(Certificate certificate, String username) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_USER); - - // validate user exists - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) { - String msg = "Can not remove User {0} because user does not exist!"; - throw new PrivilegeModelException(format(msg, username)); - } - - // validate this user may remove this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); - - // delegate user removal to persistence handler - invalidSessionsFor(existingUser); - this.persistenceHandler.removeUser(username); - persistModelAsync(); - - logger.info("Removed user " + username); - - return existingUser.asUserRep(); + return this.lockingHandler.lockedExecuteWithResult(username, + () -> crudHandler.removeUser(certificate, username)); } @Override public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { return this.lockingHandler.lockedExecuteWithResult(username, - () -> internalAddRoleToUser(certificate, username, roleName)); - } - - private UserRep internalAddRoleToUser(Certificate certificate, String username, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE_TO_USER); - - // get user - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - // validate that this user may add this role to this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE_TO_USER, new Tuple(existingUser, roleName))); - - // check that user not already has role - Set currentRoles = existingUser.getRoles(); - if (currentRoles.contains(roleName)) { - String msg = format("User {0} already has role {1}", username, roleName); - throw new PrivilegeModelException(msg); - } - - // validate that the role exists - if (this.persistenceHandler.getRole(roleName) == null) { - String msg = format("Role {0} does not exist!", roleName); - throw new PrivilegeModelException(msg); - } - - // create new user - Set newRoles = new HashSet<>(currentRoles); - newRoles.add(roleName); - - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, - existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(), - existingUser.getHistory().getClone()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Added role " + roleName + " to " + newUser.getUsername()); - - // update any existing sessions for this user - updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); + () -> crudHandler.addRoleToUser(certificate, username, roleName)); } @Override public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { return this.lockingHandler.lockedExecuteWithResult(username, - () -> internalRemoveRoleFromUser(certificate, username, roleName)); - } - - private UserRep internalRemoveRoleFromUser(Certificate certificate, String username, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE_FROM_USER); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - // validate that this user may remove this role from this user - prvCtx.validateAction( - new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); - - // ignore if user does not have role - Set currentRoles = existingUser.getRoles(); - if (!currentRoles.contains(roleName)) { - String msg = format("User {0} does not have role {1}", existingUser.getUsername(), roleName); - throw new PrivilegeModelException(msg); - } - - // create new user - Set newRoles = new HashSet<>(currentRoles); - newRoles.remove(roleName); - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, - existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(), - existingUser.getHistory().getClone()); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Removed role " + roleName + " from " + newUser.getUsername()); - - // update any existing sessions for this user - updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); + () -> crudHandler.removeRoleFromUser(certificate, username, roleName)); } @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { return this.lockingHandler.lockedExecuteWithResult(username, - () -> internalSetUserLocale(certificate, username, locale)); - } - - private UserRep internalSetUserLocale(Certificate certificate, String username, Locale locale) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_LOCALE); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - // create new user - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), - existingUser.getRoles(), locale, existingUser.getProperties(), existingUser.isPasswordChangeRequested(), - existingUser.getHistory().getClone()); - - // if the user is not setting their own locale, then make sure this user may set this user's locale - if (!certificate.getUsername().equals(username)) { - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_LOCALE, new Tuple(existingUser, newUser))); - } - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Set locale to " + locale + " for " + newUser.getUsername()); - - return newUser.asUserRep(); + () -> crudHandler.setUserLocale(certificate, username, locale)); } @Override public void requirePasswordChange(Certificate certificate, String username) throws PrivilegeException { - this.lockingHandler.lockedExecute(username, () -> internalRequirePasswordChange(certificate, username)); - } - - private void internalRequirePasswordChange(Certificate certificate, String username) throws PrivilegeException { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_REQUIRE_PASSWORD_CHANGE); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - if (existingUser.getUserState().isRemote()) - throw new PrivilegeModelException(format("User {0} is remote and can not set password!", username)); - - // create new user - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), - existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(), true, - existingUser.getHistory().getClone()); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Requiring user " + newUser.getUsername() + " to change their password on next login."); + this.lockingHandler.lockedExecute(username, () -> crudHandler.requirePasswordChange(certificate, username)); } @Override public void setUserPassword(Certificate certificate, String username, char[] password) { - this.lockingHandler.lockedExecute(username, () -> internalSetUserPassword(certificate, username, password)); - } - - private void internalSetUserPassword(Certificate certificate, String username, char[] password) { - - // we don't want the user to worry about whitespace - username = trimOrEmpty(username); - - try { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_PASSWORD); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - UserHistory history = existingUser.getHistory().getClone(); - - PasswordCrypt passwordCrypt = null; - if (password != null) { - - // validate password meets basic requirements - validatePassword(certificate.getLocale(), password); - - // get new salt for user - byte[] salt = this.encryptionHandler.nextSalt(); - - // hash password - passwordCrypt = this.encryptionHandler.hashPassword(password, salt); - - history.setLastPasswordChange(ZonedDateTime.now()); - } - - // create new user - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordCrypt, - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), - existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(), false, history); - - if (!certificate.getUsername().equals(username)) { - // check that the user may change their own password - Tuple value = new Tuple(existingUser, newUser); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, value)); - } - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - if (certificate.getUsage() == Usage.SET_PASSWORD) - invalidate(certificate); - - if (password == null) - logger.info("Cleared password for " + newUser.getUsername()); - else - logger.info("Updated password for " + newUser.getUsername()); - - } finally { - clearPassword(password); - } + this.lockingHandler.lockedExecute(username, () -> crudHandler.setUserPassword(certificate, username, password)); } @Override public UserRep setUserState(Certificate certificate, String username, UserState state) { return this.lockingHandler.lockedExecuteWithResult(username, - () -> internalSetUserState(certificate, username, state)); - } - - private UserRep internalSetUserState(Certificate certificate, String username, UserState state) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_STATE); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(format("User {0} does not exist!", username)); - - // create new user - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), state, existingUser.getRoles(), - existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(), - existingUser.getHistory().getClone()); - - // validate that this user may modify this user's state - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser))); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - persistModelAsync(); - - logger.info("Set state of user " + newUser.getUsername() + " to " + state); - - return newUser.asUserRep(); + () -> crudHandler.setUserState(certificate, username, state)); } @Override public RoleRep addRole(Certificate certificate, RoleRep roleRep) { return this.lockingHandler.lockedExecuteWithResult(roleRep.getName(), - () -> internalAddRole(certificate, roleRep)); - } - - private RoleRep internalAddRole(Certificate certificate, RoleRep roleRep) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE); - - // first validate role - roleRep.validate(); - - // validate role does not exist - if (this.persistenceHandler.getRole(roleRep.getName()) != null) { - String msg = format("Can not add role {0} as it already exists!", roleRep.getName()); - throw new PrivilegeModelException(msg); - } - - // create new role from RoleRep - Role newRole = new Role(roleRep); - - // validate that this user may add this new role - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE, new Tuple(null, newRole))); - - // validate policy if not null - validatePolicies(newRole); - - // delegate to persistence handler - this.persistenceHandler.addRole(newRole); - persistModelAsync(); - - logger.info("Added new role " + newRole.getName()); - - return newRole.asRoleRep(); + () -> crudHandler.addRole(certificate, roleRep)); } @Override public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { return this.lockingHandler.lockedExecuteWithResult(roleRep.getName(), - () -> internalReplaceRole(certificate, roleRep)); - } - - private RoleRep internalReplaceRole(Certificate certificate, RoleRep roleRep) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); - - // first validate role - roleRep.validate(); - - // validate role does exist - Role existingRole = this.persistenceHandler.getRole(roleRep.getName()); - if (existingRole == null) { - String msg = format("Can not replace role {0} as it does not exist!", roleRep.getName()); - throw new PrivilegeModelException(msg); - } - - // create new role from RoleRep - Role newRole = new Role(roleRep); - - // detect privilege conflicts - assertNoPrivilegeConflict(newRole); - - // validate that this user may modify this role - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); - - // validate policy if not null - validatePolicies(newRole); - - // delegate to persistence handler - this.persistenceHandler.replaceRole(newRole); - persistModelAsync(); - - logger.info("Replaced role " + newRole.getName()); - - // update any existing certificates with new role - updateExistingSessionsWithNewRole(newRole); - - return newRole.asRoleRep(); + () -> crudHandler.replaceRole(certificate, roleRep)); } @Override public RoleRep removeRole(Certificate certificate, String roleName) { - return this.lockingHandler.lockedExecuteWithResult(roleName, () -> internalRemoveRole(certificate, roleName)); - } - - private RoleRep internalRemoveRole(Certificate certificate, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE); - - // validate no user is using this role - Set roles = new HashSet<>(Collections.singletonList(roleName)); - UserRep selector = new UserRep(null, null, null, null, null, roles, null, null, null); - List usersWithRole = queryUsers(certificate, selector); - if (!usersWithRole.isEmpty()) { - String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); - String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; - msg = format(msg, roleName, usersWithRole.size(), usersS); - throw new PrivilegeModelException(msg); - } - - // validate role exists - Role existingRole = this.persistenceHandler.getRole(roleName); - if (existingRole == null) { - String msg = "Can not remove Role {0} because role does not exist!"; - throw new PrivilegeModelException(format(msg, roleName)); - } - - // validate that this user may remove this role - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE, new Tuple(null, existingRole))); - - // delegate role removal to persistence handler - this.persistenceHandler.removeRole(roleName); - persistModelAsync(); - - logger.info("Removed role " + roleName); - - return existingRole.asRoleRep(); + return this.lockingHandler.lockedExecuteWithResult(roleName, + () -> crudHandler.removeRole(certificate, roleName)); } @Override public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { return this.lockingHandler.lockedExecuteWithResult(roleName, - () -> internalAddOrReplacePrivilegeOnRole(certificate, roleName, privilegeRep)); - } - - private RoleRep internalAddOrReplacePrivilegeOnRole(Certificate certificate, String roleName, - PrivilegeRep privilegeRep) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); - - // validate PrivilegeRep - privilegeRep.validate(); - - // get role - Role existingRole = this.persistenceHandler.getRole(roleName); - if (existingRole == null) { - String msg = format("Role {0} does not exist!", roleName); - throw new PrivilegeModelException(msg); - } - - // validate that policy exists if needed - String policy = privilegeRep.getPolicy(); - if (policy != null && !this.policyMap.containsKey(policy)) { - String msg = "Policy {0} for Privilege {1} does not exist"; - msg = format(msg, policy, privilegeRep.getName()); - throw new PrivilegeModelException(msg); - } - - // create new role with the additional privilege - IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); - - // copy existing privileges - Set existingPrivilegeNames = existingRole.getPrivilegeNames(); - Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); - for (String name : existingPrivilegeNames) { - IPrivilege privilege = existingRole.getPrivilege(name); - privilegeMap.put(name, privilege); - } - - // add new one - privilegeMap.put(newPrivilege.getName(), newPrivilege); - - // create new role - Role newRole = new Role(existingRole.getName(), privilegeMap); - - // detect privilege conflicts - assertNoPrivilegeConflict(newRole); - - // validate that this user may modify this role - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); - - // delegate role replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - persistModelAsync(); - - logger.info("Added/replaced privilege " + privilegeRep.getName() + " to " + roleName); - - // update any existing certificates with new role - updateExistingSessionsWithNewRole(newRole); - - return newRole.asRoleRep(); + () -> crudHandler.addOrReplacePrivilegeOnRole(certificate, roleName, privilegeRep)); } @Override public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { return this.lockingHandler.lockedExecuteWithResult(roleName, - () -> internalRemovePrivilegeFromRole(certificate, roleName, privilegeName)); + () -> crudHandler.removePrivilegeFromRole(certificate, roleName, privilegeName)); } - private RoleRep internalRemovePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = validate(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); - - // get role - Role existingRole = this.persistenceHandler.getRole(roleName); - if (existingRole == null) { - throw new PrivilegeModelException(format("Role {0} does not exist!", roleName)); - } - - // ignore if role does not have privilege - if (!existingRole.hasPrivilege(privilegeName)) { - String msg = format("Role {0} does not have Privilege {1}", roleName, privilegeName); - throw new PrivilegeModelException(msg); - } - - // create new set of privileges with out the to removed privilege - Set privilegeNames = existingRole.getPrivilegeNames(); - Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); - for (String name : privilegeNames) { - IPrivilege privilege = existingRole.getPrivilege(name); - if (!privilege.getName().equals(privilegeName)) - newPrivileges.put(privilege.getName(), privilege); - } - - // create new role - Role newRole = new Role(existingRole.getName(), newPrivileges); - - // validate that this user may modify this role - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - persistModelAsync(); - - logger.info("Removed privilege " + privilegeName + " from " + roleName); - - // update any existing certificates with new role - updateExistingSessionsWithNewRole(newRole); - - return newRole.asRoleRep(); - } - - /** - * Replaces any existing {@link PrivilegeContext} for the given user by updating with the new user object - * - * @param newUser the new user to update with - */ - private void updateExistingSessionsForUser(User newUser) { - List contexts = new ArrayList<>(this.privilegeContextMap.values()); - for (PrivilegeContext ctx : contexts) { - if (ctx.getUserRep().getUsername().equals(newUser.getUsername())) { - Certificate cert = ctx.getCertificate(); - cert = buildCertificate(cert.getUsage(), newUser, cert.getAuthToken(), cert.getSessionId(), - cert.getSource(), cert.getLoginTime(), cert.isKeepAlive()); - PrivilegeContext privilegeContext = buildPrivilegeContext(cert, newUser); - this.privilegeContextMap.put(cert.getSessionId(), privilegeContext); - } - } - - persistSessionsAsync(); - } - - /** - * Replaces any existing {@link PrivilegeContext} for users with the given role - * - * @param role the role to update with - */ - private void updateExistingSessionsWithNewRole(Role role) { - List contexts = new ArrayList<>(this.privilegeContextMap.values()); - for (PrivilegeContext ctx : contexts) { - if (ctx.getUserRep().hasRole(role.getName())) { - User user = this.persistenceHandler.getUser(ctx.getUsername()); - if (user == null) - continue; - - Certificate cert = ctx.getCertificate(); - cert = buildCertificate(cert.getUsage(), user, cert.getAuthToken(), cert.getSessionId(), - cert.getSource(), cert.getLoginTime(), cert.isKeepAlive()); - PrivilegeContext privilegeContext = buildPrivilegeContext(cert, user); - this.privilegeContextMap.put(cert.getSessionId(), privilegeContext); - } - } - - persistSessionsAsync(); - } - - private void invalidSessionsFor(User user) { + void invalidSessionsFor(User user) { List contexts = new ArrayList<>(this.privilegeContextMap.values()); for (PrivilegeContext ctx : contexts) { if (ctx.getUserRep().getUsername().equals(user.getUsername())) @@ -1351,16 +377,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate the response UserChallenge userChallenge = this.userChallengeHandler.validateResponse(user, challenge); - String authToken = this.encryptionHandler.nextToken(); - String sessionId = UUID.randomUUID().toString(); - // create a new certificate, with details of the user + // initialize a new privilege context Usage usage = userChallenge.getUsage(); - Certificate certificate = buildCertificate(usage, user, authToken, sessionId, userChallenge.getSource(), - ZonedDateTime.now(), false); - - PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); - this.privilegeContextMap.put(sessionId, privilegeContext); + Certificate certificate = buildPrivilegeContext(usage, user, userChallenge.getSource(), ZonedDateTime.now(), + false).getCertificate(); if (!source.equals("unknown") && !source.equals(userChallenge.getSource())) { logger.warn("Challenge request and response source's are different: request: " + userChallenge.getSource() + @@ -1402,9 +423,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { User user = checkCredentialsAndUserState(username, password); // validate user has at least one role - Set userRoles = user.getRoles(); - if (userRoles.isEmpty()) - throw new InvalidCredentialsException(format("User {0} does not have any roles defined!", username)); + if (streamAllRolesForUser(this.persistenceHandler, user).findAny().isEmpty()) + throw new InvalidCredentialsException( + format("User {0} does not have any groups or roles defined!", username)); if (user.isPasswordChangeRequested()) { if (usage == Usage.SINGLE) @@ -1412,26 +433,18 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { usage = Usage.SET_PASSWORD; } - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = UUID.randomUUID().toString(); - - // create a new certificate, with details of the user - Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, ZonedDateTime.now(), - keepAlive); - - PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); - this.privilegeContextMap.put(sessionId, privilegeContext); + // initialize a new privilege context + Certificate certificate = buildPrivilegeContext(usage, user, source, ZonedDateTime.now(), + keepAlive).getCertificate(); persistSessionsAsync(); // save last login - if (user.getHistory().isFirstLoginEmpty()) - user.getHistory().setFirstLogin(ZonedDateTime.now()); - user.getHistory().setLastLogin(ZonedDateTime.now()); - this.persistenceHandler.replaceUser(user); + UserHistory history = user.getHistory(); + if (history.isFirstLoginEmpty()) + history = history.withFirstLogin(ZonedDateTime.now()); + history = history.withLastLogin(ZonedDateTime.now()); + this.persistenceHandler.replaceUser(user.withHistory(history)); persistModelAsync(); // log @@ -1452,6 +465,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } + /** + * Returns a {@link Stream} of all roles of the given user. This includes the roles referenced by the user's groups + * + * @param user the user for which to stream the roles + * + * @return a stream of role names + */ + public static Stream streamAllRolesForUser(PersistenceHandler persistenceHandler, User user) { + return Stream.concat(user.getRoles().stream(), + user.groups().stream().map(persistenceHandler::getGroup).filter(Objects::nonNull) + .flatMap(g -> g.roles().stream())); + } + @Override public Certificate authenticateSingleSignOn(Object data, boolean keepAlive) throws PrivilegeException { return authenticateSingleSignOn(data, "unknown", keepAlive); @@ -1473,31 +499,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throws PrivilegeException { DBC.PRE.assertEquals("SSO Users must have UserState.REMOTE!", UserState.REMOTE, user.getUserState()); - user.getHistory().setLastLogin(ZonedDateTime.now()); + UserHistory history = user.getHistory(); + history = history.withLastLogin(ZonedDateTime.now()); // persist this user User internalUser = this.persistenceHandler.getUser(user.getUsername()); if (internalUser == null) { - user.getHistory().setFirstLogin(ZonedDateTime.now()); - this.persistenceHandler.addUser(user); + history = history.withFirstLogin(ZonedDateTime.now()); + this.persistenceHandler.addUser(user.withHistory(history)); } else { - user.getHistory().setFirstLogin(internalUser.getHistory().getFirstLogin()); - this.persistenceHandler.replaceUser(user); + history = history.withFirstLogin(internalUser.getHistory().getFirstLogin()); + this.persistenceHandler.replaceUser(user.withHistory(history)); } persistModelAsync(); - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = UUID.randomUUID().toString(); - - // create a new certificate, with details of the user - Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source, ZonedDateTime.now(), - keepAlive); - - PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); - this.privilegeContextMap.put(sessionId, privilegeContext); + // initialize a new privilege context + Certificate certificate = buildPrivilegeContext(Usage.ANY, user, source, ZonedDateTime.now(), + keepAlive).getCertificate(); persistSessionsAsync(); @@ -1534,27 +552,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // check the password User user = this.persistenceHandler.getUser(certificate.getUsername()); - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = UUID.randomUUID().toString(); - - // create a new certificate, with details of the user - Certificate refreshedCert = buildCertificate(certificate.getUsage(), user, authToken, sessionId, source, + // initialize a new privilege context + PrivilegeContext refreshedContext = buildPrivilegeContext(certificate.getUsage(), user, source, ZonedDateTime.now(), true); - PrivilegeContext privilegeContext = buildPrivilegeContext(refreshedCert, user); - this.privilegeContextMap.put(sessionId, privilegeContext); - // invalidate the previous session invalidate(certificate); // log - logger.info(format("User {0} refreshed session: {1}", user.getUsername(), refreshedCert)); + Certificate refreshedCertificate = refreshedContext.getCertificate(); + logger.info(format("User {0} refreshed session: {1}", user.getUsername(), refreshedCertificate)); // return the certificate - return refreshedCert; + return refreshedCertificate; } catch (PrivilegeException e) { throw e; @@ -1566,32 +576,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - private Certificate buildCertificate(Usage usage, User user, String authToken, String sessionId, String source, - ZonedDateTime loginTime, boolean keepAlive) { - DBC.PRE.assertNotEmpty("source must not be empty!", source); - Set userRoles = user.getRoles(); - return new Certificate(usage, sessionId, user.getUsername(), user.getFirstname(), user.getLastname(), - user.getUserState(), authToken, source, loginTime, keepAlive && this.allowSessionRefresh, - user.getLocale(), userRoles, new HashMap<>(user.getProperties())); - } - - protected synchronized void persistModelAsync() { - if (!this.autoPersistOnUserChangesData) - return; - - // async execution, max. once per second - if (this.persistModelTask != null) - this.persistModelTask.cancel(true); - this.persistModelTask = this.executorService.schedule( - () -> this.lockingHandler.lockedExecute("persist-model", () -> { - try { - this.persistenceHandler.persist(); - } catch (Exception e) { - logger.error("Failed to persist model!", e); - } - }), 1, TimeUnit.SECONDS); - } - private synchronized boolean persistSessionsAsync() { if (!this.persistSessions) return false; @@ -1599,28 +583,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // async execution, max. once per second if (this.persistSessionsTask != null) this.persistSessionsTask.cancel(true); - this.persistSessionsTask = this.executorService.schedule( - () -> this.lockingHandler.lockedExecute("persist-sessions", () -> { + this.persistSessionsTask = this.executorService.schedule(() -> { - List sessions = new ArrayList<>(this.privilegeContextMap.values()).stream() - .map(PrivilegeContext::getCertificate).filter(c -> !c.getUserState().isSystem()) - .collect(Collectors.toList()); + // get sessions reference + AtomicReference> sessions = new AtomicReference<>(); + this.lockingHandler.lockedExecute("persist-sessions", () -> sessions.set( + new ArrayList<>(this.privilegeContextMap.values()).stream().map(PrivilegeContext::getCertificate) + .filter(c -> !c.getUserState().isSystem()).collect(toList()))); - try (OutputStream out = Files.newOutputStream(this.persistSessionsPath.toPath()); - OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, out)) { + // write the sessions + try (OutputStream out = Files.newOutputStream(this.persistSessionsPath.toPath()); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, out)) { - CertificateStubsSaxWriter writer = new CertificateStubsSaxWriter(sessions, outputStream); - writer.write(); - outputStream.flush(); + CertificateStubsSaxWriter writer = new CertificateStubsSaxWriter(sessions.get(), outputStream); + writer.write(); + outputStream.flush(); - } catch (Exception e) { - logger.error("Failed to persist sessions!", e); - if (this.persistSessionsPath.exists() && !this.persistSessionsPath.delete()) { - logger.error("Failed to delete sessions file after failing to write to it, at " + - this.persistSessionsPath.getAbsolutePath()); - } - } - }), 1, TimeUnit.SECONDS); + } catch (Exception e) { + logger.error("Failed to persist sessions!", e); + if (this.persistSessionsPath.exists() && !this.persistSessionsPath.delete()) { + logger.error("Failed to delete sessions file after failing to write to it, at " + + this.persistSessionsPath.getAbsolutePath()); + } + } + }, 1, TimeUnit.SECONDS); return true; } @@ -1663,11 +649,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } for (CertificateStub stub : certificateStubs) { - Usage usage = stub.getUsage(); String username = stub.getUsername(); - String sessionId = stub.getSessionId(); - String authToken = stub.getAuthToken(); - String source = stub.getSource(); User user = this.persistenceHandler.getUser(username); if (user == null) { logger.error("Ignoring session data for missing user " + username); @@ -1679,20 +661,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { continue; } - Set userRoles = user.getRoles(); - if (userRoles.isEmpty()) { - logger.error("Ignoring session data for user " + username + " which has not roles defined!"); + if (streamAllRolesForUser(this.persistenceHandler, user).findAny().isEmpty()) { + logger.error("Ignoring session data for user " + username + " which has no roles or groups defined!"); continue; } - // create a new certificate, with details of the user - Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, stub.getLoginTime(), - stub.isKeepAlive()); - certificate.setLocale(stub.getLocale()); - certificate.setLastAccess(stub.getLastAccess()); - - PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); - this.privilegeContextMap.put(sessionId, privilegeContext); + // initialize a new privilege context + buildPrivilegeContext(user, stub); } logger.info("Loaded " + this.privilegeContextMap.size() + " sessions."); @@ -1741,24 +716,24 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } PasswordCrypt userPasswordCrypt = user.getPasswordCrypt(); - if (userPasswordCrypt == null || userPasswordCrypt.getPassword() == null) + if (userPasswordCrypt == null || userPasswordCrypt.password() == null) throw new InvalidCredentialsException(format("User {0} has no password and may not login!", username)); // we only work with hashed passwords PasswordCrypt requestPasswordCrypt; - if (userPasswordCrypt.getSalt() == null) { + if (userPasswordCrypt.salt() == null) { requestPasswordCrypt = this.encryptionHandler.hashPasswordWithoutSalt(password); - } else if (userPasswordCrypt.getHashAlgorithm() == null || userPasswordCrypt.getHashIterations() == -1 || - userPasswordCrypt.getHashKeyLength() == -1) { - requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.getSalt()); + } else if (userPasswordCrypt.hashAlgorithm() == null || userPasswordCrypt.hashIterations() == -1 || + userPasswordCrypt.hashKeyLength() == -1) { + requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.salt()); } else { - requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.getSalt(), - userPasswordCrypt.getHashAlgorithm(), userPasswordCrypt.getHashIterations(), - userPasswordCrypt.getHashKeyLength()); + requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.salt(), + userPasswordCrypt.hashAlgorithm(), userPasswordCrypt.hashIterations(), + userPasswordCrypt.hashKeyLength()); } // validate password - if (!Arrays.equals(requestPasswordCrypt.getPassword(), userPasswordCrypt.getPassword())) + if (!Arrays.equals(requestPasswordCrypt.password(), userPasswordCrypt.password())) throw new InvalidCredentialsException(format("Password is incorrect for {0}", username)); // see if we need to update the hash @@ -1774,8 +749,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user user = new User(user.getUserId(), user.getUsername(), newPasswordCrypt, user.getFirstname(), - user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties(), - user.isPasswordChangeRequested(), user.getHistory().getClone()); + user.getLastname(), user.getUserState(), user.getGroups(), user.getRoles(), user.getLocale(), + user.getProperties(), user.isPasswordChangeRequested(), user.getHistory()); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(user); @@ -1787,84 +762,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return user; } - /** - * Builds a {@link PrivilegeContext} for the given {@link User} and its {@link Certificate} - * - * @param certificate the certificate for which to build the {@link PrivilegeContext} - * @param user the user for which to build the {@link PrivilegeContext} - * - * @return the {@link PrivilegeContext} - */ - private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) { - - Set userRoles = user.getRoles(); - Map privileges = new HashMap<>(); - Map policies = new HashMap<>(); - - // get a cache of the privileges and policies for this user - for (String roleName : userRoles) { - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - logger.error("Role " + roleName + " does not exist for user " + user.getUsername()); - continue; - } - - Set privilegeNames = role.getPrivilegeNames(); - for (String privilegeName : privilegeNames) { - - IPrivilege privilege = role.getPrivilege(privilegeName); - if (privilege == null) { - logger.error(format("The Privilege {0} does not exist for role {1}", privilegeName, roleName)); - continue; - } - - // cache the privilege - if (privileges.containsKey(privilegeName)) { - if (this.privilegeConflictResolution.isStrict()) { - throw new PrivilegeModelException( - format("User has conflicts for privilege {0} with role {1}", privilegeName, roleName)); - } - - IPrivilege priv = privileges.get(privilegeName); - boolean allAllowed = priv.isAllAllowed() || privilege.isAllAllowed(); - Set allowList; - Set denyList; - if (allAllowed) { - allowList = Collections.emptySet(); - denyList = Collections.emptySet(); - } else { - allowList = new HashSet<>(priv.getAllowList()); - allowList.addAll(privilege.getAllowList()); - denyList = new HashSet<>(priv.getDenyList()); - denyList.addAll(privilege.getDenyList()); - } - priv = new PrivilegeImpl(priv.getName(), priv.getPolicy(), allAllowed, denyList, allowList); - - privileges.put(privilegeName, priv); - continue; - } - - privileges.put(privilegeName, privilege); - - // cache the policy for the privilege - String policyName = privilege.getPolicy(); - if (policies.containsKey(policyName)) - continue; - - PrivilegePolicy policy = getPolicy(policyName); - if (policy == null) { - logger.error(format("The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); - continue; - } - - policies.put(policyName, policy); - } - } - - UserRep userRep = user.asUserRep(); - return new PrivilegeContext(userRep, certificate, privileges, policies); - } - @Override public boolean invalidate(Certificate certificate) { @@ -1898,7 +795,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate user state is system if (ctx.getUserRep().getUserState() != UserState.SYSTEM) { - String msg = "The PrivilegeContext's user {0} does not have expected user state {1}"; + String msg = "The PrivilegeContext user {0} does not have expected user state {1}"; msg = format(msg, ctx.getUserRep().getUsername(), UserState.SYSTEM); throw new PrivilegeException(msg); } @@ -1918,19 +815,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(msg); } - // validate certificate has not been tampered with - Certificate sessionCertificate = privilegeContext.getCertificate(); - if (!sessionCertificate.equals(certificate)) { - String msg = "Received illegal certificate for session id {0}"; - msg = format(msg, certificate.getSessionId()); - throw new PrivilegeException(msg); - } - certificate.setLastAccess(ZonedDateTime.now()); - - if (!certificate.getSource().equals(this.identifier)) - throw new IllegalStateException( - "Source has changed for certificate " + certificate + " to " + certificate.getSource()); } @Override @@ -1997,6 +882,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } + protected synchronized void persistModelAsync() { + if (!this.autoPersistOnUserChangesData) + return; + + // async execution, max. once per second + if (this.persistModelTask != null) + this.persistModelTask.cancel(true); + this.persistModelTask = this.executorService.schedule( + () -> this.lockingHandler.lockedExecute("persist-model", () -> { + try { + this.persistenceHandler.persist(); + } catch (Exception e) { + logger.error("Failed to persist model!", e); + } + }), 1, TimeUnit.SECONDS); + } + @Override public boolean persistSessions(Certificate certificate, String source) { @@ -2052,7 +954,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.executorService = executorService; this.lockingHandler = new ElementLockingHandler<>(executorService, TimeUnit.SECONDS, 10L); - this.policyMap = policyMap; + this.policyMap = Map.copyOf(policyMap); this.encryptionHandler = encryptionHandler; this.passwordStrengthHandler = passwordStrengthHandler; this.persistenceHandler = persistenceHandler; @@ -2067,9 +969,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.allowSessionRefresh = Boolean.parseBoolean(parameterMap.get(PARAM_ALLOW_SESSION_REFRESH)); this.disallowSourceChange = Boolean.parseBoolean(parameterMap.get(PARAM_DISALLOW_SOURCE_CHANGE)); + this.crudHandler = new PrivilegeCrudHandler(this, this.policyMap, this.privilegeConflictResolution); + // validate policies on privileges of Roles for (Role role : persistenceHandler.getAllRoles()) { - validatePolicies(role); + this.crudHandler.validatePolicies(role); } // validate privilege conflicts @@ -2175,10 +1079,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes()); - // build our identifier - byte[] encrypt = AesCryptoHelper.encrypt(this.secretKey, "PrivilegeHandler".getBytes()); - this.identifier = Base64.getEncoder().encodeToString(encrypt); - // remove secrets parameterMap.remove(PARAM_SECRET_KEY); parameterMap.remove(PARAM_SECRET_SALT); @@ -2193,7 +1093,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { List users = this.persistenceHandler.getAllUsers(); for (User user : users) { Map privilegeNames = new HashMap<>(); - conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + conflicts.addAll(this.crudHandler.detectPrivilegeConflicts(privilegeNames, user)); } if (!conflicts.isEmpty()) { @@ -2204,95 +1104,44 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - private void assertNoPrivilegeConflict(User user) { - if (this.privilegeConflictResolution.isStrict()) { - Map privilegeNames = new HashMap<>(); - List conflicts = detectPrivilegeConflicts(privilegeNames, user); - if (!conflicts.isEmpty()) { - String msg = String.join("\n", conflicts); - throw new PrivilegeModelException(msg); - } - } - } - - private void assertNoPrivilegeConflict(Role role) { - if (!this.privilegeConflictResolution.isStrict()) - return; - - Map privilegeNames = new HashMap<>(); - for (String privilegeName : role.getPrivilegeNames()) { - privilegeNames.put(privilegeName, role.getName()); + /** + * Replaces any existing {@link PrivilegeContext} for the given user by updating with the new user object + * + * @param newUser the new user to update with + */ + void updateExistingSessionsForUser(User newUser) { + List contexts = new ArrayList<>(this.privilegeContextMap.values()); + for (PrivilegeContext ctx : contexts) { + if (!ctx.getUserRep().getUsername().equals(newUser.getUsername())) + continue; + replacePrivilegeContextForCert(newUser, ctx.getCertificate()); } - List conflicts = new ArrayList<>(); - List users = this.persistenceHandler.getAllUsers(); - for (User user : users) { - if (user.hasRole(role.getName())) - conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); - } - - if (!conflicts.isEmpty()) { - String msg = String.join("\n", conflicts); - throw new PrivilegeModelException(msg); - } - } - - private List detectPrivilegeConflicts(Map privilegeNames, User user) { - List conflicts = new ArrayList<>(); - - Set userRoles = user.getRoles(); - for (String roleName : userRoles) { - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) - throw new IllegalStateException("Role " + roleName + " does not exist for user " + user.getUsername()); - for (String privilegeName : role.getPrivilegeNames()) { - String roleOrigin = privilegeNames.get(privilegeName); - if (roleOrigin == null) { - privilegeNames.put(privilegeName, roleName); - } else if (!roleOrigin.equals(roleName)) { - String msg = "User {0} has conflicts for privilege {1} on roles {2} and {3}"; - msg = format(msg, user.getUsername(), privilegeName, roleOrigin, roleName); - conflicts.add(msg); - } - } - } - - return conflicts; + persistSessionsAsync(); } /** - * Validates that the policies which are not null on the privileges of the role exist + * Replaces any existing {@link PrivilegeContext} for users with the given role * - * @param role the role for which the policies are to be checked + * @param role the role to update with */ - private void validatePolicies(Role role) { - for (String privilegeName : role.getPrivilegeNames()) { - IPrivilege privilege = role.getPrivilege(privilegeName); - String policy = privilege.getPolicy(); - if (policy != null && !this.policyMap.containsKey(policy)) { - String msg = "Policy {0} for Privilege {1} does not exist on role {2}"; - msg = format(msg, policy, privilege.getName(), role); - throw new PrivilegeModelException(msg); - } + void updateExistingSessionsWithNewRole(Role role) { + List contexts = new ArrayList<>(this.privilegeContextMap.values()); + for (PrivilegeContext ctx : contexts) { + if (!ctx.getUserRep().hasRole(role.getName())) + continue; + User user = this.persistenceHandler.getUser(ctx.getUsername()); + if (user == null) + continue; + replacePrivilegeContextForCert(user, ctx.getCertificate()); } - } - /** - * Passwords should not be kept as strings, as string are immutable, this method thus clears the char array so that - * the password is not in memory anymore - * - * @param password the char array containing the passwort which is to be set to zeroes - */ - private void clearPassword(char[] password) { - if (password != null) - Arrays.fill(password, (char) 0); + persistSessionsAsync(); } @Override public void runAs(String username, SystemAction action) throws Exception { - PrivilegeContext systemUserPrivilegeContext = initiateSystemPrivilege(username, action); - String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); try { // perform the action @@ -2304,9 +1153,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public T runWithResult(String username, SystemActionWithResult action) throws Exception { - PrivilegeContext systemUserPrivilegeContext = initiateSystemPrivilege(username, action); - String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); try { // perform the action @@ -2318,14 +1165,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public PrivilegeContext openSystemUserContext(String username) throws PrivilegeException { - - // get privilegeContext for this system user - PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(username); - - String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); - this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); - - return systemUserPrivilegeContext; + return buildSystemUserPrivilegeContext(username); } private PrivilegeContext initiateSystemPrivilege(String username, Restrictable restrictable) { @@ -2334,15 +1174,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (restrictable == null) throw new PrivilegeException("action may not be null!"); - // get privilegeContext for this system user - PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(username); - - // validate this system user may perform the given action + PrivilegeContext systemUserPrivilegeContext = buildSystemUserPrivilegeContext(username); systemUserPrivilegeContext.validateAction(restrictable); - - String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); - this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); - return systemUserPrivilegeContext; } @@ -2354,7 +1187,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @return the {@link Certificate} for this system user */ - private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { + private PrivilegeContext buildSystemUserPrivilegeContext(String systemUsername) { // get user object User user = this.persistenceHandler.getUser(systemUsername); @@ -2384,62 +1217,42 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(msg); } - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = UUID.randomUUID().toString(); - - // create a new certificate, with details of the user - Certificate systemUserCertificate = buildCertificate(Usage.ANY, user, authToken, sessionId, this.identifier, - ZonedDateTime.now(), false); - - // create and save a new privilege context - PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); + // initialize a new privilege context + PrivilegeContext privilegeContext = buildPrivilegeContext(Usage.ANY, user, "internal", ZonedDateTime.now(), + false); // log if (logger.isDebugEnabled()) { String msg = "The system user ''{0}'' is logged in with session {1}"; - msg = format(msg, user.getUsername(), systemUserCertificate.getSessionId()); + msg = format(msg, user.getUsername(), privilegeContext.getCertificate().getSessionId()); logger.info(msg); } return privilegeContext; } - /** - *

- * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} - * is not stored in a database. The privilege name is a class name and is then used to instantiate a new - * {@link PrivilegePolicy} object - *

- * - * @param policyName the class name of the {@link PrivilegePolicy} object to return - * - * @return the {@link PrivilegePolicy} object - * - * @throws PrivilegeException if the {@link PrivilegePolicy} object for the given policy name could not be - * instantiated - */ - private PrivilegePolicy getPolicy(String policyName) { + private void buildPrivilegeContext(User user, CertificateStub stub) { + PrivilegeContext privilegeContext = new PrivilegeContextBuilder(this).buildPrivilegeContext(stub.getUsage(), + user, stub.getAuthToken(), stub.getSessionId(), stub.getSource(), stub.getLoginTime(), + stub.isKeepAlive()); + Certificate certificate = privilegeContext.getCertificate(); + certificate.setLocale(stub.getLocale()); + certificate.setLastAccess(stub.getLastAccess()); + this.privilegeContextMap.put(certificate.getSessionId(), privilegeContext); + } - // get the policies class - Class policyClazz = this.policyMap.get(policyName); - if (policyClazz == null) { - return null; - } + private void replacePrivilegeContextForCert(User user, Certificate cert) { + PrivilegeContext privilegeContext = new PrivilegeContextBuilder(this).buildPrivilegeContext(cert.getUsage(), + user, cert.getAuthToken(), cert.getSessionId(), cert.getSource(), cert.getLoginTime(), + cert.isKeepAlive()); + this.privilegeContextMap.put(privilegeContext.getCertificate().getSessionId(), privilegeContext); + } - // instantiate the policy - PrivilegePolicy policy; - try { - - policy = policyClazz.getConstructor().newInstance(); - } catch (Exception e) { - String msg = "The class for the policy with the name {0} does not exist!{1}"; - msg = format(msg, policyName, policyName); - throw new PrivilegeModelException(msg, e); - } - - return policy; + public PrivilegeContext buildPrivilegeContext(Usage usage, User user, String source, ZonedDateTime loginTime, + boolean keepAlive) { + PrivilegeContext privilegeContext = new PrivilegeContextBuilder(this).buildPrivilegeContext(usage, user, source, + loginTime, keepAlive); + this.privilegeContextMap.put(privilegeContext.getCertificate().getSessionId(), privilegeContext); + return privilegeContext; } } diff --git a/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java index 5e9eb52fc..cffddb1bf 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java @@ -1,10 +1,11 @@ package li.strolch.privilege.handler; -import static java.lang.String.join; -import static java.util.stream.Collectors.toSet; -import static li.strolch.privilege.base.PrivilegeConstants.*; -import static li.strolch.utils.helper.StringHelper.isEmpty; -import static li.strolch.utils.helper.StringHelper.isNotEmpty; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import li.strolch.privilege.helper.LdapHelper; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.dbc.DBC; import javax.naming.NamingException; import javax.naming.directory.Attributes; @@ -13,12 +14,11 @@ import java.io.FileReader; import java.util.*; import java.util.concurrent.ScheduledExecutorService; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import li.strolch.privilege.helper.LdapHelper; -import li.strolch.privilege.policy.PrivilegePolicy; -import li.strolch.utils.dbc.DBC; +import static java.lang.String.join; +import static java.util.stream.Collectors.toSet; +import static li.strolch.privilege.base.PrivilegeConstants.*; +import static li.strolch.utils.helper.StringHelper.isEmpty; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { @@ -147,17 +147,25 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { return relevantLdapGroups; } + @Override + protected Set mapToStrolchGroups(String username, Set ldapGroups) { + return mapLdapGroupToStrolch(ldapGroups, GROUPS); + } + @Override protected Set mapToStrolchRoles(String username, Set ldapGroups) { + return mapLdapGroupToStrolch(ldapGroups, ROLES); + } - Set strolchRoles = new HashSet<>(); - + private Set mapLdapGroupToStrolch(Set ldapGroups, String type) { + Set mappedValues = new HashSet<>(); for (String relevantLdapGroup : ldapGroups) { JsonObject mappingJ = this.ldapGroupConfigs.get(relevantLdapGroup).getAsJsonObject(); - mappingJ.get(ROLES).getAsJsonArray().forEach(e -> strolchRoles.add(e.getAsString())); + if (mappingJ.has(type)) + mappingJ.get(type).getAsJsonArray().forEach(e -> mappedValues.add(e.getAsString())); } - return strolchRoles; + return mappedValues; } @Override diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java index d8318620a..529bdea35 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java @@ -15,17 +15,17 @@ */ package li.strolch.privilege.handler; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.Group; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.policy.PrivilegePolicy; import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.util.List; +import java.util.Map; /** *

@@ -36,7 +36,7 @@ import javax.xml.stream.XMLStreamException; *

* The {@link PersistenceHandler} also serves the special {@link PrivilegePolicy} objects. These policies are special * objects which implement an algorithm to define if an action is allowed on a {@link Restrictable} by a {@link Role} - * and {@link IPrivilege} + * and {@link Privilege} *

* * @author Robert von Burg @@ -50,6 +50,13 @@ public interface PersistenceHandler { */ List getAllUsers(); + /** + * Returns all currently known {@link Group}s + * + * @return all currently known {@link Group}s + */ + List getAllGroups(); + /** * Returns all currently known {@link Role}s * @@ -60,18 +67,25 @@ public interface PersistenceHandler { /** * Returns a {@link User} object from the underlying database * - * @param username - * the name/id of the {@link User} object to return + * @param username the name/id of the {@link User} object to return * * @return the {@link User} object, or null if it was not found */ User getUser(String username); + /** + * Returns a {@link Group} object from the underlying database + * + * @param groupName the name/id of the {@link Group} object to return + * + * @return the {@link Group} object, or null if it was not found + */ + Group getGroup(String groupName); + /** * Returns a {@link Role} object from the underlying database * - * @param roleName - * the name/id of the {@link Role} object to return + * @param roleName the name/id of the {@link Role} object to return * * @return the {@link Role} object, or null if it was not found */ @@ -80,18 +94,25 @@ public interface PersistenceHandler { /** * Removes a {@link User} with the given name and returns the removed object if it existed * - * @param username - * the name of the {@link User} to remove + * @param username the name of the {@link User} to remove * * @return the {@link User} removed, or null if it did not exist */ User removeUser(String username); + /** + * Removes a {@link Group} with the given name and returns the removed object if it existed + * + * @param groupName the name of the {@link Group} to remove + * + * @return the {@link Group} removed, or null if it did not exist + */ + Group removeGroup(String groupName); + /** * Removes a {@link Role} with the given name and returns the removed object if it existed * - * @param roleName - * the name of the {@link Role} to remove + * @param roleName the name of the {@link Role} to remove * * @return the {@link Role} removed, or null if it did not exist */ @@ -100,35 +121,45 @@ public interface PersistenceHandler { /** * Adds a {@link User} object to the underlying database * - * @param user - * the {@link User} object to add + * @param user the {@link User} object to add */ void addUser(User user); /** * Replaces the existing {@link User} object in the underlying database * - * @param user - * the {@link User} object to add + * @param user the {@link User} object to add */ void replaceUser(User user); /** * Adds a {@link Role} object to the underlying database * - * @param role - * the {@link User} object to add + * @param role the {@link Role} object to add */ void addRole(Role role); /** * Replaces the {@link Role} object in the underlying database * - * @param role - * the {@link User} object to add + * @param role the {@link User} object to add */ void replaceRole(Role role); + /** + * Adds a {@link Group} object to the underlying database + * + * @param group the {@link Group} object to add + */ + void addGroup(Group group); + + /** + * Replaces the {@link Group} object in the underlying database + * + * @param group the {@link Group} object to add + */ + void replaceGroup(Group group); + /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * @@ -147,8 +178,7 @@ public interface PersistenceHandler { * Initialize the concrete {@link PersistenceHandler}. The passed parameter map contains any configuration the * concrete {@link PersistenceHandler} might need * - * @param parameterMap - * a map containing configuration properties + * @param parameterMap a map containing configuration properties */ void initialize(Map parameterMap); diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeContextBuilder.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeContextBuilder.java new file mode 100644 index 000000000..e91871837 --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeContextBuilder.java @@ -0,0 +1,204 @@ +package li.strolch.privilege.handler; + +import li.strolch.privilege.base.PrivilegeConflictResolution; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.base.PrivilegeModelException; +import li.strolch.privilege.model.*; +import li.strolch.privilege.model.internal.Group; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.dbc.DBC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.ZonedDateTime; +import java.util.*; + +import static java.text.MessageFormat.format; +import static java.util.stream.Collectors.toCollection; +import static li.strolch.privilege.handler.DefaultPrivilegeHandler.streamAllRolesForUser; + +public class PrivilegeContextBuilder { + private static final Logger logger = LoggerFactory.getLogger(PrivilegeContextBuilder.class); + + private final Map> policyMap; + private final DefaultPrivilegeHandler privilegeHandler; + private final PrivilegeConflictResolution conflictResolution; + private final PersistenceHandler persistenceHandler; + + private Set userGroups; + private Set userRoles; + private Map userProperties; + + public PrivilegeContextBuilder(DefaultPrivilegeHandler privilegeHandler) { + this.privilegeHandler = privilegeHandler; + this.persistenceHandler = privilegeHandler.persistenceHandler; + this.policyMap = privilegeHandler.policyMap; + this.conflictResolution = privilegeHandler.privilegeConflictResolution; + } + + public PrivilegeContext buildPrivilegeContext(Usage usage, User user, String source, ZonedDateTime loginTime, + boolean keepAlive) { + String authToken = this.privilegeHandler.getEncryptionHandler().nextToken(); + String sessionId = UUID.randomUUID().toString(); + return buildPrivilegeContext(usage, user, authToken, sessionId, source, loginTime, keepAlive); + } + + public PrivilegeContext buildPrivilegeContext(Usage usage, User user, String authToken, String sessionId, + String source, ZonedDateTime loginTime, boolean keepAlive) { + DBC.PRE.assertNotEmpty("source must not be empty!", source); + + keepAlive = keepAlive && this.privilegeHandler.allowSessionRefresh; + + prepare(user); + + Map privileges = new HashMap<>(); + Map policies = new HashMap<>(); + + // cache the privileges and policies for this user by role + addPrivilegesForRoles(this.userRoles, user.getUsername(), privileges, policies); + + UserRep userRep = user.asUserRep(); + userRep.setRoles(this.userRoles); + userRep.setGroups(this.userGroups); + userRep.setProperties(this.userProperties); + userRep.readOnly(); + + Certificate certificate = new Certificate(usage, sessionId, user.getUsername(), user.getFirstname(), + user.getLastname(), user.getUserState(), authToken, source, loginTime, keepAlive, user.getLocale(), + this.userGroups, this.userRoles, this.userProperties); + + return new PrivilegeContext(userRep, certificate, privileges, policies); + } + + private void prepare(User user) { + this.userGroups = user.getGroups().stream().sorted().collect(toCollection(TreeSet::new)); + this.userRoles = streamAllRolesForUser(this.persistenceHandler, user).sorted() + .collect(toCollection(TreeSet::new)); + this.userProperties = new HashMap<>(user.getProperties()); + + // copy properties from groups to user properties + for (String groupName : this.userGroups) { + Group group = this.persistenceHandler.getGroup(groupName); + if (group == null) { + logger.error("Group " + groupName + " does not exist!"); + continue; + } + Map groupProperties = group.getProperties(); + for (String key : groupProperties.keySet()) { + String replaced = this.userProperties.put(key, groupProperties.get(key)); + if (replaced != null) + logger.error("Duplicate property {} for user {} from group {}", key, user.getUsername(), groupName); + } + } + } + + private void addPrivilegesForRoles(Set roles, String userName, Map privileges, + Map policies) { + + for (String roleName : roles) { + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) { + logger.error("Role " + roleName + " does not exist for user " + userName); + } else { + addPrivilegesForRole(userName, role, privileges, policies); + } + } + } + + private void addPrivilegesForRole(String userName, Role role, Map privileges, + Map policies) { + + for (Privilege privilege : role.privilegeMap().values()) { + String privilegeName = privilege.name(); + + if (!privileges.containsKey(privilegeName)) { + privileges.put(privilegeName, privilege); + } else { + handleDuplicatePrivilege(userName, role, privileges, privilege, privilegeName); + } + + // cache the policy for the privilege + addPolicyForPrivilege(policies, privilege, privilegeName); + } + } + + private void addPolicyForPrivilege(Map policies, Privilege privilege, + String privilegeName) { + String policyName = privilege.getPolicy(); + if (policies.containsKey(policyName)) + return; + + PrivilegePolicy policy = getPolicy(policyName); + if (policy == null) { + logger.error(format("The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); + } else { + policies.put(policyName, policy); + } + } + + private void handleDuplicatePrivilege(String userName, Role role, Map privileges, + Privilege additionalPrivilege, String privilegeName) { + + // for strict, we have to throw an exception + if (this.conflictResolution.isStrict()) + throw new PrivilegeModelException( + format("User " + userName + " has conflicts for privilege {0} with role {1}", privilegeName, + role.name())); + + // merge privileges + Privilege knownPrivilege = privileges.get(privilegeName); + boolean allAllowed = knownPrivilege.isAllAllowed() || additionalPrivilege.isAllAllowed(); + Set allowList; + Set denyList; + if (allAllowed) { + allowList = Set.of(); + denyList = Set.of(); + } else { + allowList = new HashSet<>(knownPrivilege.getAllowList()); + allowList.addAll(additionalPrivilege.getAllowList()); + denyList = new HashSet<>(knownPrivilege.getDenyList()); + denyList.addAll(additionalPrivilege.getDenyList()); + } + + String policy = knownPrivilege.getPolicy(); + privileges.put(privilegeName, new Privilege(knownPrivilege.getName(), policy, allAllowed, denyList, allowList)); + } + + /** + *

+ * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} + * is not stored in a database. The privilege name is a class name and is then used to instantiate a new + * {@link PrivilegePolicy} object + *

+ * + * @param policyName the class name of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException if the {@link PrivilegePolicy} object for the given policy name could not be + * instantiated + */ + private PrivilegePolicy getPolicy(String policyName) { + + // get the policies class + Class policyClazz = this.policyMap.get(policyName); + if (policyClazz == null) { + return null; + } + + // instantiate the policy + PrivilegePolicy policy; + try { + + policy = policyClazz.getConstructor().newInstance(); + } catch (Exception e) { + String msg = "The class for the policy with the name {0} does not exist!{1}"; + msg = format(msg, policyName, policyName); + throw new PrivilegeModelException(msg, e); + } + + return policy; + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java new file mode 100644 index 000000000..2ea31ff21 --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java @@ -0,0 +1,1130 @@ +package li.strolch.privilege.handler; + +import li.strolch.privilege.base.PrivilegeConflictResolution; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.base.PrivilegeModelException; +import li.strolch.privilege.model.*; +import li.strolch.privilege.model.internal.PasswordCrypt; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.model.internal.UserHistory; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.collections.Tuple; +import li.strolch.utils.helper.StringHelper; + +import java.text.MessageFormat; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.text.MessageFormat.format; + +public class PrivilegeCrudHandler { + + private final DefaultPrivilegeHandler privilegeHandler; + private final PersistenceHandler persistenceHandler; + private final Map> policyMap; + private final PrivilegeConflictResolution privilegeConflictResolution; + + public PrivilegeCrudHandler(DefaultPrivilegeHandler privilegeHandler, Map> policyMap, + PrivilegeConflictResolution privilegeConflictResolution) { + this.privilegeHandler = privilegeHandler; + this.persistenceHandler = privilegeHandler.persistenceHandler; + this.policyMap = policyMap; + this.privilegeConflictResolution = privilegeConflictResolution; + } + + public RoleRep getRole(Certificate certificate, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE); + + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) + return null; + + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE, new Tuple(null, role))); + + return role.asRoleRep(); + } + + public UserRep getUser(Certificate certificate, String username) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); + + User user = this.persistenceHandler.getUser(username); + if (user == null) + return null; + + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_GET_USER, new Tuple(null, user))); + return user.asUserRep(); + } + + public Map getPolicyDefs(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ACTION, + DefaultPrivilegeHandler.PRIVILEGE_ACTION_GET_POLICIES)); + + Map policyDef = new HashMap<>(this.policyMap.size()); + for (Map.Entry> entry : this.policyMap.entrySet()) { + policyDef.put(entry.getKey(), entry.getValue().getName()); + } + return policyDef; + } + + public List getRoles(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE); + + Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); + + // validate access to each role + rolesStream = rolesStream.filter(role -> prvCtx.hasPrivilege( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE, new Tuple(null, role)))); + + return rolesStream.map(Role::asRoleRep).collect(Collectors.toList()); + } + + public List getUsers(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); + + Stream usersStream = this.persistenceHandler.getAllUsers().stream(); + + // validate access to each user + usersStream = usersStream.filter(user -> prvCtx.hasPrivilege( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_GET_USER, new Tuple(null, user)))); + + return usersStream.map(User::asUserRep).collect(Collectors.toList()); + } + + public List queryUsers(Certificate certificate, UserRep selectorRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); + + String selUserId = selectorRep.getUserId(); + String selUsername = selectorRep.getUsername(); + String selFirstName = selectorRep.getFirstname(); + String selLastName = selectorRep.getLastname(); + UserState selUserState = selectorRep.getUserState(); + Locale selLocale = selectorRep.getLocale(); + Set selGroups = selectorRep.getGroups(); + Set selRoles = selectorRep.getRoles(); + Map selPropertyMap = selectorRep.getProperties(); + + List result = new ArrayList<>(); + List allUsers = this.persistenceHandler.getAllUsers(); + for (User user : allUsers) { + + if (!prvCtx.hasPrivilege( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_GET_USER, new Tuple(null, user)))) + continue; + + // selections + boolean userIdSelected; + boolean usernameSelected; + boolean firstNameSelected; + boolean lastNameSelected; + boolean userStateSelected; + boolean localeSelected; + boolean roleSelected; + boolean groupSelected; + boolean propertySelected; + + // userId + userIdSelected = StringHelper.isEmpty(selUserId) || selUserId.equals(user.getUserId()); + + // username + usernameSelected = StringHelper.isEmpty(selUsername) || selUsername.equals(user.getUsername()); + + // firstname + firstNameSelected = StringHelper.isEmpty(selFirstName) || selFirstName.equals(user.getFirstname()); + + // lastname + lastNameSelected = StringHelper.isEmpty(selLastName) || selLastName.equals(user.getLastname()); + + // user state + userStateSelected = selUserState == null || selUserState.equals(user.getUserState()); + + // locale + localeSelected = selLocale == null || selLocale.equals(user.getLocale()); + + // groups + groupSelected = isSelectedByGroup(selGroups, user.getGroups()); + + // roles + roleSelected = isSelectedByRole(selRoles, user.getRoles()); + + // properties + propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); + + boolean selected = userIdSelected && usernameSelected && firstNameSelected && lastNameSelected && + userStateSelected && localeSelected && groupSelected && roleSelected && propertySelected; + + if (selected) + result.add(user.asUserRep()); + } + + result.sort(Comparator.comparing(UserRep::getUsername)); + return result; + } + + /** + * Checks if the given properties contains values which are contained in the selectionMap. If the selectionMap is + * null or empty, then true is returned. If a key/value pair from the selectionMap is not in the properties, then + * false is returned + * + * @param selectionMap the map defining the expected properties + * @param properties the properties which must be a sub set of selectionMap to have this method return true + * + * @return If the selectionMap is null or empty, then true is returned. If a key/value pair from the selectionMap is + * not in the properties, then false is returned + */ + boolean isSelectedByProperty(Map selectionMap, Map properties) { + + if (selectionMap == null) + return true; + + if (selectionMap.isEmpty() && properties.isEmpty()) + return true; + + for (String selKey : selectionMap.keySet()) { + + String value = properties.get(selKey); + if (value == null || !value.equals(selectionMap.get(selKey))) + return false; + } + + return true; + } + + /** + * Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is null or + * empty, then true is returned, otherwise false + * + * @param selectionRoles the required roles + * @param roles the roles to check if they contain the selectionRoles + * + * @return Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is + * null or empty, then true is returned, otherwise false + */ + boolean isSelectedByRole(Set selectionRoles, Set roles) { + return selectionRoles == null || roles.containsAll(selectionRoles); + } + + /** + * Checks if the given groups contains the given selectionGroups, if this is the case, or selectionGroups is null or + * empty, then true is returned, otherwise false + * + * @param selectionGroups the required groups + * @param groups the groups to check if they contain the selectionGroups + * + * @return Checks if the given groups contains the given selectionGroups, if this is the case, or selectionGroups is + * null or empty, then true is returned, otherwise false + */ + boolean isSelectedByGroup(Set selectionGroups, Set groups) { + return selectionGroups == null || groups.containsAll(selectionGroups); + } + + public UserRep addUser(Certificate certificate, UserRep userRepParam, char[] password) { + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER); + + // make sure userId is not set + if (StringHelper.isNotEmpty(userRepParam.getUserId())) + throw new PrivilegeModelException("UserId can not be set when adding a new user!"); + + UserRep userRep = userRepParam.getCopy(); + + // set userId + userRep.setUserId(StringHelper.getUniqueId()); + + // first validate user + userRep.validate(); + + validateGroupsExist(userRep); + validateRolesExist(userRep); + + // validate user does not already exist + if (this.persistenceHandler.getUser(userRep.getUsername()) != null) { + String msg = "User {0} can not be added as it already exists!"; + throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); + } + + UserHistory history = UserHistory.EMPTY; + PasswordCrypt passwordCrypt = null; + if (password != null) { + + // validate password meets basic requirements + privilegeHandler.validatePassword(certificate.getLocale(), password); + + // get new salt for user + byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + + // hash password + passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + + history = history.withLastPasswordChange(ZonedDateTime.now()); + } + + // create new user + User newUser = createUser(userRep, history, passwordCrypt, false); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may create such a user + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER, new Tuple(null, newUser))); + + // delegate to persistence handler + this.persistenceHandler.addUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Created new user " + newUser.getUsername()); + + return newUser.asUserRep(); + + } finally { + clearPassword(password); + } + } + + public void addOrUpdateUsers(Certificate certificate, List userReps) throws PrivilegeException { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER); + + List toCreate = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + + for (UserRep e : userReps) { + UserRep userRep = e.getCopy(); + + User user; + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + + if (existingUser == null) { + + // add user + + // make sure userId is not set + if (StringHelper.isNotEmpty(userRep.getUserId())) + throw new PrivilegeModelException("UserId can not be set when adding a new user!"); + + // set userId + userRep.setUserId(StringHelper.getUniqueId()); + + // first validate user + userRep.validate(); + + validateGroupsExist(userRep); + validateRolesExist(userRep); + + // create new user + user = createUser(userRep, UserHistory.EMPTY, null, false); + + // detect privilege conflicts + assertNoPrivilegeConflict(user); + + // validate this user may create such a user + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER, new Tuple(null, user))); + + toCreate.add(user); + DefaultPrivilegeHandler.logger.info("Creating new user " + user.getUsername()); + + } else { + + // update user + + if (userRep.getUserId() == null) + userRep.setUserId(existingUser.getUserId()); + + user = createUser(userRep, existingUser.getHistory(), existingUser.getPasswordCrypt(), + existingUser.isPasswordChangeRequested()); + + // detect privilege conflicts + assertNoPrivilegeConflict(user); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, + new Tuple(existingUser, user))); + + toUpdate.add(user); + DefaultPrivilegeHandler.logger.info("Updating existing user " + user.getUsername()); + } + } + + // delegate to persistence handler + toCreate.forEach(this.persistenceHandler::addUser); + toUpdate.forEach(this.persistenceHandler::replaceUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Created " + toCreate.size() + " users"); + DefaultPrivilegeHandler.logger.info("Updated " + toUpdate.size() + " users"); + } + + public UserRep replaceUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER); + + // first validate user + userRep.validate(); + + validateGroupsExist(userRep); + validateRolesExist(userRep); + + // validate user exists + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { + String msg = "User {0} can not be replaced as it does not exist!"; + throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); + } + + // validate same userId + if (!existingUser.getUserId().equals(userRep.getUserId())) { + String msg = "UserId of existing user {0} does not match userRep {1}"; + msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); + throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); + } + + UserHistory history = existingUser.getHistory(); + PasswordCrypt passwordCrypt = null; + if (password != null) { + + // validate password meets basic requirements + privilegeHandler.validatePassword(certificate.getLocale(), password); + + // get new salt for user + byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + + // hash password + passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + + history = history.withLastPasswordChange(ZonedDateTime.now()); + } + + User newUser = createUser(userRep, history, passwordCrypt, existingUser.isPasswordChangeRequested()); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, + new Tuple(existingUser, newUser))); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Replaced user " + newUser.getUsername()); + + return newUser.asUserRep(); + + } finally { + clearPassword(password); + } + } + + private void assertNoPrivilegeConflict(User user) { + if (this.privilegeConflictResolution.isStrict()) { + Map privilegeNames = new HashMap<>(); + List conflicts = detectPrivilegeConflicts(privilegeNames, user); + if (!conflicts.isEmpty()) { + String msg = String.join("\n", conflicts); + throw new PrivilegeModelException(msg); + } + } + } + + private void assertNoPrivilegeConflict(Role role) { + if (!this.privilegeConflictResolution.isStrict()) + return; + + Map privilegeNames = new HashMap<>(); + for (String privilegeName : role.getPrivilegeNames()) { + privilegeNames.put(privilegeName, role.getName()); + } + + List conflicts = new ArrayList<>(); + List users = this.persistenceHandler.getAllUsers(); + for (User user : users) { + if (user.hasRole(role.getName())) + conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + } + + if (!conflicts.isEmpty()) { + String msg = String.join("\n", conflicts); + throw new PrivilegeModelException(msg); + } + } + + public List detectPrivilegeConflicts(Map privilegeNames, User user) { + List conflicts = new ArrayList<>(); + + Set userRoles = user.getRoles(); + for (String roleName : userRoles) { + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) + throw new IllegalStateException("Role " + roleName + " does not exist for user " + user.getUsername()); + for (String privilegeName : role.getPrivilegeNames()) { + String roleOrigin = privilegeNames.get(privilegeName); + if (roleOrigin == null) { + privilegeNames.put(privilegeName, roleName); + } else if (!roleOrigin.equals(roleName)) { + String msg = "User {0} has conflicts for privilege {1} on roles {2} and {3}"; + msg = format(msg, user.getUsername(), privilegeName, roleOrigin, roleName); + conflicts.add(msg); + } + } + } + + return conflicts; + } + + private void validateGroupsExist(UserRep userRep) { + for (String group : userRep.getGroups()) { + if (this.persistenceHandler.getGroup(group) == null) { + String msg = "Can not add user {0} as group {1} does not exist!"; + msg = MessageFormat.format(msg, userRep.getUsername(), group); + throw new PrivilegeModelException(msg); + } + } + } + + private void validateRolesExist(UserRep userRep) { + for (String role : userRep.getRoles()) { + if (this.persistenceHandler.getRole(role) == null) { + String msg = "Can not add user {0} as role {1} does not exist!"; + msg = MessageFormat.format(msg, userRep.getUsername(), role); + throw new PrivilegeModelException(msg); + } + } + } + + User createUser(UserRep userRep, UserHistory history, PasswordCrypt passwordCrypt, + boolean passwordChangeRequested) { + String userId = userRep.getUserId(); + String userName = userRep.getUsername(); + String firstName = userRep.getFirstname(); + String lastName = userRep.getLastname(); + UserState state = userRep.getUserState(); + Set groups = userRep.getGroups(); + Set roles = userRep.getRoles(); + Locale locale = userRep.getLocale(); + Map properties = userRep.getProperties(); + return new User(userId, userName, passwordCrypt, firstName, lastName, state, groups, roles, locale, properties, + passwordChangeRequested, history); + } + + public UserRep updateUser(Certificate certificate, UserRep userRep) throws PrivilegeException { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER); + + // get existing user + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); + + // if nothing to do, then stop + if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname()) && + userRep.getLocale() == null && (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { + throw new PrivilegeModelException( + MessageFormat.format("All updateable fields are empty for update of user {0}", + userRep.getUsername())); + } + + String userId = existingUser.getUserId(); + String username = existingUser.getUsername(); + PasswordCrypt passwordCrypt = existingUser.getPasswordCrypt(); + String firstName = existingUser.getFirstname(); + String lastName = existingUser.getLastname(); + UserState userState = existingUser.getUserState(); + Set groups = existingUser.getGroups(); + Set roles = existingUser.getRoles(); + Locale locale = existingUser.getLocale(); + Map propertyMap = existingUser.getProperties(); + + // get updated fields + if (StringHelper.isNotEmpty(userRep.getFirstname())) + firstName = userRep.getFirstname(); + if (StringHelper.isNotEmpty(userRep.getLastname())) + lastName = userRep.getLastname(); + if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) + propertyMap = userRep.getProperties(); + + if (userRep.getLocale() != null) + locale = userRep.getLocale(); + if (userRep.getUserState() != null) + userState = userRep.getUserState(); + if (userRep.getRoles() != null && !userRep.getRoles().equals(roles)) + roles = userRep.getRoles(); + + // create new user + User newUser = new User(userId, username, passwordCrypt, firstName, lastName, userState, groups, roles, locale, + propertyMap, existingUser.isPasswordChangeRequested(), existingUser.getHistory()); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, + new Tuple(existingUser, newUser))); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Updated user " + newUser.getUsername()); + + // update any existing sessions for this user + this.privilegeHandler.updateExistingSessionsForUser(newUser); + + return newUser.asUserRep(); + } + + public UserRep removeUser(Certificate certificate, String username) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_USER); + + // validate user exists + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + String msg = "Can not remove User {0} because user does not exist!"; + throw new PrivilegeModelException(MessageFormat.format(msg, username)); + } + + // validate this user may remove this user + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); + + // delegate user removal to persistence handler + privilegeHandler.invalidSessionsFor(existingUser); + this.persistenceHandler.removeUser(username); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Removed user " + username); + + return existingUser.asUserRep(); + } + + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER); + + // get user + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + // validate that this user may add this role to this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER, + new Tuple(existingUser, roleName))); + + // check that user not already has role + Set currentRoles = existingUser.getRoles(); + if (currentRoles.contains(roleName)) { + String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); + throw new PrivilegeModelException(msg); + } + + // validate that the role exists + if (this.persistenceHandler.getRole(roleName) == null) { + String msg = MessageFormat.format("Role {0} does not exist!", roleName); + throw new PrivilegeModelException(msg); + } + + // create new user + Set newRoles = new HashSet<>(currentRoles); + newRoles.add(roleName); + + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getGroups(), newRoles, existingUser.getLocale(), existingUser.getProperties(), + existingUser.isPasswordChangeRequested(), existingUser.getHistory()); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Added role " + roleName + " to " + newUser.getUsername()); + + // update any existing sessions for this user + this.privilegeHandler.updateExistingSessionsForUser(newUser); + + return newUser.asUserRep(); + } + + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + // validate that this user may remove this role from this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER, + new Tuple(existingUser, roleName))); + + // ignore if user does not have role + Set currentRoles = existingUser.getRoles(); + if (!currentRoles.contains(roleName)) { + String msg = MessageFormat.format("User {0} does not have role {1}", existingUser.getUsername(), roleName); + throw new PrivilegeModelException(msg); + } + + // create new user + Set newRoles = new HashSet<>(currentRoles); + newRoles.remove(roleName); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getGroups(), newRoles, existingUser.getLocale(), existingUser.getProperties(), + existingUser.isPasswordChangeRequested(), existingUser.getHistory()); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Removed role " + roleName + " from " + newUser.getUsername()); + + // update any existing sessions for this user + this.privilegeHandler.updateExistingSessionsForUser(newUser); + + return newUser.asUserRep(); + } + + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_LOCALE); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getGroups(), existingUser.getRoles(), locale, existingUser.getProperties(), + existingUser.isPasswordChangeRequested(), existingUser.getHistory()); + + // if the user is not setting their own locale, then make sure this user may set this user's locale + if (!certificate.getUsername().equals(username)) { + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, + new Tuple(existingUser, newUser))); + } + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Set locale to " + locale + " for " + newUser.getUsername()); + + return newUser.asUserRep(); + } + + public void requirePasswordChange(Certificate certificate, String username) throws PrivilegeException { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REQUIRE_PASSWORD_CHANGE); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + if (existingUser.getUserState().isRemote()) + throw new PrivilegeModelException( + MessageFormat.format("User {0} is remote and can not set password!", username)); + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getGroups(), existingUser.getRoles(), existingUser.getLocale(), + existingUser.getProperties(), true, existingUser.getHistory()); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info( + "Requiring user " + newUser.getUsername() + " to change their password on next login."); + } + + public void setUserPassword(Certificate certificate, String username, char[] password) { + + // we don't want the user to worry about whitespace + username = StringHelper.trimOrEmpty(username); + + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + UserHistory history = existingUser.getHistory(); + + PasswordCrypt passwordCrypt = null; + if (password != null) { + + // validate password meets basic requirements + privilegeHandler.validatePassword(certificate.getLocale(), password); + + // get new salt for user + byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + + // hash password + passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + + history = history.withLastPasswordChange(ZonedDateTime.now()); + } + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordCrypt, + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getGroups(), existingUser.getRoles(), existingUser.getLocale(), + existingUser.getProperties(), false, history); + + if (!certificate.getUsername().equals(username)) { + // check that the user may change their own password + Tuple value = new Tuple(existingUser, newUser); + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD, value)); + } + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + if (certificate.getUsage() == Usage.SET_PASSWORD) + privilegeHandler.invalidate(certificate); + + if (password == null) + DefaultPrivilegeHandler.logger.info("Cleared password for " + newUser.getUsername()); + else + DefaultPrivilegeHandler.logger.info("Updated password for " + newUser.getUsername()); + + } finally { + clearPassword(password); + } + } + + public UserRep setUserState(Certificate certificate, String username, UserState state) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_STATE); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) + throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), + existingUser.getFirstname(), existingUser.getLastname(), state, existingUser.getGroups(), + existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(), + existingUser.isPasswordChangeRequested(), existingUser.getHistory()); + + // validate that this user may modify this user's state + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_STATE, + new Tuple(existingUser, newUser))); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Set state of user " + newUser.getUsername() + " to " + state); + + return newUser.asUserRep(); + } + + public RoleRep addRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does not exist + if (this.persistenceHandler.getRole(roleRep.getName()) != null) { + String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName()); + throw new PrivilegeModelException(msg); + } + + // create new role from RoleRep + Role newRole = Role.of(roleRep); + + // validate that this user may add this new role + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE, new Tuple(null, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.addRole(newRole); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Added new role " + newRole.getName()); + + return newRole.asRoleRep(); + } + + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does exist + Role existingRole = this.persistenceHandler.getRole(roleRep.getName()); + if (existingRole == null) { + String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName()); + throw new PrivilegeModelException(msg); + } + + // create new role from RoleRep + Role newRole = Role.of(roleRep); + + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE, + new Tuple(existingRole, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.replaceRole(newRole); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Replaced role " + newRole.getName()); + + // update any existing certificates with new role + this.privilegeHandler.updateExistingSessionsWithNewRole(newRole); + + return newRole.asRoleRep(); + } + + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE); + + // validate no user is using this role + Set roles = new HashSet<>(Collections.singletonList(roleName)); + UserRep selector = new UserRep(null, null, null, null, null, null, roles, null, null, null); + List usersWithRole = privilegeHandler.queryUsers(certificate, selector); + if (!usersWithRole.isEmpty()) { + String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); + String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; + msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS); + throw new PrivilegeModelException(msg); + } + + // validate role exists + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = "Can not remove Role {0} because role does not exist!"; + throw new PrivilegeModelException(MessageFormat.format(msg, roleName)); + } + + // validate that this user may remove this role + prvCtx.validateAction( + new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE, new Tuple(null, existingRole))); + + // delegate role removal to persistence handler + this.persistenceHandler.removeRole(roleName); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Removed role " + roleName); + + return existingRole.asRoleRep(); + } + + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); + + // validate PrivilegeRep + privilegeRep.validate(); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = MessageFormat.format("Role {0} does not exist!", roleName); + throw new PrivilegeModelException(msg); + } + + // validate that policy exists if needed + String policy = privilegeRep.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + String msg = "Policy {0} for Privilege {1} does not exist"; + msg = MessageFormat.format(msg, policy, privilegeRep.getName()); + throw new PrivilegeModelException(msg); + } + + // create new role with the additional privilege + Privilege newPrivilege = Privilege.of(privilegeRep); + + // copy existing privileges + Set existingPrivilegeNames = existingRole.getPrivilegeNames(); + Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); + for (String name : existingPrivilegeNames) { + Privilege privilege = existingRole.getPrivilege(name); + privilegeMap.put(name, privilege); + } + + // add new one + privilegeMap.put(newPrivilege.getName(), newPrivilege); + + // create new role + Role newRole = new Role(existingRole.getName(), privilegeMap); + + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE, + new Tuple(existingRole, newRole))); + + // delegate role replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Added/replaced privilege " + privilegeRep.getName() + " to " + roleName); + + // update any existing certificates with new role + this.privilegeHandler.updateExistingSessionsWithNewRole(newRole); + + return newRole.asRoleRep(); + } + + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + throw new PrivilegeModelException(MessageFormat.format("Role {0} does not exist!", roleName)); + } + + // ignore if role does not have privilege + if (!existingRole.hasPrivilege(privilegeName)) { + String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); + throw new PrivilegeModelException(msg); + } + + // create new set of privileges with out the to removed privilege + Set privilegeNames = existingRole.getPrivilegeNames(); + Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); + for (String name : privilegeNames) { + Privilege privilege = existingRole.getPrivilege(name); + if (!privilege.getName().equals(privilegeName)) + newPrivileges.put(privilege.getName(), privilege); + } + + // create new role + Role newRole = new Role(existingRole.getName(), newPrivileges); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE, + new Tuple(existingRole, newRole))); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Removed privilege " + privilegeName + " from " + roleName); + + // update any existing certificates with new role + this.privilegeHandler.updateExistingSessionsWithNewRole(newRole); + + return newRole.asRoleRep(); + } + + /** + * Validates that the policies which are not null on the privileges of the role exist + * + * @param role the role for which the policies are to be checked + */ + public void validatePolicies(Role role) { + for (String privilegeName : role.getPrivilegeNames()) { + Privilege privilege = role.getPrivilege(privilegeName); + String policy = privilege.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + String msg = "Policy {0} for Privilege {1} does not exist on role {2}"; + msg = format(msg, policy, privilege.getName(), role); + throw new PrivilegeModelException(msg); + } + } + } + + /** + * Passwords should not be kept as strings, as string are immutable, this method thus clears the char array so that + * the password is not in memory anymore + * + * @param password the char array containing the passwort which is to be set to zeroes + */ + static void clearPassword(char[] password) { + if (password != null) + Arrays.fill(password, (char) 0); + } +} \ No newline at end of file diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java index 777b7343b..7c639d60a 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java @@ -502,7 +502,7 @@ public interface PrivilegeHandler { * @param roleName * the roleName of the {@link Role} to which the privilege should be added * @param privilegeRep - * the representation of the {@link IPrivilege} which should be added or replaced on the {@link Role} + * the representation of the {@link Privilege} which should be added or replaced on the {@link Role} * * @throws AccessDeniedException * if the user for this certificate may not perform the action diff --git a/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java deleted file mode 100644 index 4e335db7a..000000000 --- a/privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java +++ /dev/null @@ -1,127 +0,0 @@ -package li.strolch.privilege.handler; - -import static li.strolch.privilege.base.PrivilegeConstants.*; -import static li.strolch.utils.helper.StringHelper.isEmpty; - -import javax.naming.NamingException; -import javax.naming.directory.Attributes; -import java.util.*; -import java.util.concurrent.ScheduledExecutorService; - -import li.strolch.privilege.helper.LdapHelper; -import li.strolch.privilege.policy.PrivilegePolicy; -import li.strolch.utils.dbc.DBC; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SimpleLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { - - protected static final Logger logger = LoggerFactory.getLogger(SimpleLdapPrivilegeHandler.class); - - private Locale defaultLocale; - private String adminUsers; - private Map> rolesForLdapGroups; - private String organisation; - private String location; - private String realm; - - @Override - public void initialize(ScheduledExecutorService executorService, Map parameterMap, - EncryptionHandler encryptionHandler, PasswordStrengthHandler passwordStrengthHandler, - PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, Map> policyMap) { - - super.initialize(executorService, parameterMap, encryptionHandler, passwordStrengthHandler, persistenceHandler, - userChallengeHandler, ssoHandler, policyMap); - - this.organisation = parameterMap.getOrDefault(ORGANISATION, ""); - this.location = parameterMap.getOrDefault(LOCATION, ""); - this.realm = parameterMap.getOrDefault(REALM, ""); - - this.defaultLocale = parameterMap.containsKey("defaultLocale") ? - Locale.forLanguageTag(parameterMap.get("defaultLocale")) : Locale.getDefault(); - - this.adminUsers = parameterMap.get("adminUsers"); - this.rolesForLdapGroups = getLdapGroupToRolesMappingFromConfig(parameterMap); - } - - @Override - protected String getFirstName(String username, Attributes attrs) throws NamingException { - String value = getLdapString(attrs, "givenName"); - return isEmpty(value) ? username : value; - } - - @Override - protected String getLastName(String username, Attributes attrs) throws NamingException { - String value = getLdapString(attrs, "sn"); - return isEmpty(value) ? username : value; - } - - @Override - protected Map buildProperties(String username, Attributes attrs, Set ldapGroups, - Set strolchRoles) { - - Map properties = new HashMap<>(); - properties.put(ORGANISATION, this.organisation); - properties.put(LOCATION, this.location); - properties.put(REALM, this.realm); - return properties; - } - - @Override - protected Locale getLocale(Attributes attrs) { - return this.defaultLocale; - } - - @Override - protected Set getLdapGroups(String username, Attributes attrs) throws NamingException { - Set ldapGroups = LdapHelper.getLdapGroups(attrs); - logger.info("User " + username + " has LDAP Groups: "); - ldapGroups.forEach(s -> logger.info("- " + s)); - return ldapGroups; - } - - @Override - protected Set mapToStrolchRoles(String username, Set ldapGroups) { - Set strolchRoles = new HashSet<>(); - for (String ldapRole : ldapGroups) { - Set foundStrolchRoles = this.rolesForLdapGroups.get(ldapRole); - if (foundStrolchRoles != null) - strolchRoles.addAll(foundStrolchRoles); - } - - // see if this is an admin user - if (this.adminUsers.contains(username)) - strolchRoles = this.rolesForLdapGroups.get("admin"); - - return strolchRoles; - } - - private Map> getLdapGroupToRolesMappingFromConfig(Map params) { - - String rolesForLdapGroups = params.get("rolesForLdapGroups"); - DBC.PRE.assertNotEmpty("No roles mapping for ldap directory groups defined (param: rolesForLdapGroups)", - rolesForLdapGroups); - - // rolesForLdapGroups = admin=StrolchAdmin,UserPrivileges;user=UserPrivileges - - String[] ldapGroupRoles = rolesForLdapGroups.split(";"); - - Map> result = new HashMap<>(); - for (String ldapGroupRole : ldapGroupRoles) { - ldapGroupRole = ldapGroupRole.trim(); - - String[] splitGroupRoles = ldapGroupRole.split("="); - String ldapGroupName = splitGroupRoles[0]; - String[] strolchRoles = splitGroupRoles[1].split(","); - Set roleNames = new HashSet<>(); - for (String strolchRole : strolchRoles) { - roleNames.add(strolchRole.trim()); - } - - result.put(ldapGroupName, roleNames); - } - - return result; - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index c159ef6bf..ce4610010 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -17,12 +17,10 @@ package li.strolch.privilege.handler; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.internal.Group; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; -import li.strolch.privilege.xml.PrivilegeRolesSaxReader; -import li.strolch.privilege.xml.PrivilegeRolesSaxWriter; -import li.strolch.privilege.xml.PrivilegeUsersSaxReader; -import li.strolch.privilege.xml.PrivilegeUsersSaxWriter; +import li.strolch.privilege.xml.*; import li.strolch.utils.helper.XmlHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,20 +52,24 @@ public class XmlPersistenceHandler implements PersistenceHandler { protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); private final Map userMap; + private final Map groupMap; private final Map roleMap; private boolean userMapDirty; + private boolean groupMapDirty; private boolean roleMapDirty; private Map parameterMap; private File usersPath; + private File groupsPath; private File rolesPath; private boolean caseInsensitiveUsername; public XmlPersistenceHandler() { this.roleMap = new ConcurrentHashMap<>(); + this.groupMap = new ConcurrentHashMap<>(); this.userMap = new ConcurrentHashMap<>(); } @@ -83,6 +85,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } + @Override + public List getAllGroups() { + synchronized (this.groupMap) { + return new LinkedList<>(this.groupMap.values()); + } + } + @Override public List getAllRoles() { synchronized (this.roleMap) { @@ -95,6 +104,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { return this.userMap.get(this.caseInsensitiveUsername ? username.toLowerCase() : username); } + @Override + public Group getGroup(String groupName) { + return this.groupMap.get(groupName); + } + @Override public Role getRole(String roleName) { return this.roleMap.get(roleName); @@ -107,6 +121,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { return user; } + @Override + public Group removeGroup(String groupName) { + Group group = this.groupMap.remove(groupName); + this.groupMapDirty = group != null; + return group; + } + @Override public Role removeRole(String roleName) { Role role = this.roleMap.remove(roleName); @@ -133,6 +154,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = true; } + @Override + public void addGroup(Group group) { + if (this.groupMap.containsKey(group.name())) + throw new IllegalStateException(format("The group {0} already exists!", group.name())); + this.groupMap.put(group.name(), group); + this.groupMapDirty = true; + } + + @Override + public void replaceGroup(Group group) { + if (!this.groupMap.containsKey(group.name())) + throw new IllegalStateException( + format("The group {0} can not be replaced as it does not exist!", group.name())); + this.groupMap.put(group.name(), group); + this.groupMapDirty = true; + } + @Override public void addRole(Role role) { if (this.roleMap.containsKey(role.getName())) @@ -153,9 +191,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { /** * Initializes this {@link XmlPersistenceHandler} by reading the following parameters: *
    - *
  • {@link XmlConstants#XML_PARAM_BASE_PATH}
  • - *
  • {@link XmlConstants#XML_PARAM_USERS_FILE}
  • - *
  • {@link XmlConstants#XML_PARAM_ROLES_FILE}
  • + *
  • {@link XmlConstants#PARAM_BASE_PATH}
  • + *
  • {@link XmlConstants#PARAM_USERS_FILE}
  • + *
  • {@link XmlConstants#PARAM_GROUPS_FILE}
  • + *
  • {@link XmlConstants#PARAM_ROLES_FILE}
  • *
*/ @Override @@ -163,22 +202,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.parameterMap = Map.copyOf(paramsMap); // get and validate base bath - String basePath = this.parameterMap.get(XML_PARAM_BASE_PATH); + String basePath = this.parameterMap.get(PARAM_BASE_PATH); File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { String msg = "[{0}] Defined parameter {1} does not point to a valid path at {2}"; - msg = format(msg, PersistenceHandler.class.getName(), XML_PARAM_BASE_PATH, basePathF.getAbsolutePath()); + msg = format(msg, PersistenceHandler.class.getName(), PARAM_BASE_PATH, basePathF.getAbsolutePath()); throw new PrivilegeException(msg); } - // get users file path - File usersPath = getFile(basePath, XML_PARAM_USERS_FILE, XML_PARAM_USERS_FILE_DEF); - - // get roles file path - File rolesPath = getFile(basePath, XML_PARAM_ROLES_FILE, XML_PARAM_ROLES_FILE_DEF); + File usersPath = getFile(basePath, PARAM_USERS_FILE, PARAM_USERS_FILE_DEF, true); + File groupsPath = getFile(basePath, PARAM_GROUPS_FILE, PARAM_GROUPS_FILE_DEF, false); + File rolesPath = getFile(basePath, PARAM_ROLES_FILE, PARAM_ROLES_FILE_DEF, true); // save path to model this.usersPath = usersPath; + this.groupsPath = groupsPath; this.rolesPath = rolesPath; this.caseInsensitiveUsername = !this.parameterMap.containsKey(PARAM_CASE_INSENSITIVE_USERNAME) || @@ -188,7 +226,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info("Privilege Data loaded."); } - private File getFile(String basePath, String param, String defaultValue) { + private File getFile(String basePath, String param, String defaultValue, boolean required) { String fileName = this.parameterMap.get(param); if (isEmpty(fileName)) { fileName = defaultValue; @@ -199,7 +237,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { String path = basePath + "/" + fileName; File file = new File(path); - if (!file.exists()) { + if (required && !file.exists()) { String msg = "[{0}] Defined parameter {1} is invalid as file does not exist at path {2}"; msg = format(msg, PersistenceHandler.class.getName(), param, file.getAbsolutePath()); throw new PrivilegeException(msg); @@ -221,6 +259,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { PrivilegeUsersSaxReader usersXmlHandler = new PrivilegeUsersSaxReader(this.caseInsensitiveUsername); XmlHelper.parseDocument(this.usersPath, usersXmlHandler); + PrivilegeGroupsSaxReader groupsXmlHandler = new PrivilegeGroupsSaxReader(); + if (this.groupsPath.exists()) + XmlHelper.parseDocument(this.groupsPath, groupsXmlHandler); + PrivilegeRolesSaxReader rolesXmlHandler = new PrivilegeRolesSaxReader(); XmlHelper.parseDocument(this.rolesPath, rolesXmlHandler); @@ -230,6 +272,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.roleMap.putAll(rolesXmlHandler.getRoles()); } + // GROUPS + synchronized (this.groupMap) { + this.groupMap.clear(); + this.groupMap.putAll(groupsXmlHandler.getGroups()); + } + // USERS synchronized (this.userMap) { this.userMap.clear(); @@ -237,21 +285,40 @@ public class XmlPersistenceHandler implements PersistenceHandler { } this.userMapDirty = false; + this.groupMapDirty = false; this.roleMapDirty = false; logger.info(format("Read {0} Users", this.userMap.size())); + logger.info(format("Read {0} Groups", this.groupMap.size())); logger.info(format("Read {0} Roles", this.roleMap.size())); - // validate referenced roles exist + // validate referenced elements exist for (User user : this.userMap.values()) { for (String roleName : user.getRoles()) { - // validate that role exists if (getRole(roleName) == null) { logger.error( format("Role {0} does not exist referenced by user {1}", roleName, user.getUsername())); } } + + for (String groupName : user.getGroups()) { + // validate that group exists + if (getGroup(groupName) == null) { + logger.error( + format("Group {0} does not exist referenced by user {1}", groupName, user.getUsername())); + } + } + } + + // validate referenced roles exist on groups + for (Group group : this.groupMap.values()) { + for (String roleName : group.roles()) { + // validate that role exists + if (getRole(roleName) == null) { + logger.error(format("Role {0} does not exist referenced by group {1}", roleName, group.name())); + } + } } return true; @@ -267,20 +334,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write users file if (this.userMapDirty) { - // delegate writing - PrivilegeUsersSaxWriter modelWriter = new PrivilegeUsersSaxWriter(getAllUsers(), this.usersPath); - modelWriter.write(); - + new PrivilegeUsersSaxWriter(getAllUsers(), this.usersPath).write(); this.userMapDirty = false; saved = true; } + // write groups file + if (this.groupMapDirty) { + new PrivilegeGroupsSaxWriter(getAllGroups(), this.groupsPath).write(); + this.groupMapDirty = false; + saved = true; + } + // write roles file if (this.roleMapDirty) { - // delegate writing - PrivilegeRolesSaxWriter modelWriter = new PrivilegeRolesSaxWriter(getAllRoles(), this.rolesPath); - modelWriter.write(); - + new PrivilegeRolesSaxWriter(getAllRoles(), this.rolesPath).write(); this.roleMapDirty = false; saved = true; } diff --git a/privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java b/privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java index e5305f905..d58abb160 100644 --- a/privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java +++ b/privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java @@ -26,7 +26,6 @@ import java.util.HashMap; import java.util.Map; import static li.strolch.privilege.base.PrivilegeConstants.*; -import static li.strolch.privilege.model.internal.PasswordCrypt.buildPasswordString; /** *

@@ -111,9 +110,9 @@ public class PasswordCreator { } Map parameterMap = new HashMap<>(); - parameterMap.put(XmlConstants.XML_PARAM_HASH_ALGORITHM, hashAlgorithm); - parameterMap.put(XmlConstants.XML_PARAM_HASH_ITERATIONS, String.valueOf(iterations)); - parameterMap.put(XmlConstants.XML_PARAM_HASH_KEY_LENGTH, String.valueOf(keyLength)); + parameterMap.put(XmlConstants.PARAM_HASH_ALGORITHM, hashAlgorithm); + parameterMap.put(XmlConstants.PARAM_HASH_ITERATIONS, String.valueOf(iterations)); + parameterMap.put(XmlConstants.PARAM_HASH_KEY_LENGTH, String.valueOf(keyLength)); DefaultEncryptionHandler encryptionHandler = new DefaultEncryptionHandler(); encryptionHandler.initialize(parameterMap); @@ -136,15 +135,15 @@ public class PasswordCreator { byte[] salt = saltS.getBytes(); PasswordCrypt passwordCrypt = encryptionHandler.hashPassword(password, salt); - String passwordHashS = StringHelper.toHexString(passwordCrypt.getPassword()); + String passwordHashS = StringHelper.toHexString(passwordCrypt.password()); System.out.println("Hash is: " + passwordHashS); System.out.println("Salt is: " + saltS); System.out.println(); System.out.println( - XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordHashS + "\" " + XmlConstants.XML_ATTR_SALT + - "=\"" + saltS + "\""); - System.out.println(XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordCrypt.buildPasswordString() + "\""); + XmlConstants.ATTR_PASSWORD + "=\"" + passwordHashS + "\" " + XmlConstants.ATTR_SALT + "=\"" + + saltS + "\""); + System.out.println(XmlConstants.ATTR_PASSWORD + "=\"" + passwordCrypt.buildPasswordString() + "\""); System.out.println(); } } diff --git a/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java b/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java index 71d6a620c..596f40df3 100644 --- a/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java +++ b/privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java @@ -23,308 +23,69 @@ package li.strolch.privilege.helper; @SuppressWarnings("nls") public class XmlConstants { - /** - * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : - */ - public static final String XML_ROOT_PRIVILEGE = "Privilege"; - - /** - * XML_CONTAINER = "Container" : - */ - public static final String XML_CONTAINER = "Container"; - - /** - * XML_POLICIES = "Policies" : - */ - public static final String XML_POLICIES = "Policies"; - - /** - * XML_PRIVILEGES = "Privileges" : - */ - public static final String XML_PRIVILEGES = "Privileges"; - - /** - * XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles" : - */ - public static final String XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; - - /** - * XML_ROOT_CERTIFICATES = "Certificates" : - */ - public static final String XML_ROOT_CERTIFICATES = "Certificates"; - - /** - * XML_HANDLER_USER_CHALLENGE = "UserChallengeHandler" : - */ - public static final String XML_HANDLER_USER_CHALLENGE = "UserChallengeHandler"; - - /** - * XML_HANDLER_PERSISTENCE = "PersistenceHandler" : - */ - public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; - - /** - * XML_HANDLER_ENCRYPTION = "EncryptionHandler" : - */ - public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; - - /** - * XML_HANDLER_ENCRYPTION = "PasswordStrengthHandler" : - */ - public static final String XML_HANDLER_PASSWORD_STRENGTH = "PasswordStrengthHandler"; - - /** - * XML_HANDLER_ENCRYPTION = "SsoHandler" : - */ - public static final String XML_HANDLER_SSO = "SsoHandler"; - - /** - * XML_HANDLER_PRIVILEGE = "PrivilegeHandler" : - */ - public static final String XML_HANDLER_PRIVILEGE = "PrivilegeHandler"; - - /** - * XML_ROLES = "Roles" : - */ - public static final String XML_ROLES = "Roles"; - - /** - * XML_ROLE = "Role" : - */ - public static final String XML_ROLE = "Role"; - - /** - * XML_USERS = "Users" : - */ - public static final String XML_USERS = "Users"; - - /** - * XML_CERTIFICATE = "Certificate" : - */ - public static final String XML_CERTIFICATE = "Certificate"; - - /** - * XML_SESSION_DATA = "SessionData" : - */ - public static final String XML_SESSION_DATA = "SessionData"; - - /** - * XML_USER = "User" - */ - public static final String XML_USER = "User"; - - /** - * XML_USER = "User" - */ - public static final String XML_HISTORY = "History"; - - /** - * XML_USER = "User" - */ - public static final String XML_FIRST_LOGIN = "FirstLogin"; - - /** - * XML_USER = "User" - */ - public static final String XML_LAST_LOGIN = "LastLogin"; - - /** - * XML_USER = "User" - */ - public static final String XML_LAST_PASSWORD_CHANGE = "LastPasswordChange"; - - /** - * XML_USER = "User" - */ - public static final String XML_PASSWORD_CHANGE_REQUESTED = "PasswordChangeRequested"; - - /** - * XML_PRIVILEGE = "Privilege" : - */ - public static final String XML_PRIVILEGE = "Privilege"; - - /** - * XML_POLICY = "Policy" : - */ - public static final String XML_POLICY = "Policy"; - - /** - * XML_PARAMETERS = "Parameters" : - */ - public static final String XML_PARAMETERS = "Parameters"; - - /** - * XML_PARAMETER = "Parameter" : - */ - public static final String XML_PARAMETER = "Parameter"; - - /** - * XML_PROPERTIES = "Properties" : - */ - public static final String XML_PROPERTIES = "Properties"; - - /** - * XML_PROPERTY = "Property" : - */ - public static final String XML_PROPERTY = "Property"; - - /** - * XML_ALL_ALLOWED = "AllAllowed" : - */ - public static final String XML_ALL_ALLOWED = "AllAllowed"; - - /** - * XML_DENY = "Deny" : - */ - public static final String XML_DENY = "Deny"; - - /** - * XML_ALLOW = "Allow" : - */ - public static final String XML_ALLOW = "Allow"; - - /** - * XML_FIRSTNAME = "Firstname" : - */ - public static final String XML_FIRSTNAME = "Firstname"; - - /** - * XML_LASTNAME = "Lastname" : - */ - public static final String XML_LASTNAME = "Lastname"; - - /** - * XML_STATE = "State" : - */ - public static final String XML_STATE = "State"; - - /** - * XML_LOCALE = "Locale" : - */ - public static final String XML_LOCALE = "Locale"; - - /** - * XML_ATTR_CLASS = "class" : - */ - public static final String XML_ATTR_CLASS = "class"; - - /** - * XML_ATTR_LOGIN_TIME = "loginTime" : - */ - public static final String XML_ATTR_LOGIN_TIME = "loginTime"; - - /** - * XML_ATTR_KEEP_ALIVE = "keepAlive" : - */ - public static final String XML_ATTR_KEEP_ALIVE = "keepAlive"; - - /** - * XML_ATTR_LAST_ACCESS = "lastAccess" : - */ - public static final String XML_ATTR_LAST_ACCESS = "lastAccess"; - - /** - * XML_ATTR_NAME = "name" : - */ - public static final String XML_ATTR_NAME = "name"; - - /** - * XML_ATTR_VALUE = "value" : - */ - public static final String XML_ATTR_VALUE = "value"; - - /** - * XML_ATTR_POLICY = "policy" : - */ - public static final String XML_ATTR_POLICY = "policy"; - - /** - * XML_ATTR_USER_ID = "userId" : - */ - public static final String XML_ATTR_USER_ID = "userId"; - - /** - * XML_ATTR_SESSION_ID = "sessionId" : - */ - public static final String XML_ATTR_SESSION_ID = "sessionId"; - - /** - * XML_ATTR_SESSION_ID = "usage" : - */ - public static final String XML_ATTR_USAGE = "usage"; - - /** - * XML_ATTR_USERNAME = "username" : - */ - public static final String XML_ATTR_USERNAME = "username"; - - /** - * XML_ATTR_AUTH_TOKEN = "authToken" : - */ - public static final String XML_ATTR_AUTH_TOKEN = "authToken"; - - /** - * XML_ATTR_SOURCE = "source" : - */ - public static final String XML_ATTR_SOURCE = "source"; - - /** - * XML_ATTR_LOCALE = "locale" : - */ - public static final String XML_ATTR_LOCALE = "locale"; - - /** - * XML_ATTR_PASSWORD = "password" : - */ - public static final String XML_ATTR_PASSWORD = "password"; - - /** - * XML_ATTR_SALT = "salt" : - */ - public static final String XML_ATTR_SALT = "salt"; - - /** - * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : - */ - public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; - - /** - * XML_PARAM_HASH_ALGORITHM_NON_SALT = "hashAlgorithmNonSalt" : - */ - public static final String XML_PARAM_HASH_ALGORITHM_NON_SALT = "hashAlgorithmNonSalt"; - - /** - * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : - */ - public static final String XML_PARAM_HASH_ITERATIONS = "hashIterations"; - - /** - * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : - */ - public static final String XML_PARAM_HASH_KEY_LENGTH = "hashKeyLength"; - - /** - * XML_PARAM_USERS_FILE = "usersXmlFile" : - */ - public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; - - /** - * XML_PARAM_USERS_FILE_DEF = "PrivilegeUsers.xml" : - */ - public static final String XML_PARAM_USERS_FILE_DEF = "PrivilegeUsers.xml"; - - /** - * XML_PARAM_ROLES_FILE = "rolesXmlFile" : - */ - public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; - - /** - * XML_PARAM_ROLES_FILE_DEF = "PrivilegeRoles.xml" : - */ - public static final String XML_PARAM_ROLES_FILE_DEF = "PrivilegeRoles.xml"; - - /** - * XML_PARAM_BASE_PATH = "basePath" : - */ - public static final String XML_PARAM_BASE_PATH = "basePath"; + public static final String ROOT_PRIVILEGE = "Privilege"; + public static final String CONTAINER = "Container"; + public static final String POLICIES = "Policies"; + public static final String PRIVILEGES = "Privileges"; + public static final String ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; + public static final String ROOT_CERTIFICATES = "Certificates"; + public static final String HANDLER_USER_CHALLENGE = "UserChallengeHandler"; + public static final String HANDLER_PERSISTENCE = "PersistenceHandler"; + public static final String HANDLER_ENCRYPTION = "EncryptionHandler"; + public static final String HANDLER_PASSWORD_STRENGTH = "PasswordStrengthHandler"; + public static final String HANDLER_SSO = "SsoHandler"; + public static final String HANDLER_PRIVILEGE = "PrivilegeHandler"; + public static final String ROLES = "Roles"; + public static final String ROLE = "Role"; + public static final String USERS = "Users"; + public static final String GROUPS = "Groups"; + public static final String GROUP = "Group"; + public static final String CERTIFICATE = "Certificate"; + public static final String SESSION_DATA = "SessionData"; + public static final String USER = "User"; + public static final String HISTORY = "History"; + public static final String FIRST_LOGIN = "FirstLogin"; + public static final String LAST_LOGIN = "LastLogin"; + public static final String LAST_PASSWORD_CHANGE = "LastPasswordChange"; + public static final String PASSWORD_CHANGE_REQUESTED = "PasswordChangeRequested"; + public static final String PRIVILEGE = "Privilege"; + public static final String POLICY = "Policy"; + public static final String PARAMETERS = "Parameters"; + public static final String PARAMETER = "Parameter"; + public static final String PROPERTIES = "Properties"; + public static final String PROPERTY = "Property"; + public static final String ALL_ALLOWED = "AllAllowed"; + public static final String DENY = "Deny"; + public static final String ALLOW = "Allow"; + public static final String FIRSTNAME = "Firstname"; + public static final String LASTNAME = "Lastname"; + public static final String STATE = "State"; + public static final String LOCALE = "Locale"; + public static final String ATTR_CLASS = "class"; + public static final String ATTR_LOGIN_TIME = "loginTime"; + public static final String ATTR_KEEP_ALIVE = "keepAlive"; + public static final String ATTR_LAST_ACCESS = "lastAccess"; + public static final String ATTR_NAME = "name"; + public static final String ATTR_VALUE = "value"; + public static final String ATTR_POLICY = "policy"; + public static final String ATTR_USER_ID = "userId"; + public static final String ATTR_SESSION_ID = "sessionId"; + public static final String ATTR_USAGE = "usage"; + public static final String ATTR_USERNAME = "username"; + public static final String ATTR_AUTH_TOKEN = "authToken"; + public static final String ATTR_SOURCE = "source"; + public static final String ATTR_LOCALE = "locale"; + public static final String ATTR_PASSWORD = "password"; + public static final String ATTR_SALT = "salt"; + public static final String PARAM_HASH_ALGORITHM = "hashAlgorithm"; + public static final String PARAM_HASH_ALGORITHM_NON_SALT = "hashAlgorithmNonSalt"; + public static final String PARAM_HASH_ITERATIONS = "hashIterations"; + public static final String PARAM_HASH_KEY_LENGTH = "hashKeyLength"; + public static final String PARAM_USERS_FILE = "usersXmlFile"; + public static final String PARAM_USERS_FILE_DEF = "PrivilegeUsers.xml"; + public static final String PARAM_GROUPS_FILE = "groupsXmlFile"; + public static final String PARAM_GROUPS_FILE_DEF = "PrivilegeGroups.xml"; + public static final String PARAM_ROLES_FILE = "rolesXmlFile"; + public static final String PARAM_ROLES_FILE_DEF = "PrivilegeRoles.xml"; + public static final String PARAM_BASE_PATH = "basePath"; } diff --git a/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java b/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java index d2425f7cf..40d4b0330 100644 --- a/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java +++ b/privilege/src/main/java/li/strolch/privilege/helper/XmlHelper.java @@ -18,7 +18,7 @@ public class XmlHelper { public static void writeStringMapElement(XMLStreamWriter xmlWriter, Map parameterMap, String elementName, String valueElementName) throws XMLStreamException { - writeStringMapElement(xmlWriter, parameterMap, elementName, valueElementName, XML_ATTR_VALUE); + writeStringMapElement(xmlWriter, parameterMap, elementName, valueElementName, ATTR_VALUE); } public static void writeStringMapElement(XMLStreamWriter xmlWriter, Map parameterMap, @@ -32,7 +32,7 @@ public class XmlHelper { propertyKeys.sort(null); for (String propertyKey : propertyKeys) { xmlWriter.writeEmptyElement(valueElementName); - xmlWriter.writeAttribute(XML_ATTR_NAME, propertyKey); + xmlWriter.writeAttribute(ATTR_NAME, propertyKey); xmlWriter.writeAttribute(valueAttrName, parameterMap.get(propertyKey)); } diff --git a/privilege/src/main/java/li/strolch/privilege/model/Certificate.java b/privilege/src/main/java/li/strolch/privilege/model/Certificate.java index 7500d1f29..36762b7dd 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/Certificate.java +++ b/privilege/src/main/java/li/strolch/privilege/model/Certificate.java @@ -15,20 +15,18 @@ */ package li.strolch.privilege.model; -import static li.strolch.privilege.base.PrivilegeConstants.*; +import li.strolch.privilege.base.PrivilegeConstants; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.dbc.DBC; -import java.io.Serializable; import java.time.ZonedDateTime; -import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.Set; -import li.strolch.privilege.base.PrivilegeConstants; -import li.strolch.privilege.base.PrivilegeException; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.internal.User; -import li.strolch.utils.helper.StringHelper; +import static li.strolch.privilege.base.PrivilegeConstants.*; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; /** * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the @@ -37,7 +35,7 @@ import li.strolch.utils.helper.StringHelper; * * @author Robert von Burg */ -public final class Certificate implements Serializable { +public final class Certificate { private final Usage usage; private final String sessionId; @@ -50,6 +48,7 @@ public final class Certificate implements Serializable { private final ZonedDateTime loginTime; private final boolean keepAlive; + private final Set userGroups; private final Set userRoles; private final Map propertyMap; @@ -64,49 +63,28 @@ public final class Certificate implements Serializable { * by the {@link PrivilegeHandler} *

* - * @param usage - * the usage allowed for this certificate - * @param sessionId - * the users session id - * @param username - * the users login name - * @param firstName - * the users first name - * @param lastName - * the users last name - * @param authToken - * the authentication token defining the users unique session and is a private field of this certificate. - * @param locale - * the users {@link Locale} - * @param userRoles - * the user's roles - * @param propertyMap - * a {@link Map} containing string value pairs of properties for the logged in user. These properties can be - * edited and can be used for the user to change settings of this session + * @param usage the usage allowed for this certificate + * @param sessionId the users session id + * @param username the users login name + * @param firstName the users first name + * @param lastName the users last name + * @param authToken the authentication token defining the users unique session and is a private field of this + * certificate. + * @param locale the users {@link Locale} + * @param userRoles the user's roles + * @param propertyMap a {@link Map} containing string value pairs of properties for the logged in user. These + * properties can be edited and can be used for the user to change settings of this session */ public Certificate(Usage usage, String sessionId, String username, String firstName, String lastName, UserState userState, String authToken, String source, ZonedDateTime loginTime, boolean keepAlive, - Locale locale, Set userRoles, Map propertyMap) { + Locale locale, Set userGroups, Set userRoles, Map propertyMap) { - // validate arguments are not null - if (StringHelper.isEmpty(sessionId)) { - throw new PrivilegeException("sessionId is null!"); - } - if (StringHelper.isEmpty(username)) { - throw new PrivilegeException("username is null!"); - } - if (StringHelper.isEmpty(authToken)) { - throw new PrivilegeException("authToken is null!"); - } - if (userState == null) { - throw new PrivilegeException("userState is null!"); - } - if (usage == null) { - throw new PrivilegeException("usage is null!"); - } - if (source == null) { - throw new PrivilegeException("source is null!"); - } + DBC.PRE.assertNotEmpty("sessionId must not be empty", sessionId); + DBC.PRE.assertNotEmpty("username must not be empty", username); + DBC.PRE.assertNotEmpty("authToken must not be empty", authToken); + DBC.PRE.assertNotNull("userState must not be empty", userState); + DBC.PRE.assertNotNull("usage must not be empty", usage); + DBC.PRE.assertNotNull("source must not be null", source); this.usage = usage; this.sessionId = sessionId; @@ -126,11 +104,12 @@ public final class Certificate implements Serializable { this.locale = locale; if (propertyMap == null) - this.propertyMap = Collections.emptyMap(); + this.propertyMap = Map.of(); else - this.propertyMap = Collections.unmodifiableMap(propertyMap); + this.propertyMap = Map.copyOf(propertyMap); - this.userRoles = Collections.unmodifiableSet(userRoles); + this.userGroups = Set.copyOf(userGroups); + this.userRoles = Set.copyOf(userRoles); this.lastAccess = ZonedDateTime.now(); } @@ -150,15 +129,29 @@ public final class Certificate implements Serializable { return this.usage; } + public Set getUserGroups() { + return this.userGroups; + } + public Set getUserRoles() { return this.userRoles; } + /** + * Returns true if the user of this certificate has the given group + * + * @param group the group to check for + * + * @return true if the user of this certificate has the given group + */ + public boolean hasGroup(String group) { + return this.userGroups.contains(group); + } + /** * Returns true if the user of this certificate has the given role * - * @param role - * the role to check for + * @param role the role to check for * * @return true if the user of this certificate has the given role */ @@ -178,8 +171,7 @@ public final class Certificate implements Serializable { /** * Returns the property with the given key * - * @param key - * the key for which the property is to be returned + * @param key the key for which the property is to be returned * * @return the value of the property with the given key, or null if it does not exist */ @@ -283,12 +275,12 @@ public final class Certificate implements Serializable { builder.append(", username="); builder.append(this.username); - if (StringHelper.isNotEmpty(this.firstname)) { + if (isNotEmpty(this.firstname)) { builder.append(", firstname="); builder.append(this.firstname); } - if (StringHelper.isNotEmpty(this.lastname)) { + if (isNotEmpty(this.lastname)) { builder.append(", lastname="); builder.append(this.lastname); } diff --git a/privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java b/privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java deleted file mode 100644 index 633953995..000000000 --- a/privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.model; - -import java.util.Set; - -import li.strolch.privilege.model.internal.Role; -import li.strolch.privilege.policy.PrivilegePolicy; - -/** - *

- * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it - * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a {@link - * PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow lists - * configured which is used to evaluate if privilege is granted to a {@link Restrictable} - *

- * - * @author Robert von Burg - */ -public interface IPrivilege { - - /** - * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients - */ - PrivilegeRep asPrivilegeRep(); - - /** - * @return the name - */ - String getName(); - - /** - * @return the policy - */ - String getPolicy(); - - /** - * @return the allAllowed - */ - boolean isAllAllowed(); - - /** - * @return the allowList - */ - Set getAllowList(); - - /** - * @return the denyList - */ - Set getDenyList(); - - /** - * @return true if there are values in the allow list - */ - boolean hasAllowed(); - - /** - * @return if the value is in the allow list - */ - boolean isAllowed(String value); - - /** - * @return true if there are values in the deny list - */ - boolean hasDenied(); - - /** - * @return true if the value is in the deny list - */ - boolean isDenied(String value); - -} \ No newline at end of file diff --git a/privilege/src/main/java/li/strolch/privilege/model/Privilege.java b/privilege/src/main/java/li/strolch/privilege/model/Privilege.java new file mode 100644 index 000000000..d9835461d --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/model/Privilege.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.model; + +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.dbc.DBC; + +import java.util.HashSet; +import java.util.Set; + +/** + *

+ * {@link Privilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it which + * defines the privileges a logged in user with that role has. If the {@link Privilege} has a {@link PrivilegePolicy} + * defined, then that policy will be used for finer granularity and with the deny and allow lists configured which is + * used to evaluate if privilege is granted to a {@link Restrictable} + *

+ * + * @param name the name of this privilege, which is unique to all privileges known in the + * {@link PrivilegeHandler} + * @param policy the {@link PrivilegePolicy} configured to evaluate if the privilege is granted. If null, then + * privilege is granted + * @param allAllowed a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * {@link Restrictable} in which case the deny and allow lists are ignored and can be null + * @param denyList a list of deny rules for this {@link Privilege}, can be null if all allowed + * @param allowList a list of allow rules for this {@link Privilege}, can be null if all allowed + * + * @author Robert von Burg + */ +public record Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + + public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + DBC.PRE.assertNotEmpty("name must not be empty", name); + DBC.PRE.assertNotEmpty("policy must not be empty", policy); + DBC.PRE.assertNotNull("denyList must not be null", denyList); + DBC.PRE.assertNotNull("allowList must not be null", allowList); + + this.name = name; + this.allAllowed = allAllowed; + this.policy = policy; + this.denyList = Set.copyOf(denyList); + this.allowList = Set.copyOf(allowList); + } + + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + public PrivilegeRep asPrivilegeRep() { + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet<>(this.denyList), + new HashSet<>(this.allowList)); + } + + public String getName() { + return this.name; + } + + public String getPolicy() { + return this.policy; + } + + public boolean isAllAllowed() { + return this.allAllowed; + } + + public Set getAllowList() { + return this.allowList; + } + + public Set getDenyList() { + return this.denyList; + } + + public boolean hasAllowed() { + return !this.allowList.isEmpty(); + } + + public boolean isAllowed(String value) { + return this.allowList.contains(value); + } + + public boolean hasDenied() { + return !this.allowList.isEmpty(); + } + + public boolean isDenied(String value) { + return this.denyList.contains(value); + } + + @Override + public String toString() { + return "Privilege [name=" + this.name + ", policy=" + this.policy + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Privilege other = (Privilege) obj; + if (this.name == null) + return other.name == null; + return this.name.equals(other.name); + } + + /** + * Constructs a {@link Privilege} from the {@link PrivilegeRep} + * + * @param privilegeRep the {@link PrivilegeRep} from which to create the {@link Privilege} + */ + public static Privilege of(PrivilegeRep privilegeRep) { + return new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), + privilegeRep.getDenyList(), privilegeRep.getAllowList()); + } +} \ No newline at end of file diff --git a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java index b7b8999af..6ae46e6ee 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java +++ b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java @@ -15,14 +15,16 @@ */ package li.strolch.privilege.model; -import java.text.MessageFormat; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.dbc.DBC; + import java.util.Map; import java.util.Set; -import li.strolch.privilege.base.AccessDeniedException; -import li.strolch.privilege.base.PrivilegeException; -import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.policy.PrivilegePolicy; +import static java.text.MessageFormat.format; +import static li.strolch.privilege.i18n.PrivilegeMessages.getString; /** *

@@ -36,19 +38,15 @@ import li.strolch.privilege.policy.PrivilegePolicy; * * @author Robert von Burg */ -public class PrivilegeContext { +public record PrivilegeContext(UserRep userRep, Certificate certificate, Map privileges, + Map policies) { - // - // object state - // - - private final UserRep userRep; - private final Certificate certificate; - private final Map privileges; - private final Map policies; - - public PrivilegeContext(UserRep userRep, Certificate certificate, Map privileges, + public PrivilegeContext(UserRep userRep, Certificate certificate, Map privileges, Map policies) { + DBC.PRE.assertNotNull("userRep must not be null", userRep); + DBC.PRE.assertNotNull("certificate must not be null", certificate); + DBC.PRE.assertNotNull("privileges must not be null", privileges); + DBC.PRE.assertNotNull("policies must not be null", policies); this.userRep = userRep; this.certificate = certificate; this.privileges = Map.copyOf(privileges); @@ -85,35 +83,64 @@ public class PrivilegeContext { public void assertHasPrivilege(String privilegeName) throws AccessDeniedException { if (!this.privileges.containsKey(privilegeName)) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), - userRep.getUsername(), privilegeName); + String msg = format(getString("Privilege.noprivilege.user"), userRep.getUsername(), privilegeName); throw new AccessDeniedException(msg); } } + public boolean hasGroup(String groupName) { + return this.userRep.hasGroup(groupName); + } + public boolean hasRole(String roleName) { return this.userRep.hasRole(roleName); } - public void assertHasRole(String roleName) throws AccessDeniedException { - if (!this.userRep.hasRole(roleName)) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.role"), - userRep.getUsername(), roleName); + public void assertHasGroup(String groupName) throws AccessDeniedException { + if (!this.userRep.hasGroup(groupName)) { + String msg = format(getString("Privilege.noprivilege.group"), userRep.getUsername(), groupName); throw new AccessDeniedException(msg); } } + public void assertHasRole(String roleName) throws AccessDeniedException { + if (!this.userRep.hasRole(roleName)) { + String msg = format(getString("Privilege.noprivilege.role"), userRep.getUsername(), roleName); + throw new AccessDeniedException(msg); + } + } + + public void assertHasAnyGroup(String... groupNames) throws AccessDeniedException { + for (String groupName : groupNames) { + if (this.userRep.hasGroup(groupName)) + return; + } + + String msg = format(getString("Privilege.noprivilege.group"), userRep.getUsername(), + String.join(", ", groupNames)); + throw new AccessDeniedException(msg); + } + public void assertHasAnyRole(String... roleNames) throws AccessDeniedException { for (String roleName : roleNames) { if (this.userRep.hasRole(roleName)) return; } - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.role"), - userRep.getUsername(), String.join(", ", roleNames)); + String msg = format(getString("Privilege.noprivilege.role"), userRep.getUsername(), + String.join(", ", roleNames)); throw new AccessDeniedException(msg); } + public boolean hasAnyGroup(String... groupNames) throws AccessDeniedException { + for (String groupName : groupNames) { + if (this.userRep.hasGroup(groupName)) + return true; + } + + return false; + } + public boolean hasAnyRole(String... roleNames) throws AccessDeniedException { for (String roleName : roleNames) { if (this.userRep.hasRole(roleName)) @@ -123,7 +150,7 @@ public class PrivilegeContext { return false; } - public IPrivilege getPrivilege(String privilegeName) throws AccessDeniedException { + public Privilege getPrivilege(String privilegeName) throws AccessDeniedException { assertHasPrivilege(privilegeName); return this.privileges.get(privilegeName); } @@ -132,7 +159,7 @@ public class PrivilegeContext { PrivilegePolicy policy = this.policies.get(policyName); if (policy == null) { String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; - throw new PrivilegeException(MessageFormat.format(msg, policyName)); + throw new PrivilegeException(format(msg, policyName)); } return policy; } @@ -148,15 +175,12 @@ public class PrivilegeContext { * *

This method uses the {@link SimpleRestrictable} to verify access

* - * @param privilegeName - * the name of the privilege to verify - * @param privilegeValue - * the value + * @param privilegeName the name of the privilege to verify + * @param privilegeValue the value * - * @throws AccessDeniedException - * if the user does not have access - * @throws PrivilegeException - * if there is an internal error due to wrongly configured privileges or programming errors + * @throws AccessDeniedException if the user does not have access + * @throws PrivilegeException if there is an internal error due to wrongly configured privileges or programming + * errors */ public void validateAction(String privilegeName, String privilegeValue) throws PrivilegeException, AccessDeniedException { @@ -168,22 +192,20 @@ public class PrivilegeContext { * has the privilege, then this method returns with no exception and void, if the user does not have the privilege, * then a {@link AccessDeniedException} is thrown. * - * @param restrictable - * the {@link Restrictable} which the user wants to access + * @param restrictable the {@link Restrictable} which the user wants to access * - * @throws AccessDeniedException - * if the user does not have access - * @throws PrivilegeException - * if there is an internal error due to wrongly configured privileges or programming errors + * @throws AccessDeniedException if the user does not have access + * @throws PrivilegeException if there is an internal error due to wrongly configured privileges or programming + * errors */ public void validateAction(Restrictable restrictable) throws PrivilegeException, AccessDeniedException { // the privilege for the restrictable String privilegeName = restrictable.getPrivilegeName(); - IPrivilege privilege = this.privileges.get(privilegeName); + Privilege privilege = this.privileges.get(privilegeName); if (privilege == null) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), - getUsername(), privilegeName, restrictable.getClass().getName(), restrictable.getPrivilegeValue()); + String msg = format(getString("Privilege.accessdenied.noprivilege"), getUsername(), privilegeName, + restrictable.getClass().getName(), restrictable.getPrivilegeValue()); throw new AccessDeniedException(msg); } @@ -199,19 +221,18 @@ public class PrivilegeContext { * Validates if the user for this context has the privilege to access to the given {@link Restrictable}. Returning * true if the user has the privilege, and false if not * - * @param restrictable - * the {@link Restrictable} which the user wants to access + * @param restrictable the {@link Restrictable} which the user wants to access * * @return returns true if the user has the privilege, and false if not * - * @throws PrivilegeException - * if there is an internal error due to wrongly configured privileges or programming errors + * @throws PrivilegeException if there is an internal error due to wrongly configured privileges or programming + * errors */ public boolean hasPrivilege(Restrictable restrictable) throws PrivilegeException { // the privilege for the restrictable String privilegeName = restrictable.getPrivilegeName(); - IPrivilege privilege = this.privileges.get(privilegeName); + Privilege privilege = this.privileges.get(privilegeName); if (privilege == null) return false; @@ -229,15 +250,13 @@ public class PrivilegeContext { * *

This method uses the {@link SimpleRestrictable} to verify access

* - * @param privilegeName - * the name of the privilege to verify - * @param privilegeValue - * the value + * @param privilegeName the name of the privilege to verify + * @param privilegeValue the value * * @return returns true if the user has the privilege, and false if not * - * @throws PrivilegeException - * if there is an internal error due to wrongly configured privileges or programming errors + * @throws PrivilegeException if there is an internal error due to wrongly configured privileges or programming + * errors */ public boolean hasPrivilege(String privilegeName, String privilegeValue) throws PrivilegeException { return hasPrivilege(new SimpleRestrictable(privilegeName, privilegeValue)); diff --git a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java index bdff20a9f..8324d07c5 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java @@ -15,27 +15,26 @@ */ package li.strolch.privilege.model; -import static li.strolch.utils.helper.StringHelper.trimOrEmpty; - -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.policy.PrivilegePolicy; -import li.strolch.utils.helper.StringHelper; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import static li.strolch.utils.helper.StringHelper.isEmpty; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; /** - * To keep certain details of the {@link IPrivilege} itself hidden from remote clients and make sure instances are only + * To keep certain details of the {@link Privilege} itself hidden from remote clients and make sure instances are only * edited by users with the correct privilege, this representational version is allowed to be viewed by remote clients - * and simply wraps all public data from the {@link IPrivilege} + * and simply wraps all public data from the {@link Privilege} * * @author Robert von Burg */ -public class PrivilegeRep implements Serializable { +public class PrivilegeRep { private String name; private String policy; @@ -46,45 +45,34 @@ public class PrivilegeRep implements Serializable { /** * Default constructor * - * @param name - * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} - * @param policy - * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted - * @param allAllowed - * a boolean defining if a {@link Role} with this {@link IPrivilege} has unrestricted access to a {@link - * Restrictable} - * @param denyList - * a list of deny rules for this {@link IPrivilege} - * @param allowList - * a list of allow rules for this {@link IPrivilege} + * @param name the name of this privilege, which is unique to all privileges known in the + * {@link PrivilegeHandler} + * @param policy the {@link PrivilegePolicy} configured to evaluate if the privilege is granted + * @param allAllowed a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * {@link Restrictable} + * @param denyList a list of deny rules for this {@link Privilege} + * @param allowList a list of allow rules for this {@link Privilege} */ public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = trimOrEmpty(name); this.policy = trimOrEmpty(policy); this.allAllowed = allAllowed; - this.denyList = denyList; - this.allowList = allowList; + this.denyList = denyList == null ? Set.of() : Set.copyOf(denyList); + this.allowList = allowList == null ? Set.of() : Set.copyOf(allowList); } /** * Validates that all required fields are set */ public void validate() { - - if (StringHelper.isEmpty(this.name)) { + if (isEmpty(this.name)) throw new PrivilegeException("No name defined!"); - } - - if (StringHelper.isEmpty(this.policy)) { - throw new PrivilegeException("policy is null!"); - } - - if (this.denyList == null) { + if (isEmpty(this.policy)) + throw new PrivilegeException("No policy defined!"); + if (this.denyList == null) throw new PrivilegeException("denyList is null"); - } - if (this.allowList == null) { + if (this.allowList == null) throw new PrivilegeException("allowList is null"); - } } /** @@ -95,8 +83,7 @@ public class PrivilegeRep implements Serializable { } /** - * @param name - * the name to set + * @param name the name to set */ public void setName(String name) { this.name = trimOrEmpty(name); @@ -110,8 +97,7 @@ public class PrivilegeRep implements Serializable { } /** - * @param policy - * the policy to set + * @param policy the policy to set */ public void setPolicy(String policy) { this.policy = trimOrEmpty(policy); @@ -125,8 +111,7 @@ public class PrivilegeRep implements Serializable { } /** - * @param allAllowed - * the allAllowed to set + * @param allAllowed the allAllowed to set */ public void setAllAllowed(boolean allAllowed) { this.allAllowed = allAllowed; @@ -136,12 +121,11 @@ public class PrivilegeRep implements Serializable { * @return the denyList */ public Set getDenyList() { - return this.denyList == null ? new HashSet<>() : this.denyList; + return this.denyList; } /** - * @param denyList - * the denyList to set + * @param denyList the denyList to set */ public void setDenyList(Set denyList) { this.denyList = denyList.stream().map(String::trim).collect(Collectors.toSet()); @@ -151,12 +135,11 @@ public class PrivilegeRep implements Serializable { * @return the allowList */ public Set getAllowList() { - return this.allowList == null ? new HashSet<>() : this.allowList; + return this.allowList; } /** - * @param allowList - * the allowList to set + * @param allowList the allowList to set */ public void setAllowList(Set allowList) { this.allowList = allowList.stream().map(String::trim).collect(Collectors.toSet()); @@ -169,11 +152,8 @@ public class PrivilegeRep implements Serializable { */ @Override public String toString() { - return "PrivilegeRep [name=" + this.name + ", policy=" + this.policy + ", allAllowed=" + this.allAllowed - + ", denyList=" + (this.denyList == null ? "null" : this.denyList.size()) + ", allowList=" + ( - this.allowList == null ? - "null" : - this.allowList.size()) + "]"; + return "PrivilegeRep [name=" + this.name + ", policy=" + this.policy + ", allAllowed=" + this.allAllowed + + ", denyList=" + this.denyList.size() + ", allowList=" + this.allowList.size() + "]"; } @Override @@ -193,10 +173,9 @@ public class PrivilegeRep implements Serializable { if (getClass() != obj.getClass()) return false; PrivilegeRep other = (PrivilegeRep) obj; - if (this.name == null) { + if (this.name == null) return other.name == null; - } else - return this.name.equals(other.name); + return this.name.equals(other.name); } public T accept(PrivilegeElementVisitor visitor) { diff --git a/privilege/src/main/java/li/strolch/privilege/model/Restrictable.java b/privilege/src/main/java/li/strolch/privilege/model/Restrictable.java index 374737002..fa24569fa 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/Restrictable.java +++ b/privilege/src/main/java/li/strolch/privilege/model/Restrictable.java @@ -21,7 +21,7 @@ import li.strolch.privilege.policy.PrivilegePolicy; *

* Objects implementing this interface are used to grant/restrict privileges to them. A {@link PrivilegePolicy} * implements the logic on granting/restricting privileges for a {@link Restrictable} and the {@link - * #getPrivilegeName()} is used to find the {@link IPrivilege} which has the associated {@link PrivilegePolicy} for + * #getPrivilegeName()} is used to find the {@link Privilege} which has the associated {@link PrivilegePolicy} for * evaluating access *

* @@ -30,9 +30,9 @@ import li.strolch.privilege.policy.PrivilegePolicy; public interface Restrictable { /** - * Returns the name of the {@link IPrivilege} which is to be used to validate privileges against + * Returns the name of the {@link Privilege} which is to be used to validate privileges against * - * @return the name of the {@link IPrivilege} which is to be used to validate privileges against + * @return the name of the {@link Privilege} which is to be used to validate privileges against */ String getPrivilegeName(); diff --git a/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java b/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java index 6ccf045a0..9ad05849d 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java @@ -15,16 +15,15 @@ */ package li.strolch.privilege.model; -import static li.strolch.utils.helper.StringHelper.trimOrEmpty; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; - import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.internal.Role; -import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; + +import java.text.MessageFormat; +import java.util.List; + +import static li.strolch.utils.helper.StringHelper.isEmpty; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; /** * To keep certain details of the {@link Role} itself hidden from remote clients and make sure instances are only edited @@ -33,7 +32,7 @@ import li.strolch.utils.helper.StringHelper; * * @author Robert von Burg */ -public class RoleRep implements Serializable { +public class RoleRep { private String name; private List privileges; @@ -41,32 +40,28 @@ public class RoleRep implements Serializable { /** * Default constructor * - * @param name - * the name of this role - * @param privileges - * the list of privileges granted to this role + * @param name the name of this role + * @param privileges the list of privileges granted to this role */ public RoleRep(String name, List privileges) { this.name = trimOrEmpty(name); - this.privileges = privileges; + this.privileges = privileges == null ? List.of() : List.copyOf(privileges); } /** * validates that all required fields are set */ public void validate() { - if (StringHelper.isEmpty(this.name)) + if (isEmpty(this.name)) throw new PrivilegeException("name is null"); - if (this.privileges != null && !this.privileges.isEmpty()) { - for (PrivilegeRep privilege : this.privileges) { - try { - privilege.validate(); - } catch (Exception e) { - String msg = "Privilege {0} is invalid on role {1}"; - msg = MessageFormat.format(msg, privilege.getName(), this.name); - throw new PrivilegeException(msg, e); - } + for (PrivilegeRep privilege : this.privileges) { + try { + privilege.validate(); + } catch (Exception e) { + String msg = "Privilege {0} is invalid on role {1}"; + msg = MessageFormat.format(msg, privilege.getName(), this.name); + throw new PrivilegeException(msg, e); } } } @@ -79,8 +74,7 @@ public class RoleRep implements Serializable { } /** - * @param name - * the name to set + * @param name the name to set */ public void setName(String name) { this.name = trimOrEmpty(name); @@ -92,17 +86,17 @@ public class RoleRep implements Serializable { * @return the privileges assigned to this Role as a list */ public List getPrivileges() { - return this.privileges == null ? new ArrayList<>() : this.privileges; + return this.privileges; } /** * Sets the privileges on this from a list * - * @param privileges - * the list of privileges to assign to this role + * @param privileges the list of privileges to assign to this role */ public void setPrivileges(List privileges) { - this.privileges = privileges; + DBC.PRE.assertNotNull("privileges must not be null!", privileges); + this.privileges = List.copyOf(privileges); } /** @@ -112,8 +106,7 @@ public class RoleRep implements Serializable { */ @Override public String toString() { - return "RoleRep [name=" + this.name + ", privilegeMap=" + (this.privileges == null ? "null" : this.privileges) - + "]"; + return "RoleRep [name=" + this.name + ", privilegeMap=" + this.privileges + "]"; } @Override @@ -133,10 +126,9 @@ public class RoleRep implements Serializable { if (getClass() != obj.getClass()) return false; RoleRep other = (RoleRep) obj; - if (this.name == null) { + if (this.name == null) return other.name == null; - } else - return this.name.equals(other.name); + return this.name.equals(other.name); } public T accept(PrivilegeElementVisitor visitor) { diff --git a/privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java b/privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java index cbaff2928..86a4546c6 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java +++ b/privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java @@ -26,10 +26,8 @@ public class SimpleRestrictable implements Restrictable { private final Object value; /** - * @param name - * the name of the privilege - * @param value - * the value allowed on the privilege + * @param name the name of the privilege + * @param value the value allowed on the privilege */ public SimpleRestrictable(String name, Object value) { DBC.PRE.assertNotEmpty("name must not be empty", name); diff --git a/privilege/src/main/java/li/strolch/privilege/model/UserRep.java b/privilege/src/main/java/li/strolch/privilege/model/UserRep.java index 19b0f9da7..13d52d07a 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/UserRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/UserRep.java @@ -15,20 +15,20 @@ */ package li.strolch.privilege.model; -import static li.strolch.privilege.base.PrivilegeConstants.*; -import static li.strolch.utils.helper.StringHelper.trimOrEmpty; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.*; -import java.util.stream.Collectors; - import li.strolch.privilege.base.PrivilegeConstants; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; -import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; + +import java.text.MessageFormat; +import java.util.*; + +import static java.util.stream.Collectors.toSet; +import static li.strolch.privilege.base.PrivilegeConstants.*; +import static li.strolch.utils.helper.StringHelper.isEmpty; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; /** * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited @@ -37,7 +37,7 @@ import li.strolch.utils.helper.StringHelper; * * @author Robert von Burg */ -public class UserRep implements Serializable { +public class UserRep { private String userId; private String username; @@ -45,45 +45,42 @@ public class UserRep implements Serializable { private String lastname; private UserState userState; private Locale locale; + private Set groups; private Set roles; private Map properties; private UserHistory history; + private boolean readOnly; + /** * Default constructor * - * @param userId - * the user's id - * @param username - * the user's login name - * @param firstname - * the user's first name - * @param lastname - * the user's last name - * @param userState - * the user's {@link UserState} - * @param roles - * the set of {@link Role}s assigned to this user - * @param locale - * the user's {@link Locale} - * @param propertyMap - * a {@link Map} containing string value pairs of properties for this user + * @param userId the user's id + * @param username the user's login name + * @param firstname the user's first name + * @param lastname the user's last name + * @param userState the user's {@link UserState} + * @param groups the set of {@link li.strolch.privilege.model.internal.Group}s assigned to this user + * @param roles the set of {@link Role}s assigned to this user + * @param locale the user's {@link Locale} + * @param propertyMap a {@link Map} containing string value pairs of properties for this user */ public UserRep(String userId, String username, String firstname, String lastname, UserState userState, - Set roles, Locale locale, Map propertyMap, UserHistory history) { + Set groups, Set roles, Locale locale, Map propertyMap, + UserHistory history) { this.userId = trimOrEmpty(userId); this.username = trimOrEmpty(username); this.firstname = trimOrEmpty(firstname); this.lastname = trimOrEmpty(lastname); this.userState = userState; - this.roles = roles == null ? null : roles.stream().map(String::trim).collect(Collectors.toSet()); + this.groups = groups == null ? null : groups.stream().map(String::trim).collect(toSet()); + this.roles = roles == null ? null : roles.stream().map(String::trim).collect(toSet()); this.locale = locale; - if (propertyMap != null) { - this.properties = new HashMap<>(); + this.properties = new HashMap<>(); + if (propertyMap != null) propertyMap.forEach((key, value) -> this.properties.put(key.trim(), value.trim())); - } this.history = history; } @@ -97,30 +94,52 @@ public class UserRep implements Serializable { * Validates that all required fields are set */ public void validate() { + if (isEmpty(this.userId)) + throw new PrivilegeException("userId must not be empty"); + if (isEmpty(this.username)) + throw new PrivilegeException("username must not be empty"); - if (StringHelper.isEmpty(this.userId)) - throw new PrivilegeException("userId is null or empty"); - - if (StringHelper.isEmpty(this.username)) - throw new PrivilegeException("username is null or empty"); - - // username must be at least 2 characters in length - if (this.username.length() < 2) { - String msg = MessageFormat.format("The given username ''{0}'' is shorter than 2 characters", this.username); + // username must be at least 3 characters in length + if (this.username.length() < 3) { + String msg = MessageFormat.format("The given username ''{0}'' is shorter than 3 characters", this.username); throw new PrivilegeException(msg); } if (this.userState == null) - throw new PrivilegeException("userState is null"); + throw new PrivilegeException("userState may not be null"); - if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); + if (this.userState != UserState.SYSTEM) { + if (isEmpty(this.firstname)) + throw new PrivilegeException("firstname may not be empty for non-system users"); + if (isEmpty(this.lastname)) + throw new PrivilegeException("lastname may not be empty for non-system users"); + } - if (StringHelper.isEmpty(this.lastname)) - throw new PrivilegeException("lastname is null or empty"); + if (this.groups == null) + throw new PrivilegeException("groups may not be null"); + if (this.roles == null) + throw new PrivilegeException("roles may not be null"); - if (this.roles == null || this.roles.isEmpty()) - throw new PrivilegeException("roles is null or empty"); + if (this.groups.isEmpty() && this.roles.isEmpty()) + throw new PrivilegeException("User must have at least one group or role assigned!"); + } + + public boolean isReadOnly() { + return readOnly; + } + + public UserRep readOnly() { + assertNotReadonly(); + this.readOnly = true; + this.groups = Set.copyOf(this.groups); + this.roles = Set.copyOf(this.roles); + this.properties = Map.copyOf(this.properties); + return this; + } + + protected void assertNotReadonly() { + if (this.readOnly) + throw new IllegalStateException("User is currently readOnly, to modify get a copy!"); } public boolean isSystemUser() { @@ -145,10 +164,10 @@ public class UserRep implements Serializable { /** * Set the userId * - * @param userId - * to set + * @param userId to set */ public void setUserId(String userId) { + assertNotReadonly(); this.userId = trimOrEmpty(userId); } @@ -160,10 +179,10 @@ public class UserRep implements Serializable { } /** - * @param username - * the username to set + * @param username the username to set */ public void setUsername(String username) { + assertNotReadonly(); this.username = trimOrEmpty(username); } @@ -175,10 +194,10 @@ public class UserRep implements Serializable { } /** - * @param firstname - * the firstname to set + * @param firstname the firstname to set */ public void setFirstname(String firstname) { + assertNotReadonly(); this.firstname = trimOrEmpty(firstname); } @@ -190,10 +209,10 @@ public class UserRep implements Serializable { } /** - * @param lastname - * the lastname to set + * @param lastname the lastname to set */ public void setLastname(String lastname) { + assertNotReadonly(); this.lastname = trimOrEmpty(lastname); } @@ -205,13 +224,27 @@ public class UserRep implements Serializable { } /** - * @param userState - * the userState to set + * @param userState the userState to set */ public void setUserState(UserState userState) { + assertNotReadonly(); this.userState = userState; } + public Set getGroups() { + return groups; + } + + public void setGroups(Set groups) { + DBC.PRE.assertNotNull("groups must not be null!", groups); + assertNotReadonly(); + this.groups = groups.stream().map(String::trim).collect(toSet()); + } + + public boolean hasGroup(String group) { + return this.groups != null && this.groups.contains(group); + } + /** * @return the roles */ @@ -220,23 +253,16 @@ public class UserRep implements Serializable { } /** - * @param roles - * the roles to set + * @param roles the roles to set */ public void setRoles(Set roles) { - this.roles = roles.stream().map(String::trim).collect(Collectors.toSet()); + DBC.PRE.assertNotNull("roles must not be null!", roles); + assertNotReadonly(); + this.roles = roles.stream().map(String::trim).collect(toSet()); } - /** - * Returns true if this user has the given role - * - * @param role - * the role to check for - * - * @return returns true if this user has the given role - */ public boolean hasRole(String role) { - return this.roles.contains(role); + return this.roles != null && this.roles.contains(role); } /** @@ -247,10 +273,10 @@ public class UserRep implements Serializable { } /** - * @param locale - * the locale to set + * @param locale the locale to set */ public void setLocale(Locale locale) { + assertNotReadonly(); this.locale = locale; } @@ -261,17 +287,16 @@ public class UserRep implements Serializable { */ public UserHistory getHistory() { if (this.history == null) - return new UserHistory(); + return UserHistory.EMPTY; return this.history; } /** - * Returns true if the the given property exists + * Returns true if the given property exists * - * @param key - * the property key to check + * @param key the property key to check * - * @return true if the the given property exists + * @return true if the given property exists */ public boolean hasProperty(String key) { return this.properties.containsKey(key); @@ -280,39 +305,41 @@ public class UserRep implements Serializable { /** * Returns the property with the given key * - * @param key - * the key for which the property is to be returned + * @param key the key for which the property is to be returned * * @return the property with the given key, or null if the property is not defined */ public String getProperty(String key) { - if (this.properties == null) - return null; return this.properties.get(key); } /** * Set the property with the key to the value * - * @param key - * the key of the property to set - * @param value - * the value of the property to set + * @param key the key of the property to set + * @param value the value of the property to set */ public void setProperty(String key, String value) { - if (this.properties == null) - this.properties = new HashMap<>(1); + DBC.PRE.assertNotEmpty("key must not be empty!", key); + DBC.PRE.assertNotEmpty("value must not be empty!", value); + assertNotReadonly(); this.properties.put(key.trim(), value.trim()); } + public void setProperties(Map properties) { + DBC.PRE.assertNotNull("properties must not be null!", properties); + assertNotReadonly(); + this.properties = properties; + } + /** * Returns the {@link Set} of keys of all properties * * @return the {@link Set} of keys of all properties */ public Set getPropertyKeySet() { - if (this.properties == null) - return new HashSet<>(); + if (this.readOnly) + return this.properties.keySet(); return new HashSet<>(this.properties.keySet()); } @@ -322,8 +349,8 @@ public class UserRep implements Serializable { * @return the map of properties */ public Map getProperties() { - if (this.properties == null) - return new HashMap<>(); + if (this.readOnly) + return this.properties; return new HashMap<>(this.properties); } @@ -361,9 +388,9 @@ public class UserRep implements Serializable { */ @Override public String toString() { - return "UserRep [userId=" + this.userId + ", username=" + this.username + ", firstname=" + this.firstname - + ", lastname=" + this.lastname + ", userState=" + this.userState + ", locale=" + this.locale - + ", roles=" + this.roles + "]"; + return "UserRep [userId=" + this.userId + ", username=" + this.username + ", firstname=" + this.firstname + + ", lastname=" + this.lastname + ", userState=" + this.userState + ", locale=" + this.locale + + ", roles=" + this.roles + "]"; } @Override @@ -391,11 +418,12 @@ public class UserRep implements Serializable { public UserRep getCopy() { - Set roles = new HashSet<>(this.roles); - Map propertyMap = this.properties == null ? null : new HashMap<>(this.properties); + Set groups = this.groups == null ? null : new HashSet<>(this.groups); + Set roles = this.roles == null ? null : new HashSet<>(this.roles); + Map propertyMap = new HashMap<>(this.properties); - return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, roles, - this.locale, propertyMap, this.history == null ? new UserHistory() : this.history.getClone()); + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, groups, roles, + this.locale, propertyMap, this.history == null ? UserHistory.EMPTY : this.history); } public T accept(PrivilegeElementVisitor visitor) { diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/Group.java b/privilege/src/main/java/li/strolch/privilege/model/internal/Group.java new file mode 100644 index 000000000..8c2c069de --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/Group.java @@ -0,0 +1,92 @@ +package li.strolch.privilege.model.internal; + +import li.strolch.privilege.base.PrivilegeConstants; +import li.strolch.utils.dbc.DBC; + +import java.util.Map; +import java.util.Set; + +import static li.strolch.privilege.base.PrivilegeConstants.*; + +/** + * This entity represents a group with which {@link User Users} can be associated. This allows to put roles and + * properties which are always duplicated on users on to the group, and the User is then in the group, eliminating + * duplication. + */ +public record Group(String name, Set roles, Map propertyMap) { + public Group(String name, Set roles, Map propertyMap) { + DBC.PRE.assertNotEmpty("name must not be empty", name); + DBC.PRE.assertNotNull("roles must not be null", roles); + DBC.PRE.assertNotNull("propertyMap must not be null", propertyMap); + this.name = name; + this.roles = Set.copyOf(roles); + this.propertyMap = Map.copyOf(propertyMap); + } + + /** + * Returns true if this group has the specified role + * + * @param role the name of the {@link Role} to check for + * + * @return true if this group has the specified role + */ + public boolean hasRole(String role) { + return this.roles.contains(role); + } + + /** + * Returns the property with the given key + * + * @param key the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + return this.propertyMap.keySet(); + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getProperties() { + return this.propertyMap; + } + + /** + * Returns the value of the property {@link PrivilegeConstants#REALM} + * + * @return the value of the property {@link PrivilegeConstants#REALM} + */ + public String getRealm() { + return getProperty(REALM); + } + + /** + * Returns the value of the property {@link PrivilegeConstants#ORGANISATION} + * + * @return the value of the property {@link PrivilegeConstants#ORGANISATION} + */ + public String getOrganisation() { + return getProperty(ORGANISATION); + } + + /** + * Returns the value of the property {@link PrivilegeConstants#LOCATION} + * + * @return the value of the property {@link PrivilegeConstants#LOCATION} + */ + public String getLocation() { + return getProperty(LOCATION); + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/PasswordCrypt.java b/privilege/src/main/java/li/strolch/privilege/model/internal/PasswordCrypt.java index e0ba31350..fc749f9ec 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/PasswordCrypt.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/PasswordCrypt.java @@ -2,48 +2,33 @@ package li.strolch.privilege.model.internal; import static li.strolch.utils.helper.StringHelper.*; -public class PasswordCrypt { +public record PasswordCrypt(byte[] password, byte[] salt, String hashAlgorithm, int hashIterations, int hashKeyLength) { - private final byte[] password; - private final byte[] salt; - private final String hashAlgorithm; - private final int hashIterations; - private final int hashKeyLength; - - public PasswordCrypt(byte[] password, byte[] salt) { - this.password = password; - this.salt = salt; - this.hashAlgorithm = null; - this.hashIterations = -1; - this.hashKeyLength = -1; + @Override + public String toString() { + return buildPasswordString(); } - public PasswordCrypt(byte[] password, byte[] salt, String hashAlgorithm, int hashIterations, int hashKeyLength) { - this.password = password; - this.salt = salt; - this.hashAlgorithm = hashAlgorithm; - this.hashIterations = hashIterations; - this.hashKeyLength = hashKeyLength; + public String buildPasswordString() { + if (this.password == null || this.salt == null || this.hashAlgorithm == null || this.hashIterations == -1 || + this.hashKeyLength == -1) { + return null; + } + + return buildPasswordString(this.hashAlgorithm, this.hashIterations, this.hashKeyLength, this.salt, + this.password); } - public byte[] getPassword() { - return password; + public static String buildPasswordString(String hashAlgorithm, int hashIterations, int hashKeyLength, byte[] salt, + byte[] passwordArr) { + String algo = hashAlgorithm + "," + hashIterations + "," + hashKeyLength; + String hash = toHexString(salt); + String password = toHexString(passwordArr); + return "$" + algo + "$" + hash + "$" + password; } - public byte[] getSalt() { - return salt; - } - - public String getHashAlgorithm() { - return hashAlgorithm; - } - - public int getHashIterations() { - return hashIterations; - } - - public int getHashKeyLength() { - return hashKeyLength; + public static PasswordCrypt of(byte[] password, byte[] salt) { + return new PasswordCrypt(password, salt, null, -1, -1); } public static PasswordCrypt parse(String passwordS, String saltS) { @@ -55,14 +40,14 @@ public class PasswordCrypt { salt = fromHexString(saltS.trim()); if (isEmpty(passwordS)) - return new PasswordCrypt(null, salt); + return PasswordCrypt.of(null, salt); passwordS = passwordS.trim(); byte[] password; if (!passwordS.startsWith("$")) { password = fromHexString(passwordS); - return new PasswordCrypt(password, salt); + return PasswordCrypt.of(password, salt); } String[] parts = passwordS.split("\\$"); @@ -86,28 +71,4 @@ public class PasswordCrypt { return new PasswordCrypt(password, salt, hashAlgorithm, hashIterations, hashKeyLength); } - - @Override - public String toString() { - return buildPasswordString(); - } - - public String buildPasswordString() { - if (this.password == null || this.salt == null || this.hashAlgorithm == null || this.hashIterations == -1 || - this.hashKeyLength == -1) { - return null; - } - - return buildPasswordString(getHashAlgorithm(), getHashIterations(), getHashKeyLength(), getSalt(), - getPassword()); - - } - - public static String buildPasswordString(String hashAlgorithm, int hashIterations, int hashKeyLength, byte[] salt, - byte[] passwordArr) { - String algo = hashAlgorithm + "," + hashIterations + "," + hashKeyLength; - String hash = toHexString(salt); - String password = toHexString(passwordArr); - return "$" + algo + "$" + hash + "$" + password; - } } diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java b/privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java deleted file mode 100644 index 076a07581..000000000 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.privilege.model.internal; - -import java.text.MessageFormat; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import li.strolch.privilege.base.PrivilegeException; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.IPrivilege; -import li.strolch.privilege.model.PrivilegeRep; -import li.strolch.privilege.model.Restrictable; -import li.strolch.privilege.policy.PrivilegePolicy; -import li.strolch.utils.helper.StringHelper; - -/** - *

- * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it - * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a {@link - * PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow lists - * configured which is used to evaluate if privilege is granted to a {@link Restrictable} - *

- * - *

- * {@link IPrivilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to - *

- * - *

- * Note: This is an internal object which is not to be serialized or passed to clients, {@link PrivilegeRep}s are used - * for that - *

- * - * @author Robert von Burg - */ -public final class PrivilegeImpl implements IPrivilege { - - private final String name; - private final String policy; - private final boolean allAllowed; - private final Set denyList; - private final Set allowList; - - /** - * Default constructor - * - * @param name - * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} - * @param policy - * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted. If null, then privilege is - * granted - * @param allAllowed - * a boolean defining if a {@link Role} with this {@link PrivilegeImpl} has unrestricted access to a {@link - * Restrictable} in which case the deny and allow lists are ignored and can be null - * @param denyList - * a list of deny rules for this {@link PrivilegeImpl}, can be null if all allowed - * @param allowList - * a list of allow rules for this {@link PrivilegeImpl}, can be null if all allowed - */ - public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { - - if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); - } - if (StringHelper.isEmpty(policy)) { - throw new PrivilegeException( - MessageFormat.format("Policy may not be empty for Privilege {0}!", name)); - } - if (denyList == null) { - throw new PrivilegeException( - MessageFormat.format("denyList is null for Privilege {0}!", name)); - } - if (allowList == null) { - throw new PrivilegeException( - MessageFormat.format("allowList is null for Privilege {0}!", name)); - } - - this.name = name; - this.allAllowed = allAllowed; - this.policy = policy; - this.denyList = Collections.unmodifiableSet(denyList); - this.allowList = Collections.unmodifiableSet(allowList); - } - - /** - * Constructs a {@link PrivilegeImpl} from the {@link PrivilegeRep} - * - * @param privilegeRep - * the {@link PrivilegeRep} from which to create the {@link PrivilegeImpl} - */ - public PrivilegeImpl(PrivilegeRep privilegeRep) { - this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), - privilegeRep.getAllowList()); - } - - /** - * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients - */ - @Override - public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet<>(this.denyList), - new HashSet<>(this.allowList)); - } - - /** - * @return the name - */ - @Override - public String getName() { - return this.name; - } - - /** - * @return the policy - */ - @Override - public String getPolicy() { - return this.policy; - } - - /** - * @return the allAllowed - */ - @Override - public boolean isAllAllowed() { - return this.allAllowed; - } - - /** - * @return the allowList - */ - @Override - public Set getAllowList() { - return this.allowList; - } - - /** - * @return the denyList - */ - @Override - public Set getDenyList() { - return this.denyList; - } - - /** - * @return true if there are values in the allow list - */ - @Override - public boolean hasAllowed() { - return !this.allowList.isEmpty(); - } - - /** - * @return if the value is in the allow list - */ - @Override - public boolean isAllowed(String value) { - return this.allowList.contains(value); - } - - /** - * @return true if there are values in the deny list - */ - @Override - public boolean hasDenied() { - return !this.allowList.isEmpty(); - } - - /** - * @return true if the value is in the deny list - */ - @Override - public boolean isDenied(String value) { - return this.denyList.contains(value); - } - - /** - * Returns a string representation of this object displaying its concrete type and its values - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "Privilege [name=" + this.name + ", policy=" + this.policy + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - PrivilegeImpl other = (PrivilegeImpl) obj; - if (this.name == null) { - return other.name == null; - } else - return this.name.equals(other.name); - } -} diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java b/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java index 0b48a20d3..b212bfaae 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java @@ -15,14 +15,16 @@ */ package li.strolch.privilege.model.internal; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Privilege; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.RoleRep; +import li.strolch.utils.dbc.DBC; + import java.util.*; import java.util.Map.Entry; -import li.strolch.privilege.base.PrivilegeException; -import li.strolch.privilege.model.IPrivilege; -import li.strolch.privilege.model.PrivilegeRep; -import li.strolch.privilege.model.RoleRep; -import li.strolch.utils.helper.StringHelper; +import static li.strolch.utils.helper.StringHelper.isEmpty; /** *

@@ -37,57 +39,34 @@ import li.strolch.utils.helper.StringHelper; * * @author Robert von Burg */ -public final class Role { - - private final String name; - private final Map privilegeMap; - - /** - * Default constructor - * - * @param name - * the name of the role - * @param privilegeMap - * a map of {@link IPrivilege}s granted to this role - */ - public Role(String name, Map privilegeMap) { - - if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); - } - if (privilegeMap == null) { - throw new PrivilegeException("No privileges defined!"); - } +public record Role(String name, Map privilegeMap) { + public Role(String name, Map privilegeMap) { + DBC.PRE.assertNotEmpty("name must not be empty", name); + DBC.PRE.assertNotNull("privilegeMap must not be null", privilegeMap); this.name = name; - this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + this.privilegeMap = Map.copyOf(privilegeMap); } /** * Construct {@link Role} from its representation {@link RoleRep} * - * @param roleRep - * the representation from which to create the {@link Role} + * @param roleRep the representation from which to create the {@link Role} */ - public Role(RoleRep roleRep) { - + public static Role of(RoleRep roleRep) { String name = roleRep.getName(); - if (StringHelper.isEmpty(name)) { + if (isEmpty(name)) throw new PrivilegeException("No name defined!"); - } - - if (roleRep.getPrivileges() == null) { + if (roleRep.getPrivileges() == null) throw new PrivilegeException("Privileges may not be null!"); - } // build privileges from rep - Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); + Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); for (PrivilegeRep privilege : roleRep.getPrivileges()) { - privilegeMap.put(privilege.getName(), new PrivilegeImpl(privilege)); + privilegeMap.put(privilege.getName(), Privilege.of(privilege)); } - this.name = name; - this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + return new Role(name, privilegeMap); } /** @@ -98,30 +77,29 @@ public final class Role { } /** - * Returns the {@link Set} of names for the currently stored {@link IPrivilege Privileges} + * Returns the {@link Set} of names for the currently stored {@link Privilege Privileges} * - * @return the {@link Set} of names for the currently stored {@link IPrivilege Privileges} + * @return the {@link Set} of names for the currently stored {@link Privilege Privileges} */ public Set getPrivilegeNames() { return this.privilegeMap.keySet(); } /** - * Returns the {@link IPrivilege} for the given name, null if it does not exist + * Returns the {@link Privilege} for the given name, null if it does not exist * - * @return the {@link IPrivilege} for the given name, null if it does not exist + * @return the {@link Privilege} for the given name, null if it does not exist */ - public IPrivilege getPrivilege(String name) { + public Privilege getPrivilege(String name) { return this.privilegeMap.get(name); } /** - * Determines if this {@link Role} has the {@link IPrivilege} with the given name + * Determines if this {@link Role} has the {@link Privilege} with the given name * - * @param name - * the name of the {@link IPrivilege} + * @param name the name of the {@link Privilege} * - * @return true if this {@link Role} has the {@link IPrivilege} with the given name + * @return true if this {@link Role} has the {@link Privilege} with the given name */ public boolean hasPrivilege(String name) { return this.privilegeMap.containsKey(name); @@ -132,7 +110,7 @@ public final class Role { */ public RoleRep asRoleRep() { List privileges = new ArrayList<>(); - for (Entry entry : this.privilegeMap.entrySet()) { + for (Entry entry : this.privilegeMap.entrySet()) { privileges.add(entry.getValue().asPrivilegeRep()); } return new RoleRep(this.name, privileges); diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/User.java b/privilege/src/main/java/li/strolch/privilege/model/internal/User.java index 72c3b327f..ef4cdd7a5 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/User.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/User.java @@ -16,12 +16,14 @@ package li.strolch.privilege.model.internal; import li.strolch.privilege.base.PrivilegeConstants; -import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.UserRep; import li.strolch.privilege.model.UserState; -import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; -import java.util.*; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import static li.strolch.privilege.base.PrivilegeConstants.*; @@ -34,59 +36,36 @@ import static li.strolch.privilege.base.PrivilegeConstants.*; * that *

* + * @param userId the user's id + * @param username the user's login name + * @param passwordCrypt the {@link PasswordCrypt} containing user's password information + * @param firstname the user's first name + * @param lastname the user's lastname + * @param userState the user's {@link UserState} + * @param groups the set of {@link Group}s assigned to this user + * @param roles the set of {@link Role}s assigned to this user + * @param locale the user's {@link Locale} + * @param propertyMap a {@link Map} containing string value pairs of properties for this user + * * @author Robert von Burg */ -public final class User { +public record User(String userId, String username, PasswordCrypt passwordCrypt, String firstname, String lastname, + UserState userState, Set groups, Set roles, Locale locale, + Map propertyMap, boolean passwordChangeRequested, UserHistory history) { - private final String userId; - - private final String username; - private final PasswordCrypt passwordCrypt; - - private final String firstname; - private final String lastname; - - private final Set roles; - - private final UserState userState; - private final Map propertyMap; - private final Locale locale; - - private final boolean passwordChangeRequested; - private final UserHistory history; - - /** - * Default constructor - * - * @param userId the user's id - * @param username the user's login name - * @param passwordCrypt the {@link PasswordCrypt} containing user's password information - * @param firstname the user's first name - * @param lastname the user's lastname - * @param userState the user's {@link UserState} - * @param roles the set of {@link Role}s assigned to this user - * @param locale the user's {@link Locale} - * @param propertyMap a {@link Map} containing string value pairs of properties for this user - */ public User(String userId, String username, PasswordCrypt passwordCrypt, String firstname, String lastname, - UserState userState, Set roles, Locale locale, Map propertyMap, + UserState userState, Set groups, Set roles, Locale locale, Map propertyMap, boolean passwordChangeRequested, UserHistory history) { - if (StringHelper.isEmpty(userId)) - throw new PrivilegeException("No UserId defined!"); - if (userState == null) - throw new PrivilegeException("No userState defined!"); - if (StringHelper.isEmpty(username)) - throw new PrivilegeException("No username defined!"); - if (userState != UserState.SYSTEM) { - if (StringHelper.isEmpty(lastname)) - throw new PrivilegeException("No lastname defined!"); - if (StringHelper.isEmpty(firstname)) - throw new PrivilegeException("No firstname defined!"); - } + DBC.PRE.assertNotEmpty("userId must not be empty", userId); + DBC.PRE.assertNotEmpty("username must not be empty", username); + DBC.PRE.assertNotNull("userState must not be null", userState); + DBC.PRE.assertNotNull("history must not be null", history); - if (history == null) - throw new PrivilegeException("History must not be null!"); + if (userState != UserState.SYSTEM) { + DBC.PRE.assertNotEmpty("lastname must not be empty when not system user!", username); + DBC.PRE.assertNotEmpty("firstname must not be empty when not system user!", firstname); + } // passwordCrypt may be null, meaning not able to login // roles may be null, meaning not able to login and must be added later @@ -103,35 +82,19 @@ public final class User { this.firstname = firstname; this.lastname = lastname; - if (roles == null) - this.roles = Collections.emptySet(); - else - this.roles = Set.copyOf(roles); - - if (locale == null) - this.locale = Locale.getDefault(); - else - this.locale = locale; - - if (propertyMap == null) - this.propertyMap = Collections.emptyMap(); - else - this.propertyMap = Map.copyOf(propertyMap); + this.groups = groups == null ? Set.of() : Set.copyOf(groups); + this.roles = roles == null ? Set.of() : Set.copyOf(roles); + this.locale = locale == null ? Locale.getDefault() : locale; + this.propertyMap = propertyMap == null ? Map.of() : Map.copyOf(propertyMap); this.passwordChangeRequested = passwordChangeRequested; this.history = history; } - /** - * @return the userId - */ public String getUserId() { return this.userId; } - /** - * @return the username - */ public String getUsername() { return this.username; } @@ -145,48 +108,34 @@ public final class User { return this.passwordCrypt; } - /** - * @return the first name - */ public String getFirstname() { return this.firstname; } - /** - * @return the last name - */ public String getLastname() { return this.lastname; } - /** - * @return the userState - */ public UserState getUserState() { return this.userState; } - /** - * @return the roles - */ + public Set getGroups() { + return this.groups; + } + public Set getRoles() { return this.roles; } - /** - * Returns true if this user has the specified role - * - * @param role the name of the {@link Role} to check for - * - * @return true if the this user has the specified role - */ + public boolean hasGroup(String group) { + return this.groups.contains(group); + } + public boolean hasRole(String role) { return this.roles.contains(role); } - /** - * @return the locale - */ public Locale getLocale() { return this.locale; } @@ -195,11 +144,6 @@ public final class User { return this.passwordChangeRequested; } - /** - * Returns the History object - * - * @return the History object - */ public UserHistory getHistory() { return this.history; } @@ -282,8 +226,8 @@ public final class User { * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, - new HashSet<>(this.roles), this.locale, new HashMap<>(this.propertyMap), this.history.getClone()); + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, this.groups, + this.roles, this.locale, new HashMap<>(this.propertyMap), this.history); } /** @@ -315,9 +259,13 @@ public final class User { if (getClass() != obj.getClass()) return false; User other = (User) obj; - if (this.userId == null) { + if (this.userId == null) return other.userId == null; - } else - return this.userId.equals(other.userId); + return this.userId.equals(other.userId); + } + + public User withHistory(UserHistory history) { + return new User(this.userId, this.username, this.passwordCrypt, this.firstname, this.lastname, this.userState, + this.groups, this.roles, this.locale, this.propertyMap, this.passwordChangeRequested, history); } } diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/UserChallenge.java b/privilege/src/main/java/li/strolch/privilege/model/internal/UserChallenge.java index 808810df4..f16e852db 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/UserChallenge.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/UserChallenge.java @@ -1,10 +1,11 @@ package li.strolch.privilege.model.internal; +import li.strolch.privilege.model.Usage; +import li.strolch.utils.dbc.DBC; + import java.time.LocalDateTime; -import li.strolch.privilege.model.Usage; - -public class UserChallenge { +public final class UserChallenge { private final User user; private final String challenge; private final String source; @@ -13,6 +14,10 @@ public class UserChallenge { private boolean fulfilled; public UserChallenge(Usage usage, User user, String challenge, String source) { + DBC.PRE.assertNotNull("usage may not be null", usage); + DBC.PRE.assertNotNull("user may not be null", user); + DBC.PRE.assertNotNull("challenge may not be empty", challenge); + DBC.PRE.assertNotNull("source may not be empty", source); this.usage = usage; this.user = user; this.challenge = challenge; diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/UserHistory.java b/privilege/src/main/java/li/strolch/privilege/model/internal/UserHistory.java index adb5d9f6d..e899f7796 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/UserHistory.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/UserHistory.java @@ -1,32 +1,20 @@ package li.strolch.privilege.model.internal; -import java.io.Serializable; import java.time.ZonedDateTime; -import li.strolch.utils.iso8601.ISO8601; +import static li.strolch.utils.iso8601.ISO8601.EMPTY_VALUE_ZONED_DATE; -public class UserHistory implements Serializable { +public record UserHistory(ZonedDateTime firstLogin, ZonedDateTime lastLogin, ZonedDateTime lastPasswordChange) { - private ZonedDateTime firstLogin; - private ZonedDateTime lastLogin; - private ZonedDateTime lastPasswordChange; - - public UserHistory() { - this.firstLogin = ISO8601.EMPTY_VALUE_ZONED_DATE; - this.lastLogin = ISO8601.EMPTY_VALUE_ZONED_DATE; - this.lastPasswordChange = ISO8601.EMPTY_VALUE_ZONED_DATE; - } + public static final UserHistory EMPTY = new UserHistory(EMPTY_VALUE_ZONED_DATE, EMPTY_VALUE_ZONED_DATE, + EMPTY_VALUE_ZONED_DATE); public ZonedDateTime getFirstLogin() { return this.firstLogin; } public boolean isFirstLoginEmpty() { - return this.firstLogin.equals(ISO8601.EMPTY_VALUE_ZONED_DATE); - } - - public void setFirstLogin(ZonedDateTime firstLogin) { - this.firstLogin = firstLogin; + return this.firstLogin.equals(EMPTY_VALUE_ZONED_DATE); } public ZonedDateTime getLastLogin() { @@ -34,11 +22,7 @@ public class UserHistory implements Serializable { } public boolean isLastLoginEmpty() { - return this.lastLogin.equals(ISO8601.EMPTY_VALUE_ZONED_DATE); - } - - public void setLastLogin(ZonedDateTime lastLogin) { - this.lastLogin = lastLogin; + return this.lastLogin.equals(EMPTY_VALUE_ZONED_DATE); } public ZonedDateTime getLastPasswordChange() { @@ -46,22 +30,26 @@ public class UserHistory implements Serializable { } public boolean isLastPasswordChangeEmpty() { - return this.lastPasswordChange.equals(ISO8601.EMPTY_VALUE_ZONED_DATE); - } - - public void setLastPasswordChange(ZonedDateTime lastPasswordChange) { - this.lastPasswordChange = lastPasswordChange; + return this.lastPasswordChange.equals(EMPTY_VALUE_ZONED_DATE); } public boolean isEmpty() { return isFirstLoginEmpty() && isLastLoginEmpty() && isLastPasswordChangeEmpty(); } - public UserHistory getClone() { - UserHistory clone = new UserHistory(); - clone.firstLogin = this.firstLogin; - clone.lastLogin = this.lastLogin; - clone.lastPasswordChange = this.lastPasswordChange; - return clone; + public UserHistory withFirstLogin(ZonedDateTime firstLogin) { + return new UserHistory(firstLogin, this.lastLogin, lastPasswordChange); + } + + public UserHistory withLastLogin(ZonedDateTime lastLogin) { + return new UserHistory(this.firstLogin, lastLogin, this.lastPasswordChange); + } + + public UserHistory withLogin(ZonedDateTime lastLogin) { + return new UserHistory(isFirstLoginEmpty() ? lastLogin : this.firstLogin, lastLogin, this.lastPasswordChange); + } + + public UserHistory withLastPasswordChange(ZonedDateTime lastPasswordChange) { + return new UserHistory(this.firstLogin, this.lastLogin, lastPasswordChange); } } diff --git a/privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java index 72c8c2852..6fe66b372 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java @@ -22,7 +22,7 @@ import java.text.MessageFormat; import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.Role; @@ -38,10 +38,10 @@ public class DefaultPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, IPrivilege, Restrictable) + * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Privilege, Restrictable) */ @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { String privilegeValue = validatePrivilegeValue(privilege, restrictable); @@ -54,7 +54,7 @@ public class DefaultPrivilege implements PrivilegePolicy { } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) { String privilegeValue = validatePrivilegeValue(privilege, restrictable); @@ -65,7 +65,7 @@ public class DefaultPrivilege implements PrivilegePolicy { return checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue, false); } - private String validatePrivilegeValue(IPrivilege privilege, Restrictable restrictable) { + private String validatePrivilegeValue(Privilege privilege, Restrictable restrictable) { PrivilegePolicyHelper.preValidate(privilege, restrictable); // get the value on which the action is to be performed diff --git a/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java b/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java index dcdd97d2d..119fbc950 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java @@ -17,7 +17,7 @@ package li.strolch.privilege.policy; import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.Role; @@ -26,7 +26,7 @@ import li.strolch.privilege.model.internal.User; /** *

* {@link PrivilegePolicy} implements logic to determine if a {@link User} which has the given {@link Role} and the - * given {@link IPrivilege} has access to the given {@link Restrictable} + * given {@link Privilege} has access to the given {@link Restrictable} *

* *

@@ -38,29 +38,29 @@ import li.strolch.privilege.model.internal.User; public interface PrivilegePolicy { /** - * Checks if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link Restrictable} + * Checks if the given {@link Role} and the given {@link Privilege} has access to the given {@link Restrictable} * * @param context * the privilege context * @param privilege - * the {@link IPrivilege} containing the permissions + * the {@link Privilege} containing the permissions * @param restrictable * the {@link Restrictable} to which the user wants access * * @throws AccessDeniedException * if action not allowed */ - void validateAction(PrivilegeContext context, IPrivilege privilege, Restrictable restrictable) + void validateAction(PrivilegeContext context, Privilege privilege, Restrictable restrictable) throws AccessDeniedException; /** - * Returns true if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link + * Returns true if the given {@link Role} and the given {@link Privilege} has access to the given {@link * Restrictable} * * @param context * the privilege context * @param privilege - * the {@link IPrivilege} containing the permissions + * the {@link Privilege} containing the permissions * @param restrictable * the {@link Restrictable} to which the user wants access * @@ -69,6 +69,6 @@ public interface PrivilegePolicy { * @throws AccessDeniedException * if something goes wrong with the validate */ - boolean hasPrivilege(PrivilegeContext context, IPrivilege privilege, Restrictable restrictable) + boolean hasPrivilege(PrivilegeContext context, Privilege privilege, Restrictable restrictable) throws PrivilegeException; } diff --git a/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java b/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java index 21efe70c4..e4d0ac639 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java @@ -20,7 +20,7 @@ import java.text.MessageFormat; import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.utils.helper.StringHelper; @@ -34,7 +34,7 @@ public class PrivilegePolicyHelper { * Validates the given values and returns the privilege name * * @param privilege - * the {@link IPrivilege} + * the {@link Privilege} * @param restrictable * the {@link Restrictable} * @@ -43,7 +43,7 @@ public class PrivilegePolicyHelper { * @throws PrivilegeException * if something is wrong */ - public static String preValidate(IPrivilege privilege, Restrictable restrictable) throws PrivilegeException { + public static String preValidate(Privilege privilege, Restrictable restrictable) throws PrivilegeException { if (privilege == null) throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); if (restrictable == null) @@ -88,7 +88,7 @@ public class PrivilegePolicyHelper { * @throws AccessDeniedException * if access is denied */ - public static boolean checkByAllowDenyValues(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + public static boolean checkByAllowDenyValues(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, String privilegeValue, boolean assertHasPrivilege) throws AccessDeniedException { // first check values not allowed @@ -102,7 +102,7 @@ public class PrivilegePolicyHelper { return handleAccessDenied(ctx, privilege, restrictable, privilegeValue, assertHasPrivilege); } - private static boolean handleAccessDenied(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + private static boolean handleAccessDenied(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, String privilegeValue, boolean assertHasPrivilege) { if (assertHasPrivilege) { diff --git a/privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java index 527950af1..7754a0301 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java @@ -24,7 +24,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.Role; @@ -42,18 +42,18 @@ import li.strolch.utils.dbc.DBC; public class RoleAccessPrivilege implements PrivilegePolicy { @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } - private boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + private boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { String privilegeName = preValidate(privilege, restrictable); diff --git a/privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java index 080fae322..39feeda4a 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java @@ -24,7 +24,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.User; @@ -41,18 +41,18 @@ import li.strolch.utils.dbc.DBC; public class UserAccessPrivilege implements PrivilegePolicy { @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } - protected boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + protected boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { String privilegeName = preValidate(privilege, restrictable); diff --git a/privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java index b697776e2..c05803d33 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java @@ -28,7 +28,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.i18n.PrivilegeMessages; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; import li.strolch.privilege.model.internal.User; @@ -43,13 +43,13 @@ import li.strolch.utils.dbc.DBC; public class UserAccessWithSameOrganisationPrivilege extends UserAccessPrivilege { @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } @@ -59,7 +59,7 @@ public class UserAccessWithSameOrganisationPrivilege extends UserAccessPrivilege } @Override - protected boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + protected boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { String privilegeName = preValidate(privilege, restrictable); diff --git a/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java index 414faf8a5..6bb1d8c27 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java @@ -24,7 +24,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.i18n.PrivilegeMessages; import li.strolch.privilege.model.Certificate; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; @@ -44,18 +44,18 @@ import li.strolch.privilege.model.Restrictable; public class UsernameFromCertificatePrivilege implements PrivilegePolicy { @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } - protected boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + protected boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { preValidate(privilege, restrictable); diff --git a/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java index ba654e270..46bec6302 100644 --- a/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java +++ b/privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java @@ -28,7 +28,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.i18n.PrivilegeMessages; import li.strolch.privilege.model.Certificate; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Restrictable; @@ -48,19 +48,19 @@ import li.strolch.privilege.model.Restrictable; public class UsernameFromCertificateWithSameOrganisationPrivilege extends UsernameFromCertificatePrivilege { @Override - public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public void validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws AccessDeniedException { validateAction(ctx, privilege, restrictable, true); } @Override - public boolean hasPrivilege(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) + public boolean hasPrivilege(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable) throws PrivilegeException { return validateAction(ctx, privilege, restrictable, false); } @Override - protected boolean validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + protected boolean validateAction(PrivilegeContext ctx, Privilege privilege, Restrictable restrictable, boolean assertHasPrivilege) throws AccessDeniedException { preValidate(privilege, restrictable); diff --git a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java index e5f0c92f8..2d306974d 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java @@ -55,19 +55,19 @@ public class CertificateStubsSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (qName) { - case XML_ROOT_CERTIFICATES -> { + case ROOT_CERTIFICATES -> { } - case XML_CERTIFICATE -> { + case CERTIFICATE -> { CertificateStub stub = new CertificateStub(); - stub.usage = Usage.valueOf(attributes.getValue(XML_ATTR_USAGE).trim()); - stub.sessionId = attributes.getValue(XML_ATTR_SESSION_ID).trim(); - stub.username = attributes.getValue(XML_ATTR_USERNAME).trim(); - stub.authToken = attributes.getValue(XML_ATTR_AUTH_TOKEN).trim(); - stub.source = attributes.getValue(XML_ATTR_SOURCE).trim(); - stub.locale = Locale.forLanguageTag(attributes.getValue(XML_ATTR_LOCALE).trim()); - stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME).trim()); - stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS).trim()); - stub.keepAlive = Boolean.parseBoolean(attributes.getValue(XML_ATTR_KEEP_ALIVE).trim()); + stub.usage = Usage.valueOf(attributes.getValue(ATTR_USAGE).trim()); + stub.sessionId = attributes.getValue(ATTR_SESSION_ID).trim(); + stub.username = attributes.getValue(ATTR_USERNAME).trim(); + stub.authToken = attributes.getValue(ATTR_AUTH_TOKEN).trim(); + stub.source = attributes.getValue(ATTR_SOURCE).trim(); + stub.locale = Locale.forLanguageTag(attributes.getValue(ATTR_LOCALE).trim()); + stub.loginTime = ISO8601.parseToZdt(attributes.getValue(ATTR_LOGIN_TIME).trim()); + stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(ATTR_LAST_ACCESS).trim()); + stub.keepAlive = Boolean.parseBoolean(attributes.getValue(ATTR_KEEP_ALIVE).trim()); DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId); DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username); DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken); diff --git a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java index 29aabba4c..7351ce28c 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxWriter.java @@ -50,41 +50,41 @@ public class CertificateStubsSaxWriter { Writer ioWriter = new OutputStreamWriter(this.outputStream, StandardCharsets.UTF_8); IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); - xmlWriter.writeStartElement(XML_ROOT_CERTIFICATES); + xmlWriter.writeStartElement(ROOT_CERTIFICATES); List certificates = new ArrayList<>(this.certificates); certificates.sort(comparing(Certificate::getSessionId)); for (Certificate cert : certificates) { // create the certificate element - xmlWriter.writeStartElement(XML_CERTIFICATE); + xmlWriter.writeStartElement(CERTIFICATE); // sessionId; - xmlWriter.writeAttribute(XML_ATTR_SESSION_ID, cert.getSessionId()); + xmlWriter.writeAttribute(ATTR_SESSION_ID, cert.getSessionId()); // usage; - xmlWriter.writeAttribute(XML_ATTR_USAGE, cert.getUsage().name()); + xmlWriter.writeAttribute(ATTR_USAGE, cert.getUsage().name()); // username; - xmlWriter.writeAttribute(XML_ATTR_USERNAME, cert.getUsername()); + xmlWriter.writeAttribute(ATTR_USERNAME, cert.getUsername()); // authToken; - xmlWriter.writeAttribute(XML_ATTR_AUTH_TOKEN, cert.getAuthToken()); + xmlWriter.writeAttribute(ATTR_AUTH_TOKEN, cert.getAuthToken()); // source; - xmlWriter.writeAttribute(XML_ATTR_SOURCE, cert.getSource()); + xmlWriter.writeAttribute(ATTR_SOURCE, cert.getSource()); // locale; - xmlWriter.writeAttribute(XML_ATTR_LOCALE, cert.getLocale().toLanguageTag()); + xmlWriter.writeAttribute(ATTR_LOCALE, cert.getLocale().toLanguageTag()); // loginTime; - xmlWriter.writeAttribute(XML_ATTR_LOGIN_TIME, ISO8601.toString(cert.getLoginTime())); + xmlWriter.writeAttribute(ATTR_LOGIN_TIME, ISO8601.toString(cert.getLoginTime())); // lastAccess; - xmlWriter.writeAttribute(XML_ATTR_LAST_ACCESS, ISO8601.toString(cert.getLastAccess())); + xmlWriter.writeAttribute(ATTR_LAST_ACCESS, ISO8601.toString(cert.getLastAccess())); // keepAlive; - xmlWriter.writeAttribute(XML_ATTR_KEEP_ALIVE, String.valueOf(cert.isKeepAlive())); + xmlWriter.writeAttribute(ATTR_KEEP_ALIVE, String.valueOf(cert.isKeepAlive())); } // and now end diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java index 3e25295e8..1d956b65c 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java @@ -47,9 +47,9 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { switch (qName) { - case XML_CONTAINER -> this.buildersStack.push(new ContainerParser()); - case XML_PARAMETERS -> this.buildersStack.push(new ParametersParser()); - case XML_POLICIES -> this.buildersStack.push(new PoliciesParser()); + case CONTAINER -> this.buildersStack.push(new ContainerParser()); + case PARAMETERS -> this.buildersStack.push(new ParametersParser()); + case POLICIES -> this.buildersStack.push(new PoliciesParser()); default -> { // nothing to do, probably handle on stack } @@ -72,7 +72,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = switch (qName) { - case XML_CONTAINER, XML_PARAMETERS, XML_POLICIES -> this.buildersStack.pop(); + case CONTAINER, PARAMETERS, POLICIES -> this.buildersStack.pop(); default -> null; }; @@ -88,35 +88,35 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (qName) { - case XML_CONTAINER -> this.currentElement = qName; - case XML_HANDLER_PRIVILEGE -> { + case CONTAINER -> this.currentElement = qName; + case HANDLER_PRIVILEGE -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setPrivilegeHandlerClassName(className); } - case XML_HANDLER_ENCRYPTION -> { + case HANDLER_ENCRYPTION -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setEncryptionHandlerClassName(className); } - case XML_HANDLER_PASSWORD_STRENGTH -> { + case HANDLER_PASSWORD_STRENGTH -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setPasswordStrengthHandlerClassName(className); } - case XML_HANDLER_PERSISTENCE -> { + case HANDLER_PERSISTENCE -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setPersistenceHandlerClassName(className); } - case XML_HANDLER_USER_CHALLENGE -> { + case HANDLER_USER_CHALLENGE -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setUserChallengeHandlerClassName(className); } - case XML_HANDLER_SSO -> { + case HANDLER_SSO -> { this.currentElement = qName; - String className = attributes.getValue(XML_ATTR_CLASS).trim(); + String className = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().setSsoHandlerClassName(className); } default -> throw new IllegalStateException("Unexpected value: " + qName); @@ -130,14 +130,14 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { final Map params = parametersChild.getParameterMap(); switch (this.currentElement) { - case XML_CONTAINER -> getContainerModel().setParameterMap(params); - case XML_HANDLER_PRIVILEGE -> getContainerModel().setPrivilegeHandlerParameterMap(params); - case XML_HANDLER_ENCRYPTION -> getContainerModel().setEncryptionHandlerParameterMap(params); - case XML_HANDLER_PASSWORD_STRENGTH -> + case CONTAINER -> getContainerModel().setParameterMap(params); + case HANDLER_PRIVILEGE -> getContainerModel().setPrivilegeHandlerParameterMap(params); + case HANDLER_ENCRYPTION -> getContainerModel().setEncryptionHandlerParameterMap(params); + case HANDLER_PASSWORD_STRENGTH -> getContainerModel().setPasswordStrengthHandlerParameterMap(params); - case XML_HANDLER_PERSISTENCE -> getContainerModel().setPersistenceHandlerParameterMap(params); - case XML_HANDLER_USER_CHALLENGE -> getContainerModel().setUserChallengeHandlerParameterMap(params); - case XML_HANDLER_SSO -> getContainerModel().setSsoHandlerParameterMap(params); + case HANDLER_PERSISTENCE -> getContainerModel().setPersistenceHandlerParameterMap(params); + case HANDLER_USER_CHALLENGE -> getContainerModel().setUserChallengeHandlerParameterMap(params); + case HANDLER_SSO -> getContainerModel().setSsoHandlerParameterMap(params); default -> throw new IllegalStateException("Unexpected value: " + this.currentElement); } } @@ -151,9 +151,9 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - if (qName.equals(XML_PARAMETER)) { - String key = attributes.getValue(XML_ATTR_NAME).trim(); - String value = attributes.getValue(XML_ATTR_VALUE).trim(); + if (qName.equals(PARAMETER)) { + String key = attributes.getValue(ATTR_NAME).trim(); + String value = attributes.getValue(ATTR_VALUE).trim(); this.parameterMap.put(key, value); } } @@ -172,9 +172,9 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - if (qName.equals(XML_POLICY)) { - String policyName = attributes.getValue(XML_ATTR_NAME).trim(); - String policyClassName = attributes.getValue(XML_ATTR_CLASS).trim(); + if (qName.equals(POLICY)) { + String policyName = attributes.getValue(ATTR_NAME).trim(); + String policyClassName = attributes.getValue(ATTR_CLASS).trim(); getContainerModel().addPolicy(policyName, policyClassName); } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java index 4daff831c..6732b66f6 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxWriter.java @@ -46,41 +46,41 @@ public class PrivilegeConfigSaxWriter { try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.configFile), StandardCharsets.UTF_8)) { IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); - xmlWriter.writeStartElement(XML_ROOT_PRIVILEGE); + xmlWriter.writeStartElement(ROOT_PRIVILEGE); // write container element - xmlWriter.writeStartElement(XML_CONTAINER); - writeStringMapElement(xmlWriter, this.containerModel.getParameterMap(), XML_PARAMETERS, XML_PARAMETER); + xmlWriter.writeStartElement(CONTAINER); + writeStringMapElement(xmlWriter, this.containerModel.getParameterMap(), PARAMETERS, PARAMETER); { // write PrivilegeHandler if (this.containerModel.getPrivilegeHandlerClassName() != null) - writeHandler(xmlWriter, XML_HANDLER_PRIVILEGE, this.containerModel.getPrivilegeHandlerClassName(), + writeHandler(xmlWriter, HANDLER_PRIVILEGE, this.containerModel.getPrivilegeHandlerClassName(), this.containerModel.getPrivilegeHandlerParameterMap()); // write EncryptionHandler - writeHandler(xmlWriter, XML_HANDLER_ENCRYPTION, this.containerModel.getEncryptionHandlerClassName(), + writeHandler(xmlWriter, HANDLER_ENCRYPTION, this.containerModel.getEncryptionHandlerClassName(), this.containerModel.getEncryptionHandlerParameterMap()); // write PersistenceHandler - writeHandler(xmlWriter, XML_HANDLER_PERSISTENCE, this.containerModel.getPersistenceHandlerClassName(), + writeHandler(xmlWriter, HANDLER_PERSISTENCE, this.containerModel.getPersistenceHandlerClassName(), this.containerModel.getPersistenceHandlerParameterMap()); // write PasswordStrengthHandler if (this.containerModel.getPasswordStrengthHandlerClassName() != null) - writeHandler(xmlWriter, XML_HANDLER_PASSWORD_STRENGTH, + writeHandler(xmlWriter, HANDLER_PASSWORD_STRENGTH, this.containerModel.getPasswordStrengthHandlerClassName(), this.containerModel.getPasswordStrengthHandlerParameterMap()); // write UserChallengeHandler if (this.containerModel.getUserChallengeHandlerClassName() != null) - writeHandler(xmlWriter, XML_HANDLER_USER_CHALLENGE, + writeHandler(xmlWriter, HANDLER_USER_CHALLENGE, this.containerModel.getUserChallengeHandlerClassName(), this.containerModel.getUserChallengeHandlerParameterMap()); // write SingleSignOnHandler if (this.containerModel.getSsoHandlerClassName() != null) - writeHandler(xmlWriter, XML_HANDLER_SSO, this.containerModel.getSsoHandlerClassName(), + writeHandler(xmlWriter, HANDLER_SSO, this.containerModel.getSsoHandlerClassName(), this.containerModel.getSsoHandlerParameterMap()); } @@ -89,7 +89,7 @@ public class PrivilegeConfigSaxWriter { // Policies Map policies = new HashMap<>(); this.containerModel.getPolicies().forEach((key, value) -> policies.put(key, value.getName())); - writeStringMapElement(xmlWriter, policies, XML_POLICIES, XML_POLICY, XML_ATTR_CLASS); + writeStringMapElement(xmlWriter, policies, POLICIES, POLICY, ATTR_CLASS); // and now end xmlWriter.writeEndDocument(); @@ -103,9 +103,9 @@ public class PrivilegeConfigSaxWriter { xmlWriter.writeEmptyElement(handleName); else xmlWriter.writeStartElement(handleName); - xmlWriter.writeAttribute(XML_ATTR_CLASS, className); + xmlWriter.writeAttribute(ATTR_CLASS, className); - writeStringMapElement(xmlWriter, parameters, XML_PARAMETERS, XML_PARAMETER); + writeStringMapElement(xmlWriter, parameters, PARAMETERS, PARAMETER); if (!parameters.isEmpty()) xmlWriter.writeEndElement(); diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxReader.java new file mode 100644 index 000000000..fe6a834e1 --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxReader.java @@ -0,0 +1,174 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import li.strolch.privilege.model.internal.Group; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.text.MessageFormat; +import java.util.*; + +import static li.strolch.privilege.helper.XmlConstants.*; + +/** + * @author Robert von Burg + */ +public class PrivilegeGroupsSaxReader extends DefaultHandler { + + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeGroupsSaxReader.class); + + private final Deque buildersStack = new ArrayDeque<>(); + + private final Map groups; + + public PrivilegeGroupsSaxReader() { + this.groups = new HashMap<>(); + } + + /** + * @return the users + */ + public Map getGroups() { + return this.groups; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals(GROUP)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(GroupParser.class))) + throw new IllegalArgumentException("Previous Group not closed!"); + this.buildersStack.push(new GroupParser()); + } else if (qName.equals(PROPERTIES)) { + if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(PropertyParser.class))) + throw new IllegalArgumentException("Previous Properties not closed!"); + this.buildersStack.push(new PropertyParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals(GROUP)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(PROPERTIES)) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + + // + // Application + // Administrator + // ENABLED + // en-GB + // + // PrivilegeAdmin + // AppUser + // + // + // + // + // + // + // 2021-02-19T15:32:09.592+01:00 + // 2021-02-19T15:32:09.592+01:00 + // 2021-02-19T15:32:09.592+01:00 + // + // + + public class GroupParser extends ElementParserAdapter { + + StringBuilder text; + + String name; + final Set roles; + Map parameters; + + public GroupParser() { + this.roles = new HashSet<>(); + this.parameters = new HashMap<>(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + + this.text = new StringBuilder(); + + if (qName.equals(GROUP)) { + this.name = attributes.getValue(ATTR_NAME).trim(); + } + } + + @Override + public void characters(char[] ch, int start, int length) { + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) { + + switch (qName) { + case ROLE -> this.roles.add(getText()); + case GROUP -> { + + Group group = new Group(this.name, this.roles, this.parameters); + + logger.info(MessageFormat.format("New Group: {0}", group)); + groups.put(this.name, group); + } + default -> { + if (!(qName.equals(GROUPS) // + || qName.equals(ROLES) // + || qName.equals(PARAMETER) // + || qName.equals(PARAMETERS))) { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + } + } + + private String getText() { + return this.text.toString().trim(); + } + + @Override + public void notifyChild(ElementParser child) { + if (child instanceof PropertyParser) { + this.parameters = ((PropertyParser) child).parameterMap; + } + } + } + +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxWriter.java new file mode 100644 index 000000000..cc3a9f752 --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeGroupsSaxWriter.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.privilege.xml; + +import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.privilege.model.internal.Group; + +import javax.xml.stream.XMLStreamException; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static java.util.Comparator.comparing; +import static li.strolch.privilege.helper.XmlConstants.*; +import static li.strolch.privilege.helper.XmlHelper.*; + +/** + * @author Robert von Burg + */ +public class PrivilegeGroupsSaxWriter { + + private final List groups; + private final File modelFile; + + public PrivilegeGroupsSaxWriter(List groups, File modelFile) { + this.groups = groups; + this.modelFile = modelFile; + } + + public void write() throws IOException, XMLStreamException { + + try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.modelFile), StandardCharsets.UTF_8)) { + + IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); + xmlWriter.writeStartElement(GROUPS); + + List groups = new ArrayList<>(this.groups); + groups.sort(comparing(g -> g.name().toLowerCase(Locale.ROOT))); + for (Group group : this.groups) { + + // start the user element + xmlWriter.writeStartElement(GROUP); + + xmlWriter.writeAttribute(ATTR_NAME, group.name()); + + // add all the role elements + if (!group.roles().isEmpty()) { + xmlWriter.writeStartElement(ROLES); + writeStringList(xmlWriter, ROLE, group.roles()); + xmlWriter.writeEndElement(); + } + + // add the parameters + Map properties = group.getProperties(); + if (!properties.isEmpty()) + writeStringMapElement(xmlWriter, properties, PROPERTIES, PROPERTY); + + xmlWriter.writeEndElement(); + } + + // and now end + xmlWriter.writeEndDocument(); + xmlWriter.flush(); + } + } +} diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java index e91030cdd..eb333cc23 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java @@ -15,8 +15,7 @@ */ package li.strolch.privilege.xml; -import li.strolch.privilege.model.IPrivilege; -import li.strolch.privilege.model.internal.PrivilegeImpl; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.internal.Role; import li.strolch.utils.helper.StringHelper; import org.slf4j.Logger; @@ -52,11 +51,11 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XML_ROLE)) { + if (qName.equals(ROLE)) { if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(RoleParser.class))) throw new IllegalArgumentException("Previous Role not closed!"); this.buildersStack.push(new RoleParser()); - } else if (qName.equals(XML_PROPERTIES)) { + } else if (qName.equals(PROPERTIES)) { if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(PropertyParser.class))) throw new IllegalArgumentException("Previous Properties not closed!"); this.buildersStack.push(new PropertyParser()); @@ -79,9 +78,9 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XML_ROLE)) { + if (qName.equals(ROLE)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals(XML_PROPERTIES)) { + } else if (qName.equals(PROPERTIES)) { elementParser = this.buildersStack.pop(); } @@ -114,7 +113,7 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { private Set denyList; private Set allowList; - private Map privileges; + private Map privileges; public RoleParser() { init(); @@ -139,12 +138,12 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { this.text = new StringBuilder(); switch (qName) { - case XML_ROLE -> this.roleName = attributes.getValue(XML_ATTR_NAME).trim(); - case XML_PRIVILEGE -> { - this.privilegeName = attributes.getValue(XML_ATTR_NAME).trim(); - this.privilegePolicy = attributes.getValue(XML_ATTR_POLICY).trim(); + case ROLE -> this.roleName = attributes.getValue(ATTR_NAME).trim(); + case PRIVILEGE -> { + this.privilegeName = attributes.getValue(ATTR_NAME).trim(); + this.privilegePolicy = attributes.getValue(ATTR_POLICY).trim(); } - case XML_ALLOW, XML_DENY, XML_ALL_ALLOWED -> { + case ALLOW, DENY, ALL_ALLOWED -> { } // no-op default -> throw new IllegalArgumentException("Unhandled tag " + qName); @@ -160,11 +159,11 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) { switch (qName) { - case XML_ALL_ALLOWED -> this.allAllowed = StringHelper.parseBoolean(getText()); - case XML_ALLOW -> this.allowList.add(getText()); - case XML_DENY -> this.denyList.add(getText()); - case XML_PRIVILEGE -> { - IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, + case ALL_ALLOWED -> this.allAllowed = StringHelper.parseBoolean(getText()); + case ALLOW -> this.allowList.add(getText()); + case DENY -> this.denyList.add(getText()); + case PRIVILEGE -> { + Privilege privilege = new Privilege(this.privilegeName, this.privilegePolicy, this.allAllowed, this.denyList, this.allowList); this.privileges.put(this.privilegeName, privilege); this.privilegeName = null; @@ -173,7 +172,7 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { this.denyList = new HashSet<>(); this.allowList = new HashSet<>(); } - case XML_ROLE -> { + case ROLE -> { Role role = new Role(this.roleName, this.privileges); roles.put(role.getName(), role); logger.info(MessageFormat.format("New Role: {0}", role)); @@ -196,11 +195,11 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - if (qName.equals(XML_PROPERTY)) { - String key = attributes.getValue(XML_ATTR_NAME).trim(); - String value = attributes.getValue(XML_ATTR_VALUE).trim(); + if (qName.equals(PROPERTY)) { + String key = attributes.getValue(ATTR_NAME).trim(); + String value = attributes.getValue(ATTR_VALUE).trim(); this.parameterMap.put(key, value); - } else if (!qName.equals(XML_PROPERTIES)) { + } else if (!qName.equals(PROPERTIES)) { throw new IllegalArgumentException("Unhandled tag " + qName); } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java index 904b7d38e..1645509c1 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxWriter.java @@ -16,7 +16,7 @@ package li.strolch.privilege.xml; import javanet.staxutils.IndentingXMLStreamWriter; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.internal.Role; import javax.xml.stream.XMLStreamException; @@ -48,29 +48,29 @@ public class PrivilegeRolesSaxWriter { try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.modelFile), StandardCharsets.UTF_8)) { IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); - xmlWriter.writeStartElement(XML_ROLES); + xmlWriter.writeStartElement(ROLES); List roles = new ArrayList<>(this.roles); - roles.sort(comparing(role1 -> role1.getName().toLowerCase(Locale.ROOT))); + roles.sort(comparing(r -> r.getName().toLowerCase(Locale.ROOT))); for (Role role : roles) { // start the role element - xmlWriter.writeStartElement(XML_ROLE); - xmlWriter.writeAttribute(XML_ATTR_NAME, role.getName()); + xmlWriter.writeStartElement(ROLE); + xmlWriter.writeAttribute(ATTR_NAME, role.getName()); List privilegeNames = new ArrayList<>(role.getPrivilegeNames()); privilegeNames.sort(null); for (String privilegeName : privilegeNames) { - IPrivilege privilege = role.getPrivilege(privilegeName); + Privilege privilege = role.getPrivilege(privilegeName); - xmlWriter.writeStartElement(XML_PRIVILEGE); - xmlWriter.writeAttribute(XML_ATTR_NAME, privilege.getName()); - xmlWriter.writeAttribute(XML_ATTR_POLICY, privilege.getPolicy()); + xmlWriter.writeStartElement(PRIVILEGE); + xmlWriter.writeAttribute(ATTR_NAME, privilege.getName()); + xmlWriter.writeAttribute(ATTR_POLICY, privilege.getPolicy()); if (privilege.isAllAllowed()) - writeStringElement(xmlWriter, XML_ALL_ALLOWED, "true"); - writeStringList(xmlWriter, XML_DENY, privilege.getDenyList()); - writeStringList(xmlWriter, XML_ALLOW, privilege.getAllowList()); + writeStringElement(xmlWriter, ALL_ALLOWED, "true"); + writeStringList(xmlWriter, DENY, privilege.getDenyList()); + writeStringList(xmlWriter, ALLOW, privilege.getAllowList()); xmlWriter.writeEndElement(); } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java index 80a5a7c46..ae33fff87 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java @@ -16,6 +16,7 @@ package li.strolch.privilege.xml; import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.Group; import li.strolch.privilege.model.internal.PasswordCrypt; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; @@ -57,11 +58,11 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XML_USER)) { + if (qName.equals(USER)) { if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(UserParser.class))) throw new IllegalArgumentException("Previous User not closed!"); this.buildersStack.push(new UserParser()); - } else if (qName.equals(XML_PROPERTIES)) { + } else if (qName.equals(PROPERTIES)) { if (this.buildersStack.stream().anyMatch(e -> e.getClass().equals(PropertyParser.class))) throw new IllegalArgumentException("Previous Properties not closed!"); this.buildersStack.push(new PropertyParser()); @@ -84,9 +85,9 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XML_USER)) { + if (qName.equals(USER)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals(XML_PROPERTIES)) { + } else if (qName.equals(PROPERTIES)) { elementParser = this.buildersStack.pop(); } @@ -125,12 +126,14 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { String lastname; UserState userState; Locale locale; + final Set groups; final Set userRoles; Map parameters; UserHistory history; boolean passwordChangeRequested; public UserParser() { + this.groups = new HashSet<>(); this.userRoles = new HashSet<>(); } @@ -139,15 +142,15 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { this.text = new StringBuilder(); - if (qName.equals(XML_USER)) { - this.userId = attributes.getValue(XML_ATTR_USER_ID).trim(); - this.username = attributes.getValue(XML_ATTR_USERNAME).trim(); + if (qName.equals(USER)) { + this.userId = attributes.getValue(ATTR_USER_ID).trim(); + this.username = attributes.getValue(ATTR_USERNAME).trim(); - String password = attributes.getValue(XML_ATTR_PASSWORD); - String salt = attributes.getValue(XML_ATTR_SALT); + String password = attributes.getValue(ATTR_PASSWORD); + String salt = attributes.getValue(ATTR_SALT); this.passwordCrypt = PasswordCrypt.parse(password, salt); - } else if (qName.equals(XML_HISTORY)) { - this.history = new UserHistory(); + } else if (qName.equals(HISTORY)) { + this.history = UserHistory.EMPTY; } } @@ -160,32 +163,35 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { public void endElement(String uri, String localName, String qName) { switch (qName) { - case XML_FIRSTNAME -> this.firstName = getText(); - case XML_LASTNAME -> this.lastname = getText(); - case XML_STATE -> this.userState = UserState.valueOf(getText()); - case XML_LOCALE -> this.locale = Locale.forLanguageTag(getText()); - case XML_PASSWORD_CHANGE_REQUESTED -> this.passwordChangeRequested = Boolean.parseBoolean(getText()); - case XML_FIRST_LOGIN -> this.history.setFirstLogin(ISO8601.parseToZdt(getText())); - case XML_LAST_LOGIN -> this.history.setLastLogin(ISO8601.parseToZdt(getText())); - case XML_LAST_PASSWORD_CHANGE -> this.history.setLastPasswordChange(ISO8601.parseToZdt(getText())); - case XML_ROLE -> this.userRoles.add(getText()); - case XML_USER -> { + case FIRSTNAME -> this.firstName = getText(); + case LASTNAME -> this.lastname = getText(); + case STATE -> this.userState = UserState.valueOf(getText()); + case LOCALE -> this.locale = Locale.forLanguageTag(getText()); + case PASSWORD_CHANGE_REQUESTED -> this.passwordChangeRequested = Boolean.parseBoolean(getText()); + case FIRST_LOGIN -> this.history = this.history.withFirstLogin(ISO8601.parseToZdt(getText())); + case LAST_LOGIN -> this.history = this.history.withLastLogin(ISO8601.parseToZdt(getText())); + case LAST_PASSWORD_CHANGE -> + this.history = this.history.withLastPasswordChange(ISO8601.parseToZdt(getText())); + case GROUP -> this.groups.add(getText()); + case ROLE -> this.userRoles.add(getText()); + case USER -> { if (this.history == null) - this.history = new UserHistory(); + this.history = UserHistory.EMPTY; User user = new User(this.userId, this.username, this.passwordCrypt, this.firstName, this.lastname, - this.userState, this.userRoles, this.locale, this.parameters, this.passwordChangeRequested, - this.history); + this.userState, this.groups, this.userRoles, this.locale, this.parameters, + this.passwordChangeRequested, this.history); logger.info(MessageFormat.format("New User: {0}", user)); String username = caseInsensitiveUsername ? user.getUsername().toLowerCase() : user.getUsername(); users.put(username, user); } default -> { - if (!(qName.equals(XML_ROLES) // - || qName.equals(XML_PARAMETER) // - || qName.equals(XML_HISTORY) // - || qName.equals(XML_PARAMETERS))) { + if (!(qName.equals(ROLES) // + || qName.equals(GROUPS) // + || qName.equals(PARAMETER) // + || qName.equals(HISTORY) // + || qName.equals(PARAMETERS))) { throw new IllegalArgumentException("Unhandled tag " + qName); } } @@ -203,29 +209,4 @@ public class PrivilegeUsersSaxReader extends DefaultHandler { } } } - - static class PropertyParser extends ElementParserAdapter { - - // - - public final Map parameterMap = new HashMap<>(); - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) { - - if (qName.equals(XML_PROPERTY)) { - String key = attributes.getValue(XML_ATTR_NAME).trim(); - String value = attributes.getValue(XML_ATTR_VALUE).trim(); - this.parameterMap.put(key, value); - } else { - if (!qName.equals(XML_PROPERTIES)) { - throw new IllegalArgumentException("Unhandled tag " + qName); - } - } - } - - public Map getParameterMap() { - return this.parameterMap; - } - } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java index 1d758366f..0780d1490 100644 --- a/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java +++ b/privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxWriter.java @@ -47,8 +47,6 @@ public class PrivilegeUsersSaxWriter { public PrivilegeUsersSaxWriter(List users, File modelFile) { this.users = users; this.modelFile = modelFile; - - this.users.sort(comparing(User::getUsername)); } public void write() throws IOException, XMLStreamException { @@ -56,62 +54,68 @@ public class PrivilegeUsersSaxWriter { try (Writer ioWriter = new OutputStreamWriter(new FileOutputStream(this.modelFile), StandardCharsets.UTF_8)) { IndentingXMLStreamWriter xmlWriter = openXmlStreamWriterDocument(ioWriter); - xmlWriter.writeStartElement(XML_USERS); + xmlWriter.writeStartElement(USERS); List users = new ArrayList<>(this.users); users.sort(comparing(u -> u.getUsername().toLowerCase(Locale.ROOT))); for (User user : this.users) { // start the user element - xmlWriter.writeStartElement(XML_USER); + xmlWriter.writeStartElement(USER); - xmlWriter.writeAttribute(XML_ATTR_USER_ID, user.getUserId()); - xmlWriter.writeAttribute(XML_ATTR_USERNAME, user.getUsername()); + xmlWriter.writeAttribute(ATTR_USER_ID, user.getUserId()); + xmlWriter.writeAttribute(ATTR_USERNAME, user.getUsername()); writePassword(user, xmlWriter); // add first name element if (isNotEmpty(user.getFirstname())) - writeStringElement(xmlWriter, XML_FIRSTNAME, user.getFirstname()); + writeStringElement(xmlWriter, FIRSTNAME, user.getFirstname()); // add last name element if (isNotEmpty(user.getLastname())) - writeStringElement(xmlWriter, XML_LASTNAME, user.getLastname()); + writeStringElement(xmlWriter, LASTNAME, user.getLastname()); // add state element - writeStringElement(xmlWriter, XML_STATE, user.getUserState().toString()); + writeStringElement(xmlWriter, STATE, user.getUserState().toString()); // add locale element - writeStringElement(xmlWriter, XML_LOCALE, user.getLocale().toLanguageTag()); + writeStringElement(xmlWriter, LOCALE, user.getLocale().toLanguageTag()); // add password change requested element if (user.isPasswordChangeRequested()) - writeStringElement(xmlWriter, XML_PASSWORD_CHANGE_REQUESTED, "true"); + writeStringElement(xmlWriter, PASSWORD_CHANGE_REQUESTED, "true"); + + // add all the group elements + if (!user.getGroups().isEmpty()) { + xmlWriter.writeStartElement(GROUPS); + writeStringList(xmlWriter, GROUP, user.getGroups()); + xmlWriter.writeEndElement(); + } // add all the role elements if (!user.getRoles().isEmpty()) { - xmlWriter.writeStartElement(XML_ROLES); - writeStringList(xmlWriter, XML_ROLE, user.getRoles()); + xmlWriter.writeStartElement(ROLES); + writeStringList(xmlWriter, ROLE, user.getRoles()); xmlWriter.writeEndElement(); } // add the parameters Map properties = user.getProperties(); - if (!properties.isEmpty()) { - writeStringMapElement(xmlWriter, properties, XML_PROPERTIES, XML_PROPERTY); - } + if (!properties.isEmpty()) + writeStringMapElement(xmlWriter, properties, PROPERTIES, PROPERTY); if (!user.isHistoryEmpty()) { UserHistory history = user.getHistory(); - xmlWriter.writeStartElement(XML_HISTORY); + xmlWriter.writeStartElement(HISTORY); if (!history.isFirstLoginEmpty()) - writeStringElement(xmlWriter, XML_FIRST_LOGIN, ISO8601.toString(history.getFirstLogin())); + writeStringElement(xmlWriter, FIRST_LOGIN, ISO8601.toString(history.getFirstLogin())); if (!history.isLastLoginEmpty()) - writeStringElement(xmlWriter, XML_LAST_LOGIN, ISO8601.toString(history.getLastLogin())); + writeStringElement(xmlWriter, LAST_LOGIN, ISO8601.toString(history.getLastLogin())); if (!history.isLastPasswordChangeEmpty()) - writeStringElement(xmlWriter, XML_LAST_PASSWORD_CHANGE, + writeStringElement(xmlWriter, LAST_PASSWORD_CHANGE, ISO8601.toString(history.getLastPasswordChange())); xmlWriter.writeEndElement(); @@ -133,12 +137,12 @@ public class PrivilegeUsersSaxWriter { String passwordString = passwordCrypt.buildPasswordString(); if (passwordString != null) { - xmlStreamWriter.writeAttribute(XML_ATTR_PASSWORD, passwordString); + xmlStreamWriter.writeAttribute(ATTR_PASSWORD, passwordString); } else { - if (passwordCrypt.getPassword() != null) - xmlStreamWriter.writeAttribute(XML_ATTR_PASSWORD, toHexString(passwordCrypt.getPassword())); - if (passwordCrypt.getSalt() != null) - xmlStreamWriter.writeAttribute(XML_ATTR_SALT, toHexString(passwordCrypt.getSalt())); + if (passwordCrypt.password() != null) + xmlStreamWriter.writeAttribute(ATTR_PASSWORD, toHexString(passwordCrypt.password())); + if (passwordCrypt.salt() != null) + xmlStreamWriter.writeAttribute(ATTR_SALT, toHexString(passwordCrypt.salt())); } } } diff --git a/privilege/src/main/java/li/strolch/privilege/xml/PropertyParser.java b/privilege/src/main/java/li/strolch/privilege/xml/PropertyParser.java new file mode 100644 index 000000000..a0ea1d2dc --- /dev/null +++ b/privilege/src/main/java/li/strolch/privilege/xml/PropertyParser.java @@ -0,0 +1,33 @@ +package li.strolch.privilege.xml; + +import org.xml.sax.Attributes; + +import java.util.HashMap; +import java.util.Map; + +import static li.strolch.privilege.helper.XmlConstants.*; + +class PropertyParser extends ElementParserAdapter { + + // + + public final Map parameterMap = new HashMap<>(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + + if (qName.equals(PROPERTY)) { + String key = attributes.getValue(ATTR_NAME).trim(); + String value = attributes.getValue(ATTR_VALUE).trim(); + this.parameterMap.put(key, value); + } else { + if (!qName.equals(PROPERTIES)) { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + } + + public Map getParameterMap() { + return this.parameterMap; + } +} diff --git a/privilege/src/main/resources/PrivilegeMessages.properties b/privilege/src/main/resources/PrivilegeMessages.properties index 4b7b3a77f..c4507b136 100644 --- a/privilege/src/main/resources/PrivilegeMessages.properties +++ b/privilege/src/main/resources/PrivilegeMessages.properties @@ -11,6 +11,7 @@ Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or e Privilege.privilegeNull=Privilege may not be null\! Privilege.restrictableNull=Restrictable may not be null\! Privilege.noprivilege=No Privilege exists with name {0} +Privilege.noprivilege.group=User {0} does not belong to group {1} Privilege.noprivilege.role=User {0} does not have the role {1} Privilege.noprivilege.user=User {0} does not have the privilege {1} Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} diff --git a/privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java b/privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java index d6f6b5b87..bb00bd2e4 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java @@ -1,12 +1,5 @@ package li.strolch.privilege.test; -import static org.junit.Assert.assertNotNull; - -import java.io.File; -import java.nio.file.Files; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.privilege.helper.PrivilegeInitializer; @@ -18,6 +11,13 @@ import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.nio.file.Files; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static org.junit.Assert.assertNotNull; + public class AbstractPrivilegeTest { protected static final Logger logger = LoggerFactory.getLogger(AbstractPrivilegeTest.class); @@ -57,13 +57,14 @@ public class AbstractPrivilegeTest { } } - protected static void prepareConfigs(String dst, String configFilename, String usersFilename, + protected static void prepareConfigs(String dst, String configFilename, String usersFilename, String groupsFilename, String rolesFilename) { try { File configPath = new File("src/test/resources/config"); File privilegeConfigFile = new File(configPath, configFilename); File privilegeUsersFile = new File(configPath, usersFilename); + File privilegeGroupsFile = new File(configPath, groupsFilename); File privilegeRolesFile = new File(configPath, rolesFilename); File targetPath = new File("target/" + dst); @@ -72,6 +73,7 @@ public class AbstractPrivilegeTest { File dstConfig = new File(targetPath, configFilename); File dstUsers = new File(targetPath, usersFilename); + File dstGroups = new File(targetPath, groupsFilename); File dstRoles = new File(targetPath, rolesFilename); // write config @@ -81,6 +83,7 @@ public class AbstractPrivilegeTest { // copy model Files.copy(privilegeUsersFile.toPath(), dstUsers.toPath()); + Files.copy(privilegeGroupsFile.toPath(), dstGroups.toPath()); Files.copy(privilegeRolesFile.toPath(), dstRoles.toPath()); } catch (Exception e) { diff --git a/privilege/src/test/java/li/strolch/privilege/test/CryptTest.java b/privilege/src/test/java/li/strolch/privilege/test/CryptTest.java index e13c7e1d0..377869432 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/CryptTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/CryptTest.java @@ -20,9 +20,9 @@ public class CryptTest { @BeforeClass public static void beforeClass() { Map parameterMap = new HashMap<>(); - parameterMap.put(XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM); - parameterMap.put(XML_PARAM_HASH_ITERATIONS, "" + DEFAULT_SMALL_ITERATIONS); - parameterMap.put(XML_PARAM_HASH_KEY_LENGTH, "" + DEFAULT_KEY_LENGTH); + parameterMap.put(PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM); + parameterMap.put(PARAM_HASH_ITERATIONS, "" + DEFAULT_SMALL_ITERATIONS); + parameterMap.put(PARAM_HASH_KEY_LENGTH, "" + DEFAULT_KEY_LENGTH); encryptionHandler = new DefaultEncryptionHandler(); encryptionHandler.initialize(parameterMap); @@ -41,7 +41,7 @@ public class CryptTest { PasswordCrypt passwordCrypt = encryptionHandler.hashPassword(password, salt, "PBKDF2WithHmacSHA512", 100000, 256); - assertArrayEquals(passwordCrypt.getPassword(), parsedCryptHash.getPassword()); + assertArrayEquals(passwordCrypt.password(), parsedCryptHash.password()); } @Test @@ -53,9 +53,9 @@ public class CryptTest { assertNotNull(parsedCryptHash); char[] password = "admin".toCharArray(); - PasswordCrypt passwordCrypt = encryptionHandler.hashPassword(password, parsedCryptHash.getSalt(), + PasswordCrypt passwordCrypt = encryptionHandler.hashPassword(password, parsedCryptHash.salt(), "PBKDF2WithHmacSHA512", 100000, 256); - assertArrayEquals(passwordCrypt.getPassword(), parsedCryptHash.getPassword()); + assertArrayEquals(passwordCrypt.password(), parsedCryptHash.password()); } } diff --git a/privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java b/privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java index ad8ac6b27..5029abb88 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java @@ -16,7 +16,7 @@ public class PersistSessionsTest extends AbstractPrivilegeTest { public static void init() { removeConfigs(PersistSessionsTest.class.getSimpleName()); prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", - "PrivilegeRoles.xml"); + "PrivilegeGroups.xml", "PrivilegeRoles.xml"); } @AfterClass diff --git a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java index 6fe6ed34c..68152e9bc 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java @@ -15,14 +15,16 @@ */ package li.strolch.privilege.test; -import static org.junit.Assert.*; - -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import java.util.Set; + +import static org.junit.Assert.*; + /** * @author Robert von Burg */ @@ -32,7 +34,7 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { public static void init() { removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", - "PrivilegeUsersMerge.xml", "PrivilegeRolesMerge.xml"); + "PrivilegeUsersMerge.xml", "PrivilegeGroupsMerge.xml", "PrivilegeRolesMerge.xml"); } @AfterClass @@ -49,7 +51,16 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { public void shouldMergePrivileges1() { try { login("userA", "admin".toCharArray()); - IPrivilege privilege = this.ctx.getPrivilege("Foo"); + assertEquals(Set.of(), this.ctx.getUserRep().getGroups()); + assertFalse(this.ctx.hasGroup("GroupA1")); + assertFalse(this.ctx.hasGroup("GroupA2")); + assertEquals(Set.of("RoleA1", "RoleA2"), this.ctx.getUserRep().getRoles()); + assertTrue(this.ctx.hasRole("RoleA1")); + assertTrue(this.ctx.hasRole("RoleA2")); + assertFalse(this.ctx.hasRole("RoleB2")); + assertNull(this.ctx.getUserRep().getLocation()); + assertEquals(Set.of(), this.ctx.getUserRep().getPropertyKeySet()); + Privilege privilege = this.ctx.getPrivilege("Foo"); assertTrue(privilege.isAllAllowed()); assertTrue(privilege.getAllowList().isEmpty()); assertTrue(privilege.getDenyList().isEmpty()); @@ -63,7 +74,16 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { public void shouldMergePrivileges2() { try { login("userB", "admin".toCharArray()); - IPrivilege privilege = this.ctx.getPrivilege("Bar"); + assertEquals(Set.of(), this.ctx.getUserRep().getGroups()); + assertFalse(this.ctx.hasGroup("GroupB1")); + assertFalse(this.ctx.hasGroup("GroupB2")); + assertEquals(Set.of("RoleB1", "RoleB2"), this.ctx.getUserRep().getRoles()); + assertTrue(this.ctx.hasRole("RoleB1")); + assertTrue(this.ctx.hasRole("RoleB2")); + assertFalse(this.ctx.hasRole("RoleA2")); + assertNull(this.ctx.getUserRep().getLocation()); + assertEquals(Set.of(), this.ctx.getUserRep().getPropertyKeySet()); + Privilege privilege = this.ctx.getPrivilege("Bar"); assertFalse(privilege.isAllAllowed()); assertEquals(2, privilege.getAllowList().size()); assertEquals(2, privilege.getDenyList().size()); @@ -71,4 +91,53 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { logout(); } } + + @Test + public void shouldMergePrivileges3() { + try { + login("userC", "admin".toCharArray()); + assertEquals(Set.of("GroupA1", "GroupA2"), this.ctx.getUserRep().getGroups()); + assertTrue(this.ctx.hasGroup("GroupA1")); + assertTrue(this.ctx.hasGroup("GroupA2")); + assertFalse(this.ctx.hasGroup("GroupB2")); + assertEquals(Set.of("RoleA1", "RoleA2"), this.ctx.getUserRep().getRoles()); + assertTrue(this.ctx.hasRole("RoleA1")); + assertTrue(this.ctx.hasRole("RoleA2")); + assertFalse(this.ctx.hasRole("RoleB2")); + assertEquals("LocationA2", this.ctx.getUserRep().getLocation()); + assertEquals(Set.of("location"), this.ctx.getUserRep().getPropertyKeySet()); + Privilege privilege = this.ctx.getPrivilege("Foo"); + assertTrue(privilege.isAllAllowed()); + assertTrue(privilege.getAllowList().isEmpty()); + assertTrue(privilege.getDenyList().isEmpty()); + + } finally { + logout(); + } + } + + @Test + public void shouldMergePrivileges4() { + try { + login("userD", "admin".toCharArray()); + assertEquals(Set.of("GroupB1", "GroupB2"), this.ctx.getUserRep().getGroups()); + assertTrue(this.ctx.hasGroup("GroupB1")); + assertTrue(this.ctx.hasGroup("GroupB2")); + assertFalse(this.ctx.hasGroup("GroupA2")); + assertEquals(Set.of("RoleB1", "RoleB2"), this.ctx.getUserRep().getRoles()); + assertTrue(this.ctx.hasRole("RoleB1")); + assertTrue(this.ctx.hasRole("RoleB2")); + assertFalse(this.ctx.hasRole("RoleA2")); + assertEquals("LocationB2", this.ctx.getUserRep().getLocation()); + assertEquals(Set.of("location"), this.ctx.getUserRep().getPropertyKeySet()); + Privilege privilege = this.ctx.getPrivilege("Bar"); + assertFalse(privilege.isAllAllowed()); + assertEquals(2, privilege.getAllowList().size()); + assertEquals(2, privilege.getDenyList().size()); + assertEquals(Set.of("allow1", "allow2"), privilege.getAllowList()); + assertEquals(Set.of("deny1", "deny2"), privilege.getDenyList()); + } finally { + logout(); + } + } } diff --git a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java index e285de9d2..afcc5ec66 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java @@ -15,12 +15,6 @@ */ package li.strolch.privilege.test; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; - -import java.text.MessageFormat; -import java.util.*; - import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.InvalidCredentialsException; import li.strolch.privilege.base.PrivilegeException; @@ -41,9 +35,15 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.text.MessageFormat; +import java.util.*; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; + /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> - * + *

* TODO add more tests, especially with deny and allow lists * * @author Robert von Burg @@ -77,12 +77,12 @@ public class PrivilegeTest extends AbstractPrivilegeTest { public static void init() { removeConfigs(PrivilegeTest.class.getSimpleName()); prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", - "PrivilegeRoles.xml"); + "PrivilegeGroups.xml", "PrivilegeRoles.xml"); } @AfterClass public static void destroy() { - removeConfigs(PrivilegeTest.class.getSimpleName()); + //removeConfigs(PrivilegeTest.class.getSimpleName()); } @Before @@ -209,7 +209,9 @@ public class PrivilegeTest extends AbstractPrivilegeTest { } }); MatcherAssert.assertThat(exception.getMessage(), containsString( - "User system_admin2 does not have the privilege li.strolch.privilege.handler.SystemAction with value li.strolch.privilege.test.model.TestSystemUserActionDeny needed for Restrictable li.strolch.privilege.test.model.TestSystemUserActionDeny")); + "User system_admin2 does not have the privilege li.strolch.privilege.handler.SystemAction with value " + + "li.strolch.privilege.test.model.TestSystemUserActionDeny needed for Restrictable " + + "li.strolch.privilege.test.model.TestSystemUserActionDeny")); } /** @@ -252,7 +254,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { assertNotEquals("Admin", user.getLastname()); // let's add a new user bob - UserRep userRep = new UserRep(null, ADMIN, "The", "Admin", null, null, null, null, null); + UserRep userRep = new UserRep(null, ADMIN, "The", "Admin", null, null, null, null, null, null); this.privilegeHandler.updateUser(certificate, userRep); user = this.privilegeHandler.getUser(certificate, ADMIN); @@ -273,7 +275,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); // let's add a new user bob - UserRep userRep = new UserRep(null, BOB, null, null, null, null, null, null, null); + UserRep userRep = new UserRep(null, BOB, null, null, null, null, null, null, null, null); this.privilegeHandler.updateUser(certificate, userRep); } finally { logout(); @@ -291,7 +293,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); // let's add a new user bob - UserRep userRep = new UserRep(null, ADMIN, null, null, null, null, null, null, null); + UserRep userRep = new UserRep(null, ADMIN, null, null, null, null, null, null, null, null); this.privilegeHandler.updateUser(certificate, userRep); } finally { logout(); @@ -308,7 +310,24 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); - UserRep selectorRep = new UserRep(null, ADMIN, null, null, null, null, null, null, null); + UserRep selectorRep = new UserRep(null, ADMIN, null, null, null, null, null, null, null, null); + List users = this.privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(1, users.size()); + assertEquals(ADMIN, users.get(0).getUsername()); + + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsersByGroups() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, null, null, null, null, Set.of("GroupA"), null, null, null, null); List users = this.privilegeHandler.queryUsers(certificate, selectorRep); assertEquals(1, users.size()); assertEquals(ADMIN, users.get(0).getUsername()); @@ -325,8 +344,8 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); - UserRep selectorRep = new UserRep(null, null, null, null, null, - new HashSet<>(Collections.singletonList("PrivilegeAdmin")), null, null, null); + UserRep selectorRep = new UserRep(null, null, null, null, null, null, Set.of("PrivilegeAdmin"), null, null, + null); List users = this.privilegeHandler.queryUsers(certificate, selectorRep); assertEquals(2, users.size()); assertEquals(ADMIN, users.get(0).getUsername()); @@ -344,7 +363,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); UserRep selectorRep = new UserRep(null, null, null, null, null, - new HashSet<>(Collections.singletonList(ROLE_TEMP)), null, null, null); + new HashSet<>(Collections.singletonList(ROLE_TEMP)), null, null, null, null); List users = this.privilegeHandler.queryUsers(certificate, selectorRep); assertEquals(0, users.size()); @@ -593,7 +612,10 @@ public class PrivilegeTest extends AbstractPrivilegeTest { this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { - String msg = "User bob does not have the privilege li.strolch.privilege.test.model.TestRestrictable needed for Restrictable li.strolch.privilege.test.model.TestRestrictable and value li.strolch.privilege.test.model.TestRestrictable"; + String msg = + "User bob does not have the privilege li.strolch.privilege.test.model.TestRestrictable needed for " + + "Restrictable li.strolch.privilege.test.model.TestRestrictable and value " + + "li.strolch.privilege.test.model.TestRestrictable"; assertEquals(msg, e.getLocalizedMessage()); } finally { logout(); @@ -656,7 +678,8 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // let's add a new user ted HashSet roles = new HashSet<>(); roles.add(ROLE_USER); - userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap<>(), null); + userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, Set.of(), roles, null, new HashMap<>(), + null); Certificate certificate = this.ctx.getCertificate(); this.privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + TED); @@ -688,14 +711,14 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // auth as Bob login(BOB, ArraysHelper.copyOf(PASS_BOB)); // let's add a new user Ted - userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet<>(), null, + userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, Set.of(), Set.of(), null, new HashMap<>(), null); certificate = this.ctx.getCertificate(); this.privilegeHandler.addUser(certificate, userRep, null); fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), - BOB, PrivilegeHandler.PRIVILEGE_ADD_USER); + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), BOB, + PrivilegeHandler.PRIVILEGE_ADD_USER); assertEquals(msg, e.getMessage()); } finally { logout(); @@ -768,8 +791,8 @@ public class PrivilegeTest extends AbstractPrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, - new HashSet<>(Collections.singletonList(ROLE_MY)), null, new HashMap<>(), null); + UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, Set.of(), Set.of(ROLE_MY), null, + new HashMap<>(), null); Certificate certificate = this.ctx.getCertificate(); this.privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/privilege/src/test/java/li/strolch/privilege/test/SsoHandlerTest.java b/privilege/src/test/java/li/strolch/privilege/test/SsoHandlerTest.java index 9c9dacd92..36a7b468e 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/SsoHandlerTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/SsoHandlerTest.java @@ -17,7 +17,7 @@ public class SsoHandlerTest extends AbstractPrivilegeTest { public static void init() { removeConfigs(SsoHandlerTest.class.getSimpleName()); prepareConfigs(SsoHandlerTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", - "PrivilegeRoles.xml"); + "PrivilegeGroups.xml", "PrivilegeRoles.xml"); } @AfterClass @@ -39,6 +39,7 @@ public class SsoHandlerTest extends AbstractPrivilegeTest { data.put("username", "admin"); data.put("firstName", "Admin"); data.put("lastName", "Istrator"); + data.put("groups", "AppUserLocationA"); data.put("roles", "PrivilegeAdmin, AppUser"); // auth diff --git a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index 15c006690..d5f88c23e 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -16,7 +16,7 @@ package li.strolch.privilege.test; import li.strolch.privilege.handler.*; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.UserState; import li.strolch.privilege.model.internal.*; import li.strolch.privilege.test.model.DummySsoHandler; @@ -80,6 +80,11 @@ public class XmlTest { throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); } + tmpFile = new File(TARGET_TEST + "PrivilegeGroupsTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + tmpFile = new File(TARGET_TEST + "PrivilegeRolesTest.xml"); if (tmpFile.exists() && !tmpFile.delete()) { throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); @@ -194,12 +199,13 @@ public class XmlTest { assertEquals("1", admin.getUserId()); assertEquals("admin", admin.getUsername()); assertEquals("cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344", - StringHelper.toHexString(admin.getPasswordCrypt().getPassword())); - assertEquals("61646d696e", StringHelper.toHexString(admin.getPasswordCrypt().getSalt())); + StringHelper.toHexString(admin.getPasswordCrypt().password())); + assertEquals("61646d696e", StringHelper.toHexString(admin.getPasswordCrypt().salt())); assertEquals("Application", admin.getFirstname()); assertEquals("Administrator", admin.getLastname()); assertEquals(UserState.ENABLED, admin.getUserState()); assertEquals("en-GB", admin.getLocale().toLanguageTag()); + assertEquals(Set.of("GroupA"), admin.getGroups()); MatcherAssert.assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); Map properties = admin.getProperties(); assertEquals(new HashSet<>(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); @@ -215,8 +221,48 @@ public class XmlTest { assertEquals("Administrator", systemAdmin.getLastname()); assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); assertEquals("en-GB", systemAdmin.getLocale().toLanguageTag()); + assertEquals(Set.of(), systemAdmin.getGroups()); MatcherAssert.assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); assertTrue(systemAdmin.getProperties().isEmpty()); + + // admin2 + User admin2 = findUser("admin2", users); + assertEquals("1", admin2.getUserId()); + assertEquals("admin2", admin2.getUsername()); + assertEquals("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", + StringHelper.toHexString(admin2.getPasswordCrypt().password())); + assertEquals("Application", admin2.getFirstname()); + assertEquals("Administrator", admin2.getLastname()); + assertEquals(UserState.ENABLED, admin2.getUserState()); + assertEquals("en-GB", admin2.getLocale().toLanguageTag()); + MatcherAssert.assertThat(admin2.getGroups(), containsInAnyOrder("AppUserLocationA")); + MatcherAssert.assertThat(admin2.getRoles(), containsInAnyOrder("PrivilegeAdmin")); + properties = admin2.getProperties(); + assertEquals(new HashSet<>(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); + assertEquals("eitchnet.ch", properties.get("organization")); + assertEquals("Development", properties.get("organizationalUnit")); + + } + + @Test + public void canReadGroups() { + + PrivilegeGroupsSaxReader xmlHandler = new PrivilegeGroupsSaxReader(); + File xmlFile = new File(SRC_TEST + "PrivilegeGroups.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + Map groups = xmlHandler.getGroups(); + assertNotNull(groups); + + assertEquals(1, groups.size()); + + // group AppUserLocationA + Group group = groups.get("AppUserLocationA"); + assertEquals("AppUserLocationA", group.name()); + MatcherAssert.assertThat(group.roles(), containsInAnyOrder("AppUser", "ModelAccessor", "UserPrivileges")); + Map properties = group.getProperties(); + assertEquals(new HashSet<>(List.of("location")), properties.keySet()); + assertEquals("LocationA", properties.get("location")); } @Test @@ -241,18 +287,18 @@ public class XmlTest { Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); assertEquals(18, privilegeAdmin.getPrivilegeNames().size()); - IPrivilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); + Privilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); assertFalse(privilegeAction.isAllAllowed()); assertEquals(5, privilegeAction.getAllowList().size()); assertEquals(0, privilegeAction.getDenyList().size()); assertEquals("DefaultPrivilege", privilegeAction.getPolicy()); - IPrivilege privilegeAddRole = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ADD_ROLE); + Privilege privilegeAddRole = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ADD_ROLE); assertTrue(privilegeAddRole.isAllAllowed()); assertEquals(0, privilegeAddRole.getAllowList().size()); assertEquals(0, privilegeAddRole.getDenyList().size()); - IPrivilege privilegeRemRoleFromUser = privilegeAdmin.getPrivilege( + Privilege privilegeRemRoleFromUser = privilegeAdmin.getPrivilege( PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); assertTrue(privilegeRemRoleFromUser.isAllAllowed()); assertEquals(0, privilegeRemRoleFromUser.getAllowList().size()); @@ -264,7 +310,7 @@ public class XmlTest { assertEquals(new HashSet<>(Collections.singletonList("li.strolch.privilege.test.model.TestRestrictable")), appUser.getPrivilegeNames()); - IPrivilege testRestrictable = appUser.getPrivilege("li.strolch.privilege.test.model.TestRestrictable"); + Privilege testRestrictable = appUser.getPrivilege("li.strolch.privilege.test.model.TestRestrictable"); assertEquals("li.strolch.privilege.test.model.TestRestrictable", testRestrictable.getName()); assertEquals("DefaultPrivilege", testRestrictable.getPolicy()); assertTrue(testRestrictable.isAllAllowed()); @@ -279,7 +325,7 @@ public class XmlTest { containsInAnyOrder("li.strolch.privilege.handler.SystemAction", "li.strolch.privilege.test.model.TestSystemRestrictable")); - IPrivilege testSystemUserAction = systemAdminPrivileges.getPrivilege( + Privilege testSystemUserAction = systemAdminPrivileges.getPrivilege( "li.strolch.privilege.handler.SystemAction"); assertEquals("li.strolch.privilege.handler.SystemAction", testSystemUserAction.getName()); assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); @@ -287,7 +333,7 @@ public class XmlTest { assertEquals(1, testSystemUserAction.getAllowList().size()); assertEquals(1, testSystemUserAction.getDenyList().size()); - IPrivilege testSystemRestrictable = systemAdminPrivileges.getPrivilege( + Privilege testSystemRestrictable = systemAdminPrivileges.getPrivilege( "li.strolch.privilege.test.model.TestSystemRestrictable"); assertEquals("li.strolch.privilege.test.model.TestSystemRestrictable", testSystemRestrictable.getName()); assertEquals("DefaultPrivilege", testSystemRestrictable.getPolicy()); @@ -302,7 +348,7 @@ public class XmlTest { MatcherAssert.assertThat(restrictedRole.getPrivilegeNames(), containsInAnyOrder("li.strolch.privilege.handler.SystemAction")); - IPrivilege testSystemUserAction2 = restrictedRole.getPrivilege("li.strolch.privilege.handler.SystemAction"); + Privilege testSystemUserAction2 = restrictedRole.getPrivilege("li.strolch.privilege.handler.SystemAction"); assertEquals("li.strolch.privilege.handler.SystemAction", testSystemUserAction2.getName()); assertEquals("DefaultPrivilege", testSystemUserAction2.getPolicy()); assertFalse(testSystemUserAction2.isAllAllowed()); @@ -326,29 +372,35 @@ public class XmlTest { public void canWriteUsers() throws XMLStreamException, IOException { Map propertyMap; + Set groups; Set userRoles; List users = new ArrayList<>(); propertyMap = new HashMap<>(); propertyMap.put("prop1", "value1"); + groups = new HashSet<>(); + groups.add("group1"); userRoles = new HashSet<>(); userRoles.add("role1"); - UserHistory history = new UserHistory(); - history.setFirstLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault())); + UserHistory history = UserHistory.EMPTY.withFirstLogin( + ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault())); User user1 = new User("1", "user1", new PasswordCrypt("blabla".getBytes(), "blabla".getBytes(), "PBKDF2WithHmacSHA512", 10000, 256), "Bob", - "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap, false, history); + "White", UserState.DISABLED, groups, userRoles, Locale.ENGLISH, propertyMap, false, history); users.add(user1); propertyMap = new HashMap<>(); propertyMap.put("prop2", "value2"); + groups = new HashSet<>(); + groups.add("group2"); userRoles = new HashSet<>(); userRoles.add("role2"); - history = new UserHistory(); - history.setFirstLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault())); - history.setLastLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 5, 2, 3, 4, 5), ZoneId.systemDefault())); + history = UserHistory.EMPTY.withFirstLogin( + ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault())) + .withLastLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 5, 2, 3, 4, 5), ZoneId.systemDefault())); User user2 = new User("2", "user2", new PasswordCrypt("haha".getBytes(), "haha".getBytes(), null, -1, -1), - "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap, false, history); + "Leonard", "Sheldon", UserState.ENABLED, groups, userRoles, Locale.ENGLISH, propertyMap, false, + history); users.add(user2); File modelFile = new File(TARGET_TEST + "PrivilegeUsersTest.xml"); @@ -370,8 +422,8 @@ public class XmlTest { assertEquals(user1.getFirstname(), parsedUser1.getFirstname()); assertEquals(user1.getLastname(), parsedUser1.getLastname()); assertEquals(user1.getLocale(), parsedUser1.getLocale()); - assertArrayEquals(user1.getPasswordCrypt().getPassword(), parsedUser1.getPasswordCrypt().getPassword()); - assertArrayEquals(user1.getPasswordCrypt().getSalt(), parsedUser1.getPasswordCrypt().getSalt()); + assertArrayEquals(user1.getPasswordCrypt().password(), parsedUser1.getPasswordCrypt().password()); + assertArrayEquals(user1.getPasswordCrypt().salt(), parsedUser1.getPasswordCrypt().salt()); assertEquals(user1.getProperties(), parsedUser1.getProperties()); assertEquals(user1.getUserId(), parsedUser1.getUserId()); assertEquals(user1.getUserState(), parsedUser1.getUserState()); @@ -380,22 +432,57 @@ public class XmlTest { assertEquals(user2.getFirstname(), parsedUser2.getFirstname()); assertEquals(user2.getLastname(), parsedUser2.getLastname()); assertEquals(user2.getLocale(), parsedUser2.getLocale()); - assertArrayEquals(user2.getPasswordCrypt().getPassword(), parsedUser2.getPasswordCrypt().getPassword()); - assertArrayEquals(user2.getPasswordCrypt().getSalt(), parsedUser2.getPasswordCrypt().getSalt()); + assertArrayEquals(user2.getPasswordCrypt().password(), parsedUser2.getPasswordCrypt().password()); + assertArrayEquals(user2.getPasswordCrypt().salt(), parsedUser2.getPasswordCrypt().salt()); assertEquals(user2.getProperties(), parsedUser2.getProperties()); assertEquals(user2.getUserId(), parsedUser2.getUserId()); assertEquals(user2.getUserState(), parsedUser2.getUserState()); assertEquals(user2.getRoles(), parsedUser2.getRoles()); } + @Test + public void canWriteGroups() throws XMLStreamException, IOException { + + Map propertyMap; + Set roles; + + List groups = new ArrayList<>(); + propertyMap = new HashMap<>(); + propertyMap.put("prop1", "value1"); + roles = new HashSet<>(); + roles.add("role1"); + Group newGroup = new Group("group1", roles, propertyMap); + groups.add(newGroup); + + File modelFile = new File(TARGET_TEST + "PrivilegeGroupsTest.xml"); + PrivilegeGroupsSaxWriter configSaxWriter = new PrivilegeGroupsSaxWriter(groups, modelFile); + configSaxWriter.write(); + + PrivilegeGroupsSaxReader xmlHandler = new PrivilegeGroupsSaxReader(); + XmlHelper.parseDocument(modelFile, xmlHandler); + + Map parsedGroups = xmlHandler.getGroups(); + assertNotNull(parsedGroups); + assertEquals(1, parsedGroups.size()); + + // group group1 + Group parsedGroup1 = parsedGroups.get("group1"); + assertNotNull(parsedGroup1); + assertEquals("group1", parsedGroup1.name()); + MatcherAssert.assertThat(parsedGroup1.roles(), containsInAnyOrder("role1")); + Map properties = parsedGroup1.getProperties(); + assertEquals(new HashSet<>(List.of("prop1")), properties.keySet()); + assertEquals("value1", properties.get("prop1")); + } + @Test public void canWriteRoles() throws XMLStreamException, IOException { - Map privilegeMap; + Map privilegeMap; List roles = new ArrayList<>(); Set list = Collections.emptySet(); privilegeMap = new HashMap<>(); - privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); + privilegeMap.put("priv1", new Privilege("priv1", "DefaultPrivilege", true, list, list)); Role role1 = new Role("role1", privilegeMap); roles.add(role1); @@ -404,7 +491,7 @@ public class XmlTest { denyList.add("myself"); Set allowList = new HashSet<>(); allowList.add("other"); - privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); + privilegeMap.put("priv2", new Privilege("priv2", "DefaultPrivilege", false, denyList, allowList)); Role role2 = new Role("role2", privilegeMap); roles.add(role2); @@ -428,8 +515,8 @@ public class XmlTest { Set privilegeNames = role1.getPrivilegeNames(); assertEquals(privilegeNames, parsedRole1.getPrivilegeNames()); for (String privilegeName : privilegeNames) { - IPrivilege privilege = role1.getPrivilege(privilegeName); - IPrivilege privilege2 = parsedRole1.getPrivilege(privilegeName); + Privilege privilege = role1.getPrivilege(privilegeName); + Privilege privilege2 = parsedRole1.getPrivilege(privilegeName); assertNotNull(privilege); assertNotNull(privilege2); @@ -443,8 +530,8 @@ public class XmlTest { assertEquals(role2.getPrivilegeNames(), parsedRole2.getPrivilegeNames()); privilegeNames = role2.getPrivilegeNames(); for (String privilegeName : privilegeNames) { - IPrivilege privilege = role2.getPrivilege(privilegeName); - IPrivilege privilege2 = parsedRole2.getPrivilege(privilegeName); + Privilege privilege = role2.getPrivilege(privilegeName); + Privilege privilege2 = parsedRole2.getPrivilege(privilegeName); assertNotNull(privilege); assertNotNull(privilege2); diff --git a/privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java b/privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java index 98432ceb9..1acdb05c6 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java +++ b/privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java @@ -28,9 +28,10 @@ public class DummySsoHandler implements SingleSignOnHandler { @SuppressWarnings("unchecked") Map map = (Map) data; + Set groups = Arrays.stream(map.get("groups").split(",")).map(String::trim).collect(Collectors.toSet()); Set roles = Arrays.stream(map.get("roles").split(",")).map(String::trim).collect(Collectors.toSet()); Map properties = new HashMap<>(); return new User(map.get("userId"), map.get("username"), null, map.get("firstName"), map.get("lastName"), - UserState.REMOTE, roles, Locale.ENGLISH, properties, false, new UserHistory()); + UserState.REMOTE, groups, roles, Locale.ENGLISH, properties, false, UserHistory.EMPTY); } } diff --git a/privilege/src/test/resources/config/PrivilegeConfigMerge.xml b/privilege/src/test/resources/config/PrivilegeConfigMerge.xml index 98fd39c2a..b49a8416c 100644 --- a/privilege/src/test/resources/config/PrivilegeConfigMerge.xml +++ b/privilege/src/test/resources/config/PrivilegeConfigMerge.xml @@ -27,6 +27,7 @@ + diff --git a/privilege/src/test/resources/config/PrivilegeGroups.xml b/privilege/src/test/resources/config/PrivilegeGroups.xml new file mode 100644 index 000000000..166062110 --- /dev/null +++ b/privilege/src/test/resources/config/PrivilegeGroups.xml @@ -0,0 +1,13 @@ + + + + + AppUser + ModelAccessor + UserPrivileges + + + + + + \ No newline at end of file diff --git a/privilege/src/test/resources/config/PrivilegeGroupsMerge.xml b/privilege/src/test/resources/config/PrivilegeGroupsMerge.xml new file mode 100644 index 000000000..287714813 --- /dev/null +++ b/privilege/src/test/resources/config/PrivilegeGroupsMerge.xml @@ -0,0 +1,37 @@ + + + + + RoleA1 + + + + + + + + RoleA2 + + + + + + + + + RoleB1 + + + + + + + + + RoleB2 + + + + + + \ No newline at end of file diff --git a/privilege/src/test/resources/config/PrivilegeUsers.xml b/privilege/src/test/resources/config/PrivilegeUsers.xml index e0a38b179..d1a7f1b3d 100644 --- a/privilege/src/test/resources/config/PrivilegeUsers.xml +++ b/privilege/src/test/resources/config/PrivilegeUsers.xml @@ -6,6 +6,9 @@ Administrator ENABLED en-GB + + GroupA + PrivilegeAdmin AppUser @@ -21,9 +24,11 @@ Administrator ENABLED en-GB + + AppUserLocationA + PrivilegeAdmin - AppUser diff --git a/privilege/src/test/resources/config/PrivilegeUsersMerge.xml b/privilege/src/test/resources/config/PrivilegeUsersMerge.xml index 72f075877..bf473df6c 100644 --- a/privilege/src/test/resources/config/PrivilegeUsersMerge.xml +++ b/privilege/src/test/resources/config/PrivilegeUsersMerge.xml @@ -23,4 +23,26 @@ + + System User + Administrator + ENABLED + en-GB + + GroupA1 + GroupA2 + + + + + System User + Administrator + ENABLED + en-GB + + GroupB1 + GroupB2 + + + \ No newline at end of file diff --git a/service/src/main/java/li/strolch/report/ReportSearch.java b/service/src/main/java/li/strolch/report/ReportSearch.java index 77d55674f..e15b204b1 100644 --- a/service/src/main/java/li/strolch/report/ReportSearch.java +++ b/service/src/main/java/li/strolch/report/ReportSearch.java @@ -5,7 +5,7 @@ import static li.strolch.report.ReportConstants.TYPE_REPORT; import java.util.Set; import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.search.ResourceSearch; /** @@ -18,7 +18,7 @@ public class ReportSearch extends ResourceSearch { public ReportSearch(StrolchTransaction tx) { types(TYPE_REPORT); - IPrivilege reportPrivilege = tx.getPrivilegeContext().getPrivilege(ReportSearch.class.getName()); + Privilege reportPrivilege = tx.getPrivilegeContext().getPrivilege(ReportSearch.class.getName()); if (!reportPrivilege.isAllAllowed()) { Set allowedReportIds = reportPrivilege.getAllowList(); where(id().isIn(allowedReportIds)); diff --git a/service/src/test/java/li/strolch/command/privilege/users/PrivilegeAddUserCommandTest.java b/service/src/test/java/li/strolch/command/privilege/users/PrivilegeAddUserCommandTest.java index 4f47fbaaa..bb950a94a 100644 --- a/service/src/test/java/li/strolch/command/privilege/users/PrivilegeAddUserCommandTest.java +++ b/service/src/test/java/li/strolch/command/privilege/users/PrivilegeAddUserCommandTest.java @@ -15,11 +15,6 @@ */ package li.strolch.command.privilege.users; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.util.*; - import li.strolch.command.AbstractRealmCommandTest; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.model.UserRep; @@ -27,6 +22,11 @@ import li.strolch.privilege.model.UserState; import li.strolch.service.api.Command; import li.strolch.service.privilege.users.PrivilegeAddUserCommand; +import java.util.*; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + /** * @author Robert von Burg */ @@ -40,12 +40,14 @@ public class PrivilegeAddUserCommandTest extends AbstractRealmCommandTest { @Override protected Command getCommandInstance(StrolchTransaction tx) { + Set groups = new HashSet<>(); + groups.add("AppGroups"); Set roles = new HashSet<>(); roles.add("AppUser"); Map propertyMap = new HashMap<>(); - UserRep user = new UserRep(null, "dude", "Jeff", "Lebowski", UserState.ENABLED, roles, Locale.getDefault(), - propertyMap, null); + UserRep user = new UserRep(null, "dude", "Jeff", "Lebowski", UserState.ENABLED, groups, roles, + Locale.getDefault(), propertyMap, null); PrivilegeAddUserCommand command = new PrivilegeAddUserCommand(tx); command.setUserIn(user); diff --git a/service/src/test/java/li/strolch/service/privilege/users/PrivilegeAddUserServiceTest.java b/service/src/test/java/li/strolch/service/privilege/users/PrivilegeAddUserServiceTest.java index 1d4a701bf..97d0ce8b9 100644 --- a/service/src/test/java/li/strolch/service/privilege/users/PrivilegeAddUserServiceTest.java +++ b/service/src/test/java/li/strolch/service/privilege/users/PrivilegeAddUserServiceTest.java @@ -43,12 +43,14 @@ public class PrivilegeAddUserServiceTest extends AbstractRealmServiceTest groups = new HashSet<>(); + groups.add("AppGroups"); Set roles = new HashSet<>(); roles.add("AppUser"); Map propertyMap = new HashMap<>(); PrivilegeUserArgument arg = new PrivilegeUserArgument(); - arg.user = new UserRep(null, "dude", "Jeff", "Lebowski", UserState.ENABLED, roles, Locale.getDefault(), + arg.user = new UserRep(null, "dude", "Jeff", "Lebowski", UserState.ENABLED, groups, roles, Locale.getDefault(), propertyMap, null); return arg; diff --git a/service/src/test/java/li/strolch/service/test/ServiceTest.java b/service/src/test/java/li/strolch/service/test/ServiceTest.java index e6fde9997..6d06ed5f5 100644 --- a/service/src/test/java/li/strolch/service/test/ServiceTest.java +++ b/service/src/test/java/li/strolch/service/test/ServiceTest.java @@ -33,6 +33,7 @@ import li.strolch.service.test.model.GreetingResult; import li.strolch.service.test.model.GreetingService; import li.strolch.service.test.model.GreetingService.GreetingArgument; import li.strolch.service.test.model.TestService; +import li.strolch.utils.dbc.DBC; import org.junit.Test; /** @@ -42,11 +43,11 @@ public class ServiceTest extends AbstractServiceTest { @Test public void shouldFailInvalidCertificate1() { - assertThrows(PrivilegeException.class, () -> { + assertThrows(DBC.DbcException.class, () -> { TestService testService = new TestService(); getServiceHandler().doService( new Certificate(null, null, null, null, null, null, null, null, ZonedDateTime.now(), false, null, - new HashSet<>(), null), testService); + new HashSet<>(), new HashSet<>(), null), testService); }); } @@ -54,7 +55,7 @@ public class ServiceTest extends AbstractServiceTest { public void shouldFailInvalidCertificate2() { TestService testService = new TestService(); Certificate badCert = new Certificate(Usage.ANY, "1", "bob", "Bob", "Brown", UserState.ENABLED, "dsdf", "asd", - ZonedDateTime.now(), false, null, new HashSet<>(), null); + ZonedDateTime.now(), false, null, new HashSet<>(), new HashSet<>(), null); ServiceResult svcResult = getServiceHandler().doService(badCert, testService); assertThat(svcResult.getThrowable(), instanceOf(NotAuthenticatedException.class)); } @@ -67,8 +68,7 @@ public class ServiceTest extends AbstractServiceTest { try { TestService testService = new TestService(); ServiceResult svcResult = getServiceHandler().doService(certificate, testService); - assertThat(svcResult.getMessage(), - containsString("User jill may not perform service TestService")); + assertThat(svcResult.getMessage(), containsString("User jill may not perform service TestService")); assertThat(svcResult.getThrowable(), instanceOf(AccessDeniedException.class)); } finally { runtimeMock.getPrivilegeHandler().invalidate(certificate); diff --git a/service/src/test/resources/svctest/config/PrivilegeGroups.xml b/service/src/test/resources/svctest/config/PrivilegeGroups.xml new file mode 100644 index 000000000..735d8c42e --- /dev/null +++ b/service/src/test/resources/svctest/config/PrivilegeGroups.xml @@ -0,0 +1,8 @@ + + + + + AppGroup + + + \ No newline at end of file diff --git a/service/src/test/resources/svctest/config/PrivilegeRoles.xml b/service/src/test/resources/svctest/config/PrivilegeRoles.xml index 754344715..c50e5c68f 100644 --- a/service/src/test/resources/svctest/config/PrivilegeRoles.xml +++ b/service/src/test/resources/svctest/config/PrivilegeRoles.xml @@ -1,174 +1,183 @@ - - - li.strolch.runtime.privilege.StrolchSystemAction - li.strolch.runtime.privilege.StrolchSystemActionWithResult - li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer - - - true - - - true - + + + li.strolch.runtime.privilege.StrolchSystemAction + li.strolch.runtime.privilege.StrolchSystemActionWithResult + li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer + + + true + + + true + - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + - - - true - - - true - + + + true + + + true + + - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - - Bike - - + + + true + + + true + - - - li.strolch.service.privilege.users.PrivilegeUpdateUserService - li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService - li.strolch.service.privilege.users.PrivilegeSetUserPasswordService - li.strolch.service.privilege.users.PrivilegeSetUserLocaleService - li.strolch.service.privilege.users.PrivilegeRemoveUserService - li.strolch.service.privilege.users.PrivilegeRemoveRoleFromUserService - li.strolch.service.privilege.users.PrivilegeAddUserService - li.strolch.service.privilege.users.PrivilegeAddRoleToUserService - li.strolch.service.privilege.roles.PrivilegeUpdateRoleService - li.strolch.service.privilege.roles.PrivilegeRemoveRoleService - li.strolch.service.privilege.roles.PrivilegeRemovePrivilegeFromRoleService - li.strolch.service.privilege.roles.PrivilegeAddRoleService - li.strolch.service.privilege.roles.PrivilegeAddOrReplacePrivilegeOnRoleService - - - true - - - true - - - true - - - true - - - true - - - true - - - Reload - GetPolicies - Persist - GetCertificates - PersistSessions - - - true - - - SYSTEM - DISABLED - ENABLED - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + Bike + + + + + + li.strolch.service.privilege.users.PrivilegeUpdateUserService + li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService + li.strolch.service.privilege.users.PrivilegeSetUserPasswordService + li.strolch.service.privilege.users.PrivilegeSetUserLocaleService + li.strolch.service.privilege.users.PrivilegeRemoveUserService + li.strolch.service.privilege.users.PrivilegeRemoveRoleFromUserService + li.strolch.service.privilege.users.PrivilegeAddUserService + li.strolch.service.privilege.users.PrivilegeAddRoleToUserService + li.strolch.service.privilege.roles.PrivilegeUpdateRoleService + li.strolch.service.privilege.roles.PrivilegeRemoveRoleService + li.strolch.service.privilege.roles.PrivilegeRemovePrivilegeFromRoleService + li.strolch.service.privilege.roles.PrivilegeAddRoleService + li.strolch.service.privilege.roles.PrivilegeAddOrReplacePrivilegeOnRoleService + + + true + + + true + + + true + + + true + + + true + + + true + + + Reload + GetPolicies + Persist + GetCertificates + PersistSessions + + + true + + + SYSTEM + DISABLED + ENABLED + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + diff --git a/service/src/test/resources/svctest/config/PrivilegeUsers.xml b/service/src/test/resources/svctest/config/PrivilegeUsers.xml index df7c74820..371fe63ce 100644 --- a/service/src/test/resources/svctest/config/PrivilegeUsers.xml +++ b/service/src/test/resources/svctest/config/PrivilegeUsers.xml @@ -1,28 +1,28 @@ - - SYSTEM - - agent - - - - Application - Administrator - ENABLED - en-GB - - AppUser - - - - Application - Administrator - ENABLED - en-GB - - AppUser - PrivilegeAdmin - - + + SYSTEM + + agent + + + + Application + Administrator + ENABLED + en-GB + + AppUser + + + + Application + Administrator + ENABLED + en-GB + + AppUser + PrivilegeAdmin + + diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index cfbdc9faa..288c7f49c 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -37,7 +37,7 @@ import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.InvalidCredentialsException; import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.Certificate; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Usage; import li.strolch.rest.RestfulStrolchComponent; @@ -401,7 +401,7 @@ public class AuthenticationService { loginResult.add("privileges", privArr); for (String name : privilegeContext.getPrivilegeNames()) { - IPrivilege privilege = privilegeContext.getPrivilege(name); + Privilege privilege = privilegeContext.getPrivilege(name); JsonObject privObj = new JsonObject(); privArr.add(privObj); diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/StrolchJobsResource.java b/web-rest/src/main/java/li/strolch/rest/endpoint/StrolchJobsResource.java index 49d4b4a77..336975edd 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/StrolchJobsResource.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/StrolchJobsResource.java @@ -33,7 +33,7 @@ import li.strolch.job.StrolchJob; import li.strolch.job.StrolchJobsHandler; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.model.Certificate; -import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; @@ -76,7 +76,7 @@ public class StrolchJobsResource { if (ctx.hasRole(ROLE_STROLCH_ADMIN)) return true; - IPrivilege privilege = ctx.getPrivilege(StrolchJob.class.getName()); + Privilege privilege = ctx.getPrivilege(StrolchJob.class.getName()); return privilege.isAllAllowed() || privilege.getAllowList().contains(job.getClass().getName()); }) // .sorted(comparing(StrolchJob::getName)) // From 6c09715b44c175dfe94044702456717d05ccdc5f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 26 Sep 2023 09:51:00 +0200 Subject: [PATCH 15/39] [Minor] Better message on forbidden access --- .../rest/StrolchRestfulExceptionMapper.java | 6 +-- .../li/strolch/rest/helper/ResponseUtil.java | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/web-rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java b/web-rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java index a28d3a790..df0985f75 100644 --- a/web-rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java +++ b/web-rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java @@ -43,14 +43,14 @@ public class StrolchRestfulExceptionMapper implements ExceptionMapper if (ex instanceof NotFoundException) return ResponseUtil.toResponse(Status.NOT_FOUND, ex); - if (ex instanceof StrolchAccessDeniedException e) - return ResponseUtil.toResponse(Status.FORBIDDEN, e.getI18n()); - if (ex instanceof StrolchNotAuthenticatedException e) { logger.error("User tried to access resource, but was not authenticated: " + ex.getMessage()); return Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build(); } + if (ex instanceof StrolchAccessDeniedException e) + return ResponseUtil.toResponse(Status.FORBIDDEN, e.getI18n()); + return ResponseUtil.toResponse(ex); } } \ No newline at end of file diff --git a/web-rest/src/main/java/li/strolch/rest/helper/ResponseUtil.java b/web-rest/src/main/java/li/strolch/rest/helper/ResponseUtil.java index e3a6a5072..6b4c85771 100644 --- a/web-rest/src/main/java/li/strolch/rest/helper/ResponseUtil.java +++ b/web-rest/src/main/java/li/strolch/rest/helper/ResponseUtil.java @@ -1,21 +1,14 @@ package li.strolch.rest.helper; -import static li.strolch.rest.StrolchRestfulConstants.*; -import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses; -import static li.strolch.utils.helper.ExceptionHelper.getRootCause; - -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; import li.strolch.exception.StrolchElementNotFoundException; +import li.strolch.exception.StrolchNotAuthenticatedException; import li.strolch.exception.StrolchUserMessageException; import li.strolch.model.i18n.I18nMessageToJsonVisitor; import li.strolch.privilege.base.AccessDeniedException; @@ -27,6 +20,14 @@ import li.strolch.utils.I18nMessage; import li.strolch.utils.collections.Paging; import li.strolch.utils.helper.StringHelper; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static li.strolch.rest.StrolchRestfulConstants.*; +import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses; +import static li.strolch.utils.helper.ExceptionHelper.getRootCause; + /** * Created by eitch on 29.08.16. */ @@ -155,17 +156,17 @@ public class ResponseUtil { } public static Response toResponse(Throwable t) { - if (t instanceof AccessDeniedException) { - return ResponseUtil.toResponse(Status.FORBIDDEN, t); - } else if (t instanceof StrolchElementNotFoundException) { - return ResponseUtil.toResponse(Status.NOT_FOUND, t); - } else if (t instanceof PrivilegeModelException) { - return ResponseUtil.toResponse(Status.INTERNAL_SERVER_ERROR, t); - } else if (t instanceof PrivilegeException) { + if (t instanceof StrolchNotAuthenticatedException) return ResponseUtil.toResponse(Status.UNAUTHORIZED, t); - } else { - return toResponse(Status.INTERNAL_SERVER_ERROR, t); - } + if (t instanceof AccessDeniedException) + return ResponseUtil.toResponse(Status.FORBIDDEN, t); + if (t instanceof StrolchElementNotFoundException) + return ResponseUtil.toResponse(Status.NOT_FOUND, t); + if (t instanceof PrivilegeModelException) + return ResponseUtil.toResponse(Status.INTERNAL_SERVER_ERROR, t); + if (t instanceof PrivilegeException) + return ResponseUtil.toResponse(Status.FORBIDDEN, t); + return toResponse(Status.INTERNAL_SERVER_ERROR, t); } public static Response toResponse(Status status, String msg) { @@ -183,8 +184,8 @@ public class ResponseUtil { response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor())); } else { Throwable rootCause = getRootCause(t); - if (rootCause instanceof StrolchUserMessageException ex - && ((StrolchUserMessageException) rootCause).hasI18n()) { + if (rootCause instanceof StrolchUserMessageException ex && + ((StrolchUserMessageException) rootCause).hasI18n()) { response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor())); } } From d8402bad61ba8ec2b2551503dd31db065b2d9824 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 12:15:26 +0200 Subject: [PATCH 16/39] [New] Allow to add an additionalFilter in LDAP search --- .../handler/BaseLdapPrivilegeHandler.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 1fc8c6c35..5e8f838af 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -18,12 +18,16 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; + public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(BaseLdapPrivilegeHandler.class); private String providerUrl; private String searchBase; + private String additionalFilter; private String domain; @Override @@ -37,8 +41,12 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { this.providerUrl = parameterMap.get("providerUrl"); this.searchBase = parameterMap.get("searchBase"); + this.additionalFilter = trimOrEmpty(parameterMap.get("additionalFilter")); this.domain = parameterMap.get("domain"); - + if (isNotEmpty(this.domain) && this.domain.startsWith("@")) { + logger.warn("Remove the @ symbol from the domain property! Added automatically."); + this.domain = this.domain.substring(1); + } } @Override @@ -49,6 +57,8 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { if (internalUser != null && internalUser.getUserState() != UserState.REMOTE) return super.checkCredentialsAndUserState(username, password); + String userPrincipalName = username + "@" + this.domain; + // Set up the environment for creating the initial context Hashtable env = new Hashtable<>(); @@ -57,7 +67,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { // Authenticate env.put(Context.SECURITY_AUTHENTICATION, "simple"); - env.put(Context.SECURITY_PRINCIPAL, username + this.domain); + env.put(Context.SECURITY_PRINCIPAL, userPrincipalName); env.put(Context.SECURITY_CREDENTIALS, new String(password)); logger.info("User {} tries to login on ldap {}", username + this.domain, this.providerUrl); @@ -73,21 +83,23 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { //Specify the search scope searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - String searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=" + username + - this.domain + "))"; + String searchFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s)%s)".formatted( + username, this.additionalFilter); // Search for objects using the filter NamingEnumeration answer = ctx.search(this.searchBase, searchFilter, searchCtls); if (!answer.hasMore()) { - logger.warn("No LDAP data retrieved using userPrincipalName, trying with sAMAccountName..."); - searchFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=" + username + "))"; + logger.warn("No LDAP data retrieved using sAMAccountName, trying with userPrincipalName..."); + searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=%s)%s)".formatted( + userPrincipalName, this.additionalFilter); answer = ctx.search(this.searchBase, searchFilter, searchCtls); if (!answer.hasMore()) - throw new AccessDeniedException("Could not login with user: " + username + this.domain + - " on Ldap: no LDAP Data, for either userPrincipalName or sAMAccountName"); + throw new AccessDeniedException("Could not login user: " + username + + " on Ldap: no LDAP Data, for either sAMAccountName or userPrincipalName searches. Domain used is " + + this.domain); } SearchResult searchResult = answer.next(); From 6f729554c85bc2f3814e8ed526833ee90f8943f0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 12:30:42 +0200 Subject: [PATCH 17/39] [New] Allow to add an additionalFilter in LDAP search --- .../handler/BaseLdapPrivilegeHandler.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 5e8f838af..2dfddc305 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -40,12 +40,21 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { userChallengeHandler, ssoHandler, policyMap); this.providerUrl = parameterMap.get("providerUrl"); + logger.info("providerUrl: " + this.providerUrl); this.searchBase = parameterMap.get("searchBase"); + logger.info("searchBase: " + this.searchBase); this.additionalFilter = trimOrEmpty(parameterMap.get("additionalFilter")); + if (isNotEmpty(this.additionalFilter)) + logger.info("additionalFilter: " + this.additionalFilter); this.domain = parameterMap.get("domain"); - if (isNotEmpty(this.domain) && this.domain.startsWith("@")) { - logger.warn("Remove the @ symbol from the domain property! Added automatically."); - this.domain = this.domain.substring(1); + if (isNotEmpty(this.domain)) { + if (this.domain.startsWith("@")) { + logger.warn( + "Remove the @ symbol from the domain property! Will be added automatically where required."); + this.domain = this.domain.substring(1); + } + + logger.info("domain: " + this.domain); } } @@ -70,7 +79,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { env.put(Context.SECURITY_PRINCIPAL, userPrincipalName); env.put(Context.SECURITY_CREDENTIALS, new String(password)); - logger.info("User {} tries to login on ldap {}", username + this.domain, this.providerUrl); + logger.info("User {} tries to login on ldap {}", username, this.providerUrl); // Create the initial context DirContext ctx = null; @@ -105,7 +114,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { SearchResult searchResult = answer.next(); if (answer.hasMore()) throw new AccessDeniedException( - "Could not login with user: " + username + this.domain + " on Ldap: Multiple LDAP Data"); + "Could not login with user: " + username + " on Ldap: Multiple LDAP Data"); User user = buildUserFromSearchResult(username, searchResult); From 6b6a61ab623da781769d3e848a49763d0fd96719 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 13:43:46 +0200 Subject: [PATCH 18/39] [Major] Privilege refactorings, remove unused services and actions --- .../json/PrivilegeElementFromJsonVisitor.java | 5 +- .../json/PrivilegeElementToJsonVisitor.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 34 +- .../handler/PrivilegeCrudHandler.java | 428 +++------------- .../privilege/handler/PrivilegeHandler.java | 474 +++++------------- .../strolch/privilege/model/PrivilegeRep.java | 38 +- .../li/strolch/privilege/model/RoleRep.java | 60 ++- .../li/strolch/privilege/model/UserRep.java | 53 +- .../privilege/model/internal/Role.java | 15 +- .../strolch/privilege/test/PrivilegeTest.java | 75 +-- .../li/strolch/privilege/test/XmlTest.java | 8 +- .../test/resources/config/PrivilegeGroups.xml | 1 + ...geAddOrReplacePrivilegeOnRoleArgument.java | 24 - ...egeAddOrReplacePrivilegeOnRoleService.java | 64 --- ...vilegeRemovePrivilegeFromRoleArgument.java | 23 - ...ivilegeRemovePrivilegeFromRoleService.java | 64 --- .../users/PrivilegeAddRoleToUserService.java | 64 --- .../PrivilegeRemoveRoleFromUserService.java | 63 --- .../PrivilegeUpdateUserRolesService.java | 95 ---- .../users/PrivilegeUpdateUserService.java | 2 +- .../rest/endpoint/PrivilegeRolesService.java | 53 +- .../rest/endpoint/PrivilegeUsersService.java | 83 +-- 22 files changed, 392 insertions(+), 1336 deletions(-) delete mode 100644 service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleArgument.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java delete mode 100644 service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserRolesService.java diff --git a/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java b/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java index 66ecf363f..810194a04 100644 --- a/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java +++ b/model/src/main/java/li/strolch/model/json/PrivilegeElementFromJsonVisitor.java @@ -32,11 +32,12 @@ public class PrivilegeElementFromJsonVisitor { String name = nameE == null ? null : nameE.getAsString().trim(); - List privileges = new ArrayList<>(); + Map privileges = new HashMap<>(); if (privilegesE != null) { JsonArray privilegesArr = privilegesE.getAsJsonArray(); for (JsonElement privilegeE : privilegesArr) { - privileges.add(privilegeRepFromJson(privilegeE.getAsJsonObject())); + PrivilegeRep privilegeRep = privilegeRepFromJson(privilegeE.getAsJsonObject()); + privileges.put(privilegeRep.getName(), privilegeRep); } } diff --git a/model/src/main/java/li/strolch/model/json/PrivilegeElementToJsonVisitor.java b/model/src/main/java/li/strolch/model/json/PrivilegeElementToJsonVisitor.java index 36deed256..617d57c82 100644 --- a/model/src/main/java/li/strolch/model/json/PrivilegeElementToJsonVisitor.java +++ b/model/src/main/java/li/strolch/model/json/PrivilegeElementToJsonVisitor.java @@ -53,7 +53,7 @@ public class PrivilegeElementToJsonVisitor implements PrivilegeElementVisitor privilegesJ.add(p.accept(this))); + roleRep.getPrivileges().values().forEach(p -> privilegesJ.add(p.accept(this))); jsonObject.add("privileges", privilegesJ); return jsonObject; diff --git a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index c0dbb3e01..ff42c918d 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -240,15 +240,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public UserRep replaceUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { return this.lockingHandler.lockedExecuteWithResult(userRep.getUsername(), - () -> crudHandler.replaceUser(certificate, userRep, password)); - } - - @Override - public UserRep updateUser(Certificate certificate, UserRep userRep) throws PrivilegeException { - return this.lockingHandler.lockedExecuteWithResult(userRep.getUsername(), - () -> crudHandler.updateUser(certificate, userRep)); + () -> crudHandler.updateUser(certificate, userRep, password)); } @Override @@ -257,18 +251,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { () -> crudHandler.removeUser(certificate, username)); } - @Override - public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { - return this.lockingHandler.lockedExecuteWithResult(username, - () -> crudHandler.addRoleToUser(certificate, username, roleName)); - } - - @Override - public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { - return this.lockingHandler.lockedExecuteWithResult(username, - () -> crudHandler.removeRoleFromUser(certificate, username, roleName)); - } - @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { return this.lockingHandler.lockedExecuteWithResult(username, @@ -309,18 +291,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { () -> crudHandler.removeRole(certificate, roleName)); } - @Override - public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { - return this.lockingHandler.lockedExecuteWithResult(roleName, - () -> crudHandler.addOrReplacePrivilegeOnRole(certificate, roleName, privilegeRep)); - } - - @Override - public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - return this.lockingHandler.lockedExecuteWithResult(roleName, - () -> crudHandler.removePrivilegeFromRole(certificate, roleName, privilegeName)); - } - void invalidSessionsFor(User user) { List contexts = new ArrayList<>(this.privilegeContextMap.values()); for (PrivilegeContext ctx : contexts) { diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java index 2ea31ff21..c7929108c 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java @@ -10,7 +10,6 @@ import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; import li.strolch.privilege.policy.PrivilegePolicy; import li.strolch.utils.collections.Tuple; -import li.strolch.utils.helper.StringHelper; import java.text.MessageFormat; import java.time.ZonedDateTime; @@ -19,6 +18,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static java.text.MessageFormat.format; +import static li.strolch.utils.helper.StringHelper.*; public class PrivilegeCrudHandler { @@ -146,16 +146,16 @@ public class PrivilegeCrudHandler { boolean propertySelected; // userId - userIdSelected = StringHelper.isEmpty(selUserId) || selUserId.equals(user.getUserId()); + userIdSelected = isEmpty(selUserId) || selUserId.equals(user.getUserId()); // username - usernameSelected = StringHelper.isEmpty(selUsername) || selUsername.equals(user.getUsername()); + usernameSelected = isEmpty(selUsername) || selUsername.equals(user.getUsername()); // firstname - firstNameSelected = StringHelper.isEmpty(selFirstName) || selFirstName.equals(user.getFirstname()); + firstNameSelected = isEmpty(selFirstName) || selFirstName.equals(user.getFirstname()); // lastname - lastNameSelected = StringHelper.isEmpty(selLastName) || selLastName.equals(user.getLastname()); + lastNameSelected = isEmpty(selLastName) || selLastName.equals(user.getLastname()); // user state userStateSelected = selUserState == null || selUserState.equals(user.getUserState()); @@ -248,13 +248,13 @@ public class PrivilegeCrudHandler { prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER); // make sure userId is not set - if (StringHelper.isNotEmpty(userRepParam.getUserId())) + if (isNotEmpty(userRepParam.getUserId())) throw new PrivilegeModelException("UserId can not be set when adding a new user!"); UserRep userRep = userRepParam.getCopy(); // set userId - userRep.setUserId(StringHelper.getUniqueId()); + userRep.setUserId(getUniqueId()); // first validate user userRep.validate(); @@ -327,11 +327,11 @@ public class PrivilegeCrudHandler { // add user // make sure userId is not set - if (StringHelper.isNotEmpty(userRep.getUserId())) + if (isNotEmpty(userRep.getUserId())) throw new PrivilegeModelException("UserId can not be set when adding a new user!"); // set userId - userRep.setUserId(StringHelper.getUniqueId()); + userRep.setUserId(getUniqueId()); // first validate user userRep.validate(); @@ -383,80 +383,15 @@ public class PrivilegeCrudHandler { DefaultPrivilegeHandler.logger.info("Updated " + toUpdate.size() + " users"); } - public UserRep replaceUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { - try { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER); - - // first validate user - userRep.validate(); - - validateGroupsExist(userRep); - validateRolesExist(userRep); - - // validate user exists - User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); - if (existingUser == null) { - String msg = "User {0} can not be replaced as it does not exist!"; - throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); - } - - // validate same userId - if (!existingUser.getUserId().equals(userRep.getUserId())) { - String msg = "UserId of existing user {0} does not match userRep {1}"; - msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); - throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); - } - - UserHistory history = existingUser.getHistory(); - PasswordCrypt passwordCrypt = null; - if (password != null) { - - // validate password meets basic requirements - privilegeHandler.validatePassword(certificate.getLocale(), password); - - // get new salt for user - byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); - - // hash password - passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); - - history = history.withLastPasswordChange(ZonedDateTime.now()); - } - - User newUser = createUser(userRep, history, passwordCrypt, existingUser.isPasswordChangeRequested()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // validate this user may modify this user - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, - new Tuple(existingUser, newUser))); - - // delegate to persistence handler - this.persistenceHandler.replaceUser(newUser); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Replaced user " + newUser.getUsername()); - - return newUser.asUserRep(); - - } finally { - clearPassword(password); - } - } - private void assertNoPrivilegeConflict(User user) { - if (this.privilegeConflictResolution.isStrict()) { - Map privilegeNames = new HashMap<>(); - List conflicts = detectPrivilegeConflicts(privilegeNames, user); - if (!conflicts.isEmpty()) { - String msg = String.join("\n", conflicts); - throw new PrivilegeModelException(msg); - } - } + if (!this.privilegeConflictResolution.isStrict()) + return; + Map privilegeNames = new HashMap<>(); + List conflicts = detectPrivilegeConflicts(privilegeNames, user); + if (conflicts.isEmpty()) + return; + String msg = String.join("\n", conflicts); + throw new PrivilegeModelException(msg); } private void assertNoPrivilegeConflict(Role role) { @@ -507,7 +442,7 @@ public class PrivilegeCrudHandler { private void validateGroupsExist(UserRep userRep) { for (String group : userRep.getGroups()) { if (this.persistenceHandler.getGroup(group) == null) { - String msg = "Can not add user {0} as group {1} does not exist!"; + String msg = "Can not add/update user {0} as group {1} does not exist!"; msg = MessageFormat.format(msg, userRep.getUsername(), group); throw new PrivilegeModelException(msg); } @@ -517,7 +452,7 @@ public class PrivilegeCrudHandler { private void validateRolesExist(UserRep userRep) { for (String role : userRep.getRoles()) { if (this.persistenceHandler.getRole(role) == null) { - String msg = "Can not add user {0} as role {1} does not exist!"; + String msg = "Can not add/update user {0} as role {1} does not exist!"; msg = MessageFormat.format(msg, userRep.getUsername(), role); throw new PrivilegeModelException(msg); } @@ -539,72 +474,71 @@ public class PrivilegeCrudHandler { passwordChangeRequested, history); } - public UserRep updateUser(Certificate certificate, UserRep userRep) throws PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException { + try { - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); + prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER); - // get existing user - User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); - if (existingUser == null) - throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); + // first validate user + userRep.validate(); - // if nothing to do, then stop - if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname()) && - userRep.getLocale() == null && (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { - throw new PrivilegeModelException( - MessageFormat.format("All updateable fields are empty for update of user {0}", - userRep.getUsername())); + validateGroupsExist(userRep); + validateRolesExist(userRep); + + // validate user exists + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { + String msg = "User {0} does not exist!"; + throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); + } + + // validate same userId + if (!existingUser.getUserId().equals(userRep.getUserId())) { + String msg = "UserId of existing user {0} does not match userRep {1}"; + msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); + throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername())); + } + + UserHistory history = existingUser.getHistory(); + PasswordCrypt passwordCrypt; + if (password == null) { + passwordCrypt = existingUser.getPasswordCrypt(); + } else { + + // validate password meets basic requirements + privilegeHandler.validatePassword(certificate.getLocale(), password); + + // get new salt for user + byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + + // hash password + passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + + history = history.withLastPasswordChange(ZonedDateTime.now()); + } + + User newUser = createUser(userRep, history, passwordCrypt, existingUser.isPasswordChangeRequested()); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, + new Tuple(existingUser, newUser))); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(newUser); + this.privilegeHandler.persistModelAsync(); + + DefaultPrivilegeHandler.logger.info("Replaced user " + newUser.getUsername()); + + return newUser.asUserRep(); + + } finally { + clearPassword(password); } - - String userId = existingUser.getUserId(); - String username = existingUser.getUsername(); - PasswordCrypt passwordCrypt = existingUser.getPasswordCrypt(); - String firstName = existingUser.getFirstname(); - String lastName = existingUser.getLastname(); - UserState userState = existingUser.getUserState(); - Set groups = existingUser.getGroups(); - Set roles = existingUser.getRoles(); - Locale locale = existingUser.getLocale(); - Map propertyMap = existingUser.getProperties(); - - // get updated fields - if (StringHelper.isNotEmpty(userRep.getFirstname())) - firstName = userRep.getFirstname(); - if (StringHelper.isNotEmpty(userRep.getLastname())) - lastName = userRep.getLastname(); - if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) - propertyMap = userRep.getProperties(); - - if (userRep.getLocale() != null) - locale = userRep.getLocale(); - if (userRep.getUserState() != null) - userState = userRep.getUserState(); - if (userRep.getRoles() != null && !userRep.getRoles().equals(roles)) - roles = userRep.getRoles(); - - // create new user - User newUser = new User(userId, username, passwordCrypt, firstName, lastName, userState, groups, roles, locale, - propertyMap, existingUser.isPasswordChangeRequested(), existingUser.getHistory()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // validate this user may modify this user - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_USER, - new Tuple(existingUser, newUser))); - - // delegate to persistence handler - this.persistenceHandler.replaceUser(newUser); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Updated user " + newUser.getUsername()); - - // update any existing sessions for this user - this.privilegeHandler.updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); } public UserRep removeUser(Certificate certificate, String username) { @@ -634,100 +568,6 @@ public class PrivilegeCrudHandler { return existingUser.asUserRep(); } - public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER); - - // get user - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); - - // validate that this user may add this role to this user - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER, - new Tuple(existingUser, roleName))); - - // check that user not already has role - Set currentRoles = existingUser.getRoles(); - if (currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); - throw new PrivilegeModelException(msg); - } - - // validate that the role exists - if (this.persistenceHandler.getRole(roleName) == null) { - String msg = MessageFormat.format("Role {0} does not exist!", roleName); - throw new PrivilegeModelException(msg); - } - - // create new user - Set newRoles = new HashSet<>(currentRoles); - newRoles.add(roleName); - - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), - existingUser.getGroups(), newRoles, existingUser.getLocale(), existingUser.getProperties(), - existingUser.isPasswordChangeRequested(), existingUser.getHistory()); - - // detect privilege conflicts - assertNoPrivilegeConflict(newUser); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Added role " + roleName + " to " + newUser.getUsername()); - - // update any existing sessions for this user - this.privilegeHandler.updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); - } - - public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); - - // get User - User existingUser = this.persistenceHandler.getUser(username); - if (existingUser == null) - throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); - - // validate that this user may remove this role from this user - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER, - new Tuple(existingUser, roleName))); - - // ignore if user does not have role - Set currentRoles = existingUser.getRoles(); - if (!currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} does not have role {1}", existingUser.getUsername(), roleName); - throw new PrivilegeModelException(msg); - } - - // create new user - Set newRoles = new HashSet<>(currentRoles); - newRoles.remove(roleName); - User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(), - existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), - existingUser.getGroups(), newRoles, existingUser.getLocale(), existingUser.getProperties(), - existingUser.isPasswordChangeRequested(), existingUser.getHistory()); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Removed role " + roleName + " from " + newUser.getUsername()); - - // update any existing sessions for this user - this.privilegeHandler.updateExistingSessionsForUser(newUser); - - return newUser.asUserRep(); - } - public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { // validate user actually has this type of privilege @@ -792,7 +632,7 @@ public class PrivilegeCrudHandler { public void setUserPassword(Certificate certificate, String username, char[] password) { // we don't want the user to worry about whitespace - username = StringHelper.trimOrEmpty(username); + username = trimOrEmpty(username); try { @@ -994,112 +834,6 @@ public class PrivilegeCrudHandler { return existingRole.asRoleRep(); } - public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); - - // validate PrivilegeRep - privilegeRep.validate(); - - // get role - Role existingRole = this.persistenceHandler.getRole(roleName); - if (existingRole == null) { - String msg = MessageFormat.format("Role {0} does not exist!", roleName); - throw new PrivilegeModelException(msg); - } - - // validate that policy exists if needed - String policy = privilegeRep.getPolicy(); - if (policy != null && !this.policyMap.containsKey(policy)) { - String msg = "Policy {0} for Privilege {1} does not exist"; - msg = MessageFormat.format(msg, policy, privilegeRep.getName()); - throw new PrivilegeModelException(msg); - } - - // create new role with the additional privilege - Privilege newPrivilege = Privilege.of(privilegeRep); - - // copy existing privileges - Set existingPrivilegeNames = existingRole.getPrivilegeNames(); - Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); - for (String name : existingPrivilegeNames) { - Privilege privilege = existingRole.getPrivilege(name); - privilegeMap.put(name, privilege); - } - - // add new one - privilegeMap.put(newPrivilege.getName(), newPrivilege); - - // create new role - Role newRole = new Role(existingRole.getName(), privilegeMap); - - // detect privilege conflicts - assertNoPrivilegeConflict(newRole); - - // validate that this user may modify this role - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE, - new Tuple(existingRole, newRole))); - - // delegate role replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Added/replaced privilege " + privilegeRep.getName() + " to " + roleName); - - // update any existing certificates with new role - this.privilegeHandler.updateExistingSessionsWithNewRole(newRole); - - return newRole.asRoleRep(); - } - - public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); - prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); - - // get role - Role existingRole = this.persistenceHandler.getRole(roleName); - if (existingRole == null) { - throw new PrivilegeModelException(MessageFormat.format("Role {0} does not exist!", roleName)); - } - - // ignore if role does not have privilege - if (!existingRole.hasPrivilege(privilegeName)) { - String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); - throw new PrivilegeModelException(msg); - } - - // create new set of privileges with out the to removed privilege - Set privilegeNames = existingRole.getPrivilegeNames(); - Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); - for (String name : privilegeNames) { - Privilege privilege = existingRole.getPrivilege(name); - if (!privilege.getName().equals(privilegeName)) - newPrivileges.put(privilege.getName(), privilege); - } - - // create new role - Role newRole = new Role(existingRole.getName(), newPrivileges); - - // validate that this user may modify this role - prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE, - new Tuple(existingRole, newRole))); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - this.privilegeHandler.persistModelAsync(); - - DefaultPrivilegeHandler.logger.info("Removed privilege " + privilegeName + " from " + roleName); - - // update any existing certificates with new role - this.privilegeHandler.updateExistingSessionsWithNewRole(newRole); - - return newRole.asRoleRep(); - } - /** * Validates that the policies which are not null on the privileges of the role exist * diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java index 7c639d60a..6c8fee15e 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java @@ -15,16 +15,16 @@ */ package li.strolch.privilege.handler; -import java.util.List; -import java.util.Locale; -import java.util.Map; - import li.strolch.privilege.base.*; import li.strolch.privilege.model.*; import li.strolch.privilege.model.internal.Role; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.policy.PrivilegePolicy; +import java.util.List; +import java.util.Locale; +import java.util.Map; + /** * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed * methods to access Privilege data model objects, modify them and validate if users or roles have privileges to perform @@ -212,10 +212,8 @@ public interface PrivilegeHandler { /** * Returns a {@link UserRep} for the given username * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the name of the {@link UserRep} to return + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the name of the {@link UserRep} to return * * @return the {@link UserRep} for the given username, or null if it was not found */ @@ -224,10 +222,8 @@ public interface PrivilegeHandler { /** * Returns a {@link RoleRep} for the given roleName * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleName - * the name of the {@link RoleRep} to return + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName the name of the {@link RoleRep} to return * * @return the {@link RoleRep} for the given roleName, or null if it was not found */ @@ -236,8 +232,7 @@ public interface PrivilegeHandler { /** * Returns the map of {@link PrivilegePolicy} definitions * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action * * @return the map of {@link PrivilegePolicy} definitions */ @@ -246,8 +241,7 @@ public interface PrivilegeHandler { /** * Returns the list of {@link Certificate Certificates} * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action * * @return the list of {@link Certificate Certificates} */ @@ -256,8 +250,7 @@ public interface PrivilegeHandler { /** * Returns all {@link RoleRep RoleReps} * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action * * @return the list of {@link RoleRep RoleReps} */ @@ -266,8 +259,7 @@ public interface PrivilegeHandler { /** * Returns all {@link UserRep UserReps} * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action * * @return the list of {@link UserRep UserReps} */ @@ -277,10 +269,8 @@ public interface PrivilegeHandler { * Method to query {@link UserRep} which meet the criteria set in the given {@link UserRep}. Null fields mean the * fields are irrelevant. * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param selectorRep - * the {@link UserRep} to use as criteria selection + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param selectorRep the {@link UserRep} to use as criteria selection * * @return a list of {@link UserRep}s which fit the given criteria */ @@ -289,72 +279,30 @@ public interface PrivilegeHandler { /** * Removes the user with the given username * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the user to remove + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the username of the user to remove * * @return the {@link UserRep} of the user removed, or null if the user did not exist * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate */ UserRep removeUser(Certificate certificate, String username) throws PrivilegeException; - /** - * Removes the role with the given roleName from the user with the given username - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the user from which the role is to be removed - * @param roleName - * the roleName of the role to remove from the user - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) throws PrivilegeException; - /** * Removes the role with the given roleName * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleName - * the roleName of the role to remove + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName the roleName of the role to remove * * @return the {@link RoleRep} of the role removed, or null if the role did not exist * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or the role is still in use by a user + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate or the role is still in use by a + * user */ RoleRep removeRole(Certificate certificate, String roleName) throws PrivilegeException; - /** - * Removes the privilege with the given privilegeName from the role with the given roleName - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleName - * the roleName of the role from which the privilege is to be removed - * @param privilegeName - * the privilegeName of the privilege to remove from the role - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) - throws PrivilegeException; - /** *

* Adds a new user with the information from this {@link UserRep} @@ -365,18 +313,14 @@ public interface PrivilegeHandler { * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(Locale, char[])} *

* - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param userRep - * the {@link UserRep} containing the information to create the new {@link User} - * @param password - * the password of the new user. If the password is null, then this is accepted but the user can not login, - * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(Locale, char[])} + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep the {@link UserRep} containing the information to create the new {@link User} + * @param password the password of the new user. If the password is null, then this is accepted but the user can + * not login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(Locale, char[])} * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or the user already exists + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate or the user already exists */ UserRep addUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException; @@ -384,10 +328,8 @@ public interface PrivilegeHandler { * Allows the bulk adding or updating of users. If the user exists, the user's history and password is kept, * otherwise the user is created without a password * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param userReps - * the list of users to add or update + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param userReps the list of users to add or update */ void addOrUpdateUsers(Certificate certificate, List userReps) throws PrivilegeException; @@ -410,108 +352,39 @@ public interface PrivilegeHandler { * Any other fields will be ignored *

* - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param userRep - * the {@link UserRep} with the fields set to their new values + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep the {@link UserRep} with the fields set to their new values + * @param password the password of the new user. If the password is null, then this is accepted but the user can + * not login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(Locale, char[])} * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the user does not exist + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate or if the user does not exist */ - UserRep updateUser(Certificate certificate, UserRep userRep) throws PrivilegeException; - - /** - *

- * Replaces the existing user with the information from this {@link UserRep} if the user already exists - *

- * - *

- * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet - * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(Locale, char[])} - *

- * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param userRep - * the {@link UserRep} containing the information to replace the existing {@link User} - * @param password - * the password of the new user. If the password is null, then this is accepted but the user can not login, - * otherwise the password must be validated against {@link PrivilegeHandler#validatePassword(Locale, char[])} - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the user does not exist - */ - UserRep replaceUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException; + UserRep updateUser(Certificate certificate, UserRep userRep, char[] password) throws PrivilegeException; /** * Adds a new role with the information from this {@link RoleRep} * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleRep - * the {@link RoleRep} containing the information to create the new {@link Role} + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param roleRep the {@link RoleRep} containing the information to create the new {@link Role} * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the role already exists + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate or if the role already exists */ RoleRep addRole(Certificate certificate, RoleRep roleRep) throws PrivilegeException; /** * Replaces the existing role with the information from this {@link RoleRep} * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleRep - * the {@link RoleRep} containing the information to replace the existing {@link Role} + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param roleRep the {@link RoleRep} containing the information to replace the existing {@link Role} * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the role does not exist + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate or if the role does not exist */ RoleRep replaceRole(Certificate certificate, RoleRep roleRep) throws PrivilegeException; - /** - * Adds the role with the given roleName to the {@link User} with the given username - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} to which the role should be added - * @param roleName - * the roleName of the {@link Role} which should be added to the {@link User} - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the role does not exist - */ - UserRep addRoleToUser(Certificate certificate, String username, String roleName) throws PrivilegeException; - - /** - * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName or replaces it, if it already exists - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param roleName - * the roleName of the {@link Role} to which the privilege should be added - * @param privilegeRep - * the representation of the {@link Privilege} which should be added or replaced on the {@link Role} - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or the role does not exist - */ - RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) - throws PrivilegeException; - /** *

* Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} @@ -523,19 +396,14 @@ public interface PrivilegeHandler { * It should be possible for a user to change their own password *

* - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} for which the password is to be changed - * @param password - * the new password for this user. If the password is null, then the {@link User} can not login anymore. Otherwise - * the password must meet the requirements of the implementation under - * {@link PrivilegeHandler#validatePassword(Locale, char[])} + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the username of the {@link User} for which the password is to be changed + * @param password the new password for this user. If the password is null, then the {@link User} can not login + * anymore. Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(Locale, char[])} * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate */ void setUserPassword(Certificate certificate, String username, char[] password) throws PrivilegeException; @@ -544,105 +412,79 @@ public interface PrivilegeHandler { * Requires the given user to change their password after next login *

* - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} for which the password change is requested + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the username of the {@link User} for which the password change is requested * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate */ void requirePasswordChange(Certificate certificate, String username) throws PrivilegeException; /** * Changes the {@link UserState} of the user * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} for which the {@link UserState} is to be changed - * @param state - * the new state for the user + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the username of the {@link User} for which the {@link UserState} is to be changed + * @param state the new state for the user * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate */ UserRep setUserState(Certificate certificate, String username, UserState state) throws PrivilegeException; /** * Changes the {@link Locale} of the user * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} for which the {@link Locale} is to be changed - * @param locale - * the new {@link Locale} for the user + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param username the username of the {@link User} for which the {@link Locale} is to be changed + * @param locale the new {@link Locale} for the user * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate + * @throws AccessDeniedException if the user for this certificate may not perform the action + * @throws PrivilegeException if there is anything wrong with this certificate */ UserRep setUserLocale(Certificate certificate, String username, Locale locale) throws PrivilegeException; /** * Initiate a password reset challenge for the given username * - * @param usage - * the usage for which the challenge is requested - * @param username - * the username of the user to initiate the challenge for + * @param usage the usage for which the challenge is requested + * @param username the username of the user to initiate the challenge for */ void initiateChallengeFor(Usage usage, String username); /** * Initiate a password reset challenge for the given username * - * @param usage - * the usage for which the challenge is requested - * @param username - * the username of the user to initiate the challenge for - * @param source - * the source of the challenge + * @param usage the usage for which the challenge is requested + * @param username the username of the user to initiate the challenge for + * @param source the source of the challenge */ void initiateChallengeFor(Usage usage, String username, String source); /** * Validate the response of a challenge for the given username * - * @param username - * the username of the user for which the challenge is to be validated - * @param challenge - * the challenge from the user + * @param username the username of the user for which the challenge is to be validated + * @param challenge the challenge from the user * * @return certificate with which the user can access the system with the {@link Usage} set to the value from the * initiated challenge * - * @throws PrivilegeException - * if anything goes wrong + * @throws PrivilegeException if anything goes wrong */ Certificate validateChallenge(String username, String challenge) throws PrivilegeException; /** * Validate the response of a challenge for the given username * - * @param username - * the username of the user for which the challenge is to be validated - * @param challenge - * the challenge from the user - * @param source - * the source of the challenge validation + * @param username the username of the user for which the challenge is to be validated + * @param challenge the challenge from the user + * @param source the source of the challenge validation * * @return certificate with which the user can access the system with the {@link Usage} set to the value from the * initiated challenge * - * @throws PrivilegeException - * if anything goes wrong + * @throws PrivilegeException if anything goes wrong */ Certificate validateChallenge(String username, String challenge, String source) throws PrivilegeException; @@ -650,18 +492,14 @@ public interface PrivilegeHandler { * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns * a {@link Certificate} with which this user may then perform actions * - * @param username - * the username of the {@link User} which is registered in the {@link PersistenceHandler} - * @param password - * the password with which this user is to be authenticated. Null passwords are not accepted and they must meet - * the requirements of the {@link #validatePassword(Locale, char[])}-method - * @param keepAlive - * should this session be kept alive + * @param username the username of the {@link User} which is registered in the {@link PersistenceHandler} + * @param password the password with which this user is to be authenticated. Null passwords are not accepted and + * they must meet the requirements of the {@link #validatePassword(Locale, char[])}-method + * @param keepAlive should this session be kept alive * * @return a {@link Certificate} with which this user may then perform actions * - * @throws AccessDeniedException - * if the user credentials are not valid + * @throws AccessDeniedException if the user credentials are not valid */ Certificate authenticate(String username, char[] password, boolean keepAlive) throws AccessDeniedException; @@ -669,22 +507,16 @@ public interface PrivilegeHandler { * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns * a {@link Certificate} with which this user may then perform actions * - * @param username - * the username of the {@link User} which is registered in the {@link PersistenceHandler} - * @param password - * the password with which this user is to be authenticated. Null passwords are not accepted and they must meet - * the requirements of the {@link #validatePassword(Locale, char[])}-method - * @param source - * the source of the authentication request, i.e. remote IP - * @param usage - * the usage type for this authentication - * @param keepAlive - * should this session be kept alive + * @param username the username of the {@link User} which is registered in the {@link PersistenceHandler} + * @param password the password with which this user is to be authenticated. Null passwords are not accepted and + * they must meet the requirements of the {@link #validatePassword(Locale, char[])}-method + * @param source the source of the authentication request, i.e. remote IP + * @param usage the usage type for this authentication + * @param keepAlive should this session be kept alive * * @return a {@link Certificate} with which this user may then perform actions * - * @throws AccessDeniedException - * if the user credentials are not valid + * @throws AccessDeniedException if the user credentials are not valid */ Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive) throws AccessDeniedException; @@ -692,47 +524,37 @@ public interface PrivilegeHandler { /** * Authenticates a user on a remote Single Sign On service. This is implemented by the * - * @param data - * the data to perform the SSO - * @param keepAlive - * should this session be kept alive + * @param data the data to perform the SSO + * @param keepAlive should this session be kept alive * * @return the {@link Certificate} for the user * - * @throws PrivilegeException - * if something goes wrong with the SSO + * @throws PrivilegeException if something goes wrong with the SSO */ Certificate authenticateSingleSignOn(Object data, boolean keepAlive) throws PrivilegeException; /** * Authenticates a user on a remote Single Sign On service. This is implemented by the * - * @param data - * the data to perform the SSO - * @param source - * the source of the SSO authentication - * @param keepAlive - * may the certificate be kept alive + * @param data the data to perform the SSO + * @param source the source of the SSO authentication + * @param keepAlive may the certificate be kept alive * * @return the {@link Certificate} for the user * - * @throws PrivilegeException - * if something goes wrong with the SSO + * @throws PrivilegeException if something goes wrong with the SSO */ Certificate authenticateSingleSignOn(Object data, String source, boolean keepAlive) throws PrivilegeException; /** * Refreshes the given certificate's session with a new session, i.e. a new certificate * - * @param certificate - * the certificate for which to perform a refresh - * @param source - * the source of the refresh request + * @param certificate the certificate for which to perform a refresh + * @param source the source of the refresh request * * @return a {@link Certificate} with which this user may then perform actions * - * @throws AccessDeniedException - * if the certificate is now valid, or refreshing is not allowed + * @throws AccessDeniedException if the certificate is now valid, or refreshing is not allowed */ Certificate refresh(Certificate certificate, String source) throws AccessDeniedException; @@ -754,8 +576,7 @@ public interface PrivilegeHandler { * Invalidates the session for the given {@link Certificate}, effectively logging out the user who was authenticated * with the credentials associated to the given {@link Certificate} * - * @param certificate - * the {@link Certificate} for which the session is to be invalidated + * @param certificate the {@link Certificate} for which the session is to be invalidated * * @return true if the session was still valid and is now invalidated, false otherwise */ @@ -769,15 +590,12 @@ public interface PrivilegeHandler { * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their * actions and do not need to access the {@link PrivilegeHandler} anymore * - * @param certificate - * the {@link Certificate} to check + * @param certificate the {@link Certificate} to check * * @return the {@link PrivilegeContext} for the given {@link Certificate} * - * @throws PrivilegeException - * if there is anything wrong with this certificate - * @throws NotAuthenticatedException - * if the certificate has expired + * @throws PrivilegeException if there is anything wrong with this certificate + * @throws NotAuthenticatedException if the certificate has expired */ PrivilegeContext validate(Certificate certificate) throws PrivilegeException; @@ -786,13 +604,10 @@ public interface PrivilegeHandler { * system user session and that the user exists for the certificate. This method checks if the {@link Certificate} * has been tampered with * - * @param ctx - * the {@link PrivilegeContext} to check + * @param ctx the {@link PrivilegeContext} to check * - * @throws PrivilegeException - * if there is anything wrong with this privilege context - * @throws NotAuthenticatedException - * if the privilege context has expired + * @throws PrivilegeException if there is anything wrong with this privilege context + * @throws NotAuthenticatedException if the privilege context has expired */ void validateSystemSession(PrivilegeContext ctx) throws PrivilegeException; @@ -804,17 +619,13 @@ public interface PrivilegeHandler { * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their * actions and do not need to access the {@link PrivilegeHandler} anymore * - * @param certificate - * the {@link Certificate} to check - * @param source - * the source, e.g. remote IP for this validation request + * @param certificate the {@link Certificate} to check + * @param source the source, e.g. remote IP for this validation request * * @return the {@link PrivilegeContext} for the given {@link Certificate} * - * @throws PrivilegeException - * if there is anything wrong with this certificate - * @throws NotAuthenticatedException - * if the certificate has expired + * @throws PrivilegeException if there is anything wrong with this certificate + * @throws NotAuthenticatedException if the certificate has expired */ PrivilegeContext validate(Certificate certificate, String source) throws PrivilegeException; @@ -840,15 +651,13 @@ public interface PrivilegeHandler { * * Note: It depends on the underlying {@link PersistenceHandler} implementation if data really is read * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param source - * the source of the request + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param source the source of the request * * @return true if the reload was successful, false if something went wrong * - * @throws AccessDeniedException - * if the users of the given certificate does not have the privilege to perform this action + * @throws AccessDeniedException if the users of the given certificate does not have the privilege to perform this + * action */ boolean reload(Certificate certificate, String source); @@ -856,28 +665,25 @@ public interface PrivilegeHandler { * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be * actively performed * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action * * @return true if changes were persisted, false if no changes were persisted * - * @throws AccessDeniedException - * if the users of the given certificate does not have the privilege to perform this action + * @throws AccessDeniedException if the users of the given certificate does not have the privilege to perform this + * action */ boolean persist(Certificate certificate) throws AccessDeniedException; /** * Persists all currently active sessions * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param source - * the source of the request + * @param certificate the {@link Certificate} of the user which has the privilege to perform this action + * @param source the source of the request * * @return true if changes were persisted, false if not (i.e. not enabled) * - * @throws AccessDeniedException - * if the users of the given certificate does not have the privilege to perform this action + * @throws AccessDeniedException if the users of the given certificate does not have the privilege to perform this + * action */ boolean persistSessions(Certificate certificate, String source) throws AccessDeniedException; @@ -886,15 +692,11 @@ public interface PrivilegeHandler { * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation * of the given {@link SystemAction} instance * - * @param systemUsername - * the username of the system user to perform the action as - * @param action - * the action to be performed as the system user + * @param systemUsername the username of the system user to perform the action as + * @param action the action to be performed as the system user * - * @throws PrivilegeException - * if the user does not exist, or the system action is not allowed - * @throws Exception - * if anything else goes wrong during execution + * @throws PrivilegeException if the user does not exist, or the system action is not allowed + * @throws Exception if anything else goes wrong during execution */ void runAs(String systemUsername, SystemAction action) throws PrivilegeException, Exception; @@ -903,17 +705,13 @@ public interface PrivilegeHandler { * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation * of the given {@link SystemAction} instance * - * @param systemUsername - * the username of the system user to perform the action as - * @param action - * the action to be performed as the system user + * @param systemUsername the username of the system user to perform the action as + * @param action the action to be performed as the system user * * @return the action * - * @throws PrivilegeException - * if the user does not exist, or the system action is not allowed - * @throws Exception - * if anything else goes wrong during execution + * @throws PrivilegeException if the user does not exist, or the system action is not allowed + * @throws Exception if anything else goes wrong during execution */ T runWithResult(String systemUsername, SystemActionWithResult action) throws PrivilegeException, Exception; @@ -923,13 +721,11 @@ public interface PrivilegeHandler { * {@link PrivilegeContext} should be open for a longer period of time, or where opening many * {@link PrivilegeContext} is resource intensive e.g. on low power devices. * - * @param systemUsername - * the username of the system user to perform the action as + * @param systemUsername the username of the system user to perform the action as * * @return the action * - * @throws PrivilegeException - * if the user does not exist, or the system action is not allowed + * @throws PrivilegeException if the user does not exist, or the system action is not allowed */ PrivilegeContext openSystemUserContext(String systemUsername) throws PrivilegeException; diff --git a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java index 8324d07c5..959e0d14b 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java @@ -22,7 +22,6 @@ import li.strolch.privilege.policy.PrivilegePolicy; import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import static li.strolch.utils.helper.StringHelper.isEmpty; import static li.strolch.utils.helper.StringHelper.trimOrEmpty; @@ -42,6 +41,8 @@ public class PrivilegeRep { private Set denyList; private Set allowList; + private boolean readOnly; + /** * Default constructor * @@ -57,8 +58,26 @@ public class PrivilegeRep { this.name = trimOrEmpty(name); this.policy = trimOrEmpty(policy); this.allAllowed = allAllowed; - this.denyList = denyList == null ? Set.of() : Set.copyOf(denyList); - this.allowList = allowList == null ? Set.of() : Set.copyOf(allowList); + setDenyList(denyList == null ? Set.of() : denyList); + setAllowList(allowList == null ? Set.of() : allowList); + } + + public boolean isReadOnly() { + return readOnly; + } + + public PrivilegeRep readOnly() { + if (this.readOnly) + return this; + this.readOnly = true; + this.denyList = Set.copyOf(this.denyList); + this.allowList = Set.copyOf(this.allowList); + return this; + } + + protected void assertNotReadonly() { + if (this.readOnly) + throw new IllegalStateException("Privilege is currently readOnly, to modify get a copy!"); } /** @@ -86,6 +105,7 @@ public class PrivilegeRep { * @param name the name to set */ public void setName(String name) { + assertNotReadonly(); this.name = trimOrEmpty(name); } @@ -100,6 +120,7 @@ public class PrivilegeRep { * @param policy the policy to set */ public void setPolicy(String policy) { + assertNotReadonly(); this.policy = trimOrEmpty(policy); } @@ -114,6 +135,7 @@ public class PrivilegeRep { * @param allAllowed the allAllowed to set */ public void setAllAllowed(boolean allAllowed) { + assertNotReadonly(); this.allAllowed = allAllowed; } @@ -128,7 +150,8 @@ public class PrivilegeRep { * @param denyList the denyList to set */ public void setDenyList(Set denyList) { - this.denyList = denyList.stream().map(String::trim).collect(Collectors.toSet()); + assertNotReadonly(); + this.denyList = denyList.stream().map(String::trim).collect(HashSet::new, HashSet::add, HashSet::addAll); } /** @@ -142,7 +165,8 @@ public class PrivilegeRep { * @param allowList the allowList to set */ public void setAllowList(Set allowList) { - this.allowList = allowList.stream().map(String::trim).collect(Collectors.toSet()); + assertNotReadonly(); + this.allowList = allowList.stream().map(String::trim).collect(HashSet::new, HashSet::add, HashSet::addAll); } /** @@ -178,6 +202,10 @@ public class PrivilegeRep { return this.name.equals(other.name); } + public PrivilegeRep getCopy() { + return new PrivilegeRep(this.name, this.policy, this.allAllowed, this.denyList, this.allowList); + } + public T accept(PrivilegeElementVisitor visitor) { return visitor.visitPrivilegeRep(this); } diff --git a/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java b/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java index 9ad05849d..d43d2bfe5 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/RoleRep.java @@ -20,7 +20,8 @@ import li.strolch.privilege.model.internal.Role; import li.strolch.utils.dbc.DBC; import java.text.MessageFormat; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import static li.strolch.utils.helper.StringHelper.isEmpty; import static li.strolch.utils.helper.StringHelper.trimOrEmpty; @@ -35,7 +36,9 @@ import static li.strolch.utils.helper.StringHelper.trimOrEmpty; public class RoleRep { private String name; - private List privileges; + private Map privileges; + + private boolean readOnly; /** * Default constructor @@ -43,9 +46,26 @@ public class RoleRep { * @param name the name of this role * @param privileges the list of privileges granted to this role */ - public RoleRep(String name, List privileges) { + public RoleRep(String name, Map privileges) { this.name = trimOrEmpty(name); - this.privileges = privileges == null ? List.of() : List.copyOf(privileges); + setPrivileges(privileges == null ? Map.of() : privileges); + } + + public boolean isReadOnly() { + return readOnly; + } + + public RoleRep readOnly() { + if (this.readOnly) + return this; + this.readOnly = true; + this.privileges = Map.copyOf(this.privileges); + return this; + } + + protected void assertNotReadonly() { + if (this.readOnly) + throw new IllegalStateException("Role is currently readOnly, to modify get a copy!"); } /** @@ -55,7 +75,7 @@ public class RoleRep { if (isEmpty(this.name)) throw new PrivilegeException("name is null"); - for (PrivilegeRep privilege : this.privileges) { + for (PrivilegeRep privilege : this.privileges.values()) { try { privilege.validate(); } catch (Exception e) { @@ -77,26 +97,26 @@ public class RoleRep { * @param name the name to set */ public void setName(String name) { + assertNotReadonly(); this.name = trimOrEmpty(name); } - /** - * Returns the privileges assigned to this Role as a list - * - * @return the privileges assigned to this Role as a list - */ - public List getPrivileges() { + public Map getPrivileges() { + if (this.privileges == null) + return null; return this.privileges; } - /** - * Sets the privileges on this from a list - * - * @param privileges the list of privileges to assign to this role - */ - public void setPrivileges(List privileges) { + public void setPrivileges(Map privileges) { + assertNotReadonly(); DBC.PRE.assertNotNull("privileges must not be null!", privileges); - this.privileges = List.copyOf(privileges); + this.privileges = new HashMap<>(privileges); + } + + public void addPrivilege(PrivilegeRep privilegeRep) { + DBC.PRE.assertFalse(() -> "Privilege " + privilegeRep.getName() + " already on role " + this.name, + privileges.containsKey(privilegeRep.getName())); + this.privileges.put(privilegeRep.getName(), privilegeRep); } /** @@ -131,6 +151,10 @@ public class RoleRep { return this.name.equals(other.name); } + public RoleRep getCopy() { + return new RoleRep(this.name, this.privileges); + } + public T accept(PrivilegeElementVisitor visitor) { return visitor.visitRoleRep(this); } diff --git a/privilege/src/main/java/li/strolch/privilege/model/UserRep.java b/privilege/src/main/java/li/strolch/privilege/model/UserRep.java index 13d52d07a..8a82e9fbf 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/UserRep.java +++ b/privilege/src/main/java/li/strolch/privilege/model/UserRep.java @@ -25,7 +25,6 @@ import li.strolch.utils.dbc.DBC; import java.text.MessageFormat; import java.util.*; -import static java.util.stream.Collectors.toSet; import static li.strolch.privilege.base.PrivilegeConstants.*; import static li.strolch.utils.helper.StringHelper.isEmpty; import static li.strolch.utils.helper.StringHelper.trimOrEmpty; @@ -74,15 +73,11 @@ public class UserRep { this.firstname = trimOrEmpty(firstname); this.lastname = trimOrEmpty(lastname); this.userState = userState; - this.groups = groups == null ? null : groups.stream().map(String::trim).collect(toSet()); - this.roles = roles == null ? null : roles.stream().map(String::trim).collect(toSet()); this.locale = locale; - - this.properties = new HashMap<>(); - if (propertyMap != null) - propertyMap.forEach((key, value) -> this.properties.put(key.trim(), value.trim())); - - this.history = history; + setGroups(groups == null ? Set.of() : groups); + setRoles(roles == null ? Set.of() : roles); + setProperties(properties == null ? Map.of() : properties); + this.history = history == null ? UserHistory.EMPTY : history; } @SuppressWarnings("unused") @@ -129,7 +124,8 @@ public class UserRep { } public UserRep readOnly() { - assertNotReadonly(); + if (this.readOnly) + return this; this.readOnly = true; this.groups = Set.copyOf(this.groups); this.roles = Set.copyOf(this.roles); @@ -232,17 +228,17 @@ public class UserRep { } public Set getGroups() { - return groups; + return this.groups; } public void setGroups(Set groups) { DBC.PRE.assertNotNull("groups must not be null!", groups); assertNotReadonly(); - this.groups = groups.stream().map(String::trim).collect(toSet()); + this.groups = groups.stream().map(String::trim).collect(HashSet::new, HashSet::add, HashSet::addAll); } public boolean hasGroup(String group) { - return this.groups != null && this.groups.contains(group); + return this.groups.contains(group); } /** @@ -258,11 +254,16 @@ public class UserRep { public void setRoles(Set roles) { DBC.PRE.assertNotNull("roles must not be null!", roles); assertNotReadonly(); - this.roles = roles.stream().map(String::trim).collect(toSet()); + this.roles = roles.stream().map(String::trim).collect(HashSet::new, HashSet::add, HashSet::addAll); + } + + public void addRole(String role) { + assertNotReadonly(); + this.roles.add(role); } public boolean hasRole(String role) { - return this.roles != null && this.roles.contains(role); + return this.roles.contains(role); } /** @@ -286,8 +287,6 @@ public class UserRep { * @return the user history */ public UserHistory getHistory() { - if (this.history == null) - return UserHistory.EMPTY; return this.history; } @@ -329,7 +328,8 @@ public class UserRep { public void setProperties(Map properties) { DBC.PRE.assertNotNull("properties must not be null!", properties); assertNotReadonly(); - this.properties = properties; + this.properties = new HashMap<>(); + properties.forEach((key, value) -> this.properties.put(key.trim(), value.trim())); } /** @@ -338,9 +338,7 @@ public class UserRep { * @return the {@link Set} of keys of all properties */ public Set getPropertyKeySet() { - if (this.readOnly) - return this.properties.keySet(); - return new HashSet<>(this.properties.keySet()); + return this.properties.keySet(); } /** @@ -349,9 +347,7 @@ public class UserRep { * @return the map of properties */ public Map getProperties() { - if (this.readOnly) - return this.properties; - return new HashMap<>(this.properties); + return this.properties; } /** @@ -417,13 +413,8 @@ public class UserRep { } public UserRep getCopy() { - - Set groups = this.groups == null ? null : new HashSet<>(this.groups); - Set roles = this.roles == null ? null : new HashSet<>(this.roles); - Map propertyMap = new HashMap<>(this.properties); - - return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, groups, roles, - this.locale, propertyMap, this.history == null ? UserHistory.EMPTY : this.history); + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, this.groups, + this.roles, this.locale, this.properties, this.history); } public T accept(PrivilegeElementVisitor visitor) { diff --git a/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java b/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java index b212bfaae..2ac464ef1 100644 --- a/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java +++ b/privilege/src/main/java/li/strolch/privilege/model/internal/Role.java @@ -21,8 +21,9 @@ import li.strolch.privilege.model.PrivilegeRep; import li.strolch.privilege.model.RoleRep; import li.strolch.utils.dbc.DBC; -import java.util.*; -import java.util.Map.Entry; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import static li.strolch.utils.helper.StringHelper.isEmpty; @@ -62,9 +63,7 @@ public record Role(String name, Map privilegeMap) { // build privileges from rep Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); - for (PrivilegeRep privilege : roleRep.getPrivileges()) { - privilegeMap.put(privilege.getName(), Privilege.of(privilege)); - } + roleRep.getPrivileges().values().forEach(p -> privilegeMap.put(p.getName(), Privilege.of(p))); return new Role(name, privilegeMap); } @@ -109,10 +108,8 @@ public record Role(String name, Map privilegeMap) { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - List privileges = new ArrayList<>(); - for (Entry entry : this.privilegeMap.entrySet()) { - privileges.add(entry.getValue().asPrivilegeRep()); - } + Map privileges = new HashMap<>(); + this.privilegeMap.values().forEach(p -> privileges.put(p.getName(), p.asPrivilegeRep())); return new RoleRep(this.name, privileges); } diff --git a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java index afcc5ec66..354532f97 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java @@ -137,7 +137,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - RoleRep roleRep = new RoleRep(ROLE_TEMP, new ArrayList<>()); + RoleRep roleRep = new RoleRep(ROLE_TEMP, Map.of()); Certificate certificate = this.ctx.getCertificate(); this.privilegeHandler.addRole(certificate, roleRep); @@ -253,9 +253,12 @@ public class PrivilegeTest extends AbstractPrivilegeTest { assertNotEquals("The", user.getFirstname()); assertNotEquals("Admin", user.getLastname()); - // let's add a new user bob - UserRep userRep = new UserRep(null, ADMIN, "The", "Admin", null, null, null, null, null, null); - this.privilegeHandler.updateUser(certificate, userRep); + // set new name + user.setFirstname("The"); + user.setLastname("Admin"); + + // update user + this.privilegeHandler.updateUser(certificate, user, null); user = this.privilegeHandler.getUser(certificate, ADMIN); assertEquals("The", user.getFirstname()); @@ -275,8 +278,9 @@ public class PrivilegeTest extends AbstractPrivilegeTest { Certificate certificate = this.ctx.getCertificate(); // let's add a new user bob - UserRep userRep = new UserRep(null, BOB, null, null, null, null, null, null, null, null); - this.privilegeHandler.updateUser(certificate, userRep); + UserRep userRep = new UserRep(BOB, BOB, "Bob", "Anderson", UserState.ENABLED, + Set.of("AppUserLocationA"), null, null, null, null); + this.privilegeHandler.updateUser(certificate, userRep, null); } finally { logout(); } @@ -284,25 +288,6 @@ public class PrivilegeTest extends AbstractPrivilegeTest { MatcherAssert.assertThat(exception.getMessage(), containsString("User bob does not exist")); } - @Test - public void shouldFailUpdateAdminNoChanges() { - PrivilegeException exception = assertThrows(PrivilegeException.class, () -> { - try { - login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - - Certificate certificate = this.ctx.getCertificate(); - - // let's add a new user bob - UserRep userRep = new UserRep(null, ADMIN, null, null, null, null, null, null, null, null); - this.privilegeHandler.updateUser(certificate, userRep); - } finally { - logout(); - } - }); - MatcherAssert.assertThat(exception.getMessage(), - containsString("All updateable fields are empty for update of user admin")); - } - @Test public void shouldQueryUsers() { try { @@ -379,8 +364,10 @@ public class PrivilegeTest extends AbstractPrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); PrivilegeRep privilegeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_ACTION, "DefaultPrivilege", - true, Collections.emptySet(), Collections.emptySet()); - this.privilegeHandler.addOrReplacePrivilegeOnRole(certificate, ROLE_APP_USER, privilegeRep); + true, Set.of(), Set.of()); + RoleRep role = this.privilegeHandler.getRole(certificate, ROLE_APP_USER); + role.addPrivilege(privilegeRep); + this.privilegeHandler.replaceRole(certificate, role); } finally { logout(); } @@ -394,8 +381,10 @@ public class PrivilegeTest extends AbstractPrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - this.privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY); - this.privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY2); + UserRep user = this.privilegeHandler.getUser(certificate, ADMIN); + user.addRole(ROLE_MY); + user.addRole(ROLE_MY2); + this.privilegeHandler.updateUser(certificate, user, null); } finally { logout(); } @@ -559,15 +548,21 @@ public class PrivilegeTest extends AbstractPrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); PrivilegeRep passwordRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD, - PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); + PRIVILEGE_USER_ACCESS, false, Set.of(), Set.of()); PrivilegeRep localeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, PRIVILEGE_USER_ACCESS, - false, Collections.emptySet(), Collections.emptySet()); + false, Set.of(), Set.of()); - RoleRep roleRep = new RoleRep(ROLE_CHANGE_PW, Arrays.asList(passwordRep, localeRep)); + Map privileges = new HashMap<>(); + privileges.put(passwordRep.getName(), passwordRep); + privileges.put(localeRep.getName(), localeRep); + RoleRep roleRep = new RoleRep(ROLE_CHANGE_PW, privileges); Certificate certificate = this.ctx.getCertificate(); this.privilegeHandler.addRole(certificate, roleRep); - this.privilegeHandler.addRoleToUser(certificate, TED, ROLE_CHANGE_PW); + + UserRep ted = this.privilegeHandler.getUser(certificate, TED); + ted.addRole(ROLE_CHANGE_PW); + this.privilegeHandler.updateUser(certificate, ted, null); logger.info("Added " + ROLE_CHANGE_PW + " to " + TED); this.privilegeHandler.persist(certificate); } finally { @@ -593,7 +588,9 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // testAddAppRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - this.privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + UserRep bob = this.privilegeHandler.getUser(certificate, BOB); + bob.addRole(ROLE_APP_USER); + this.privilegeHandler.updateUser(certificate, bob, null); logger.info("Added " + ROLE_APP_USER + " to " + BOB); this.privilegeHandler.persist(certificate); } finally { @@ -694,7 +691,9 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // testAddAdminRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - this.privilegeHandler.addRoleToUser(certificate, BOB, ROLE_PRIVILEGE_ADMIN); + UserRep bob = this.privilegeHandler.getUser(certificate, BOB); + bob.addRole(ROLE_PRIVILEGE_ADMIN); + this.privilegeHandler.updateUser(certificate, bob, null); logger.info("Added " + ROLE_PRIVILEGE_ADMIN + " to " + ADMIN); this.privilegeHandler.persist(certificate); } finally { @@ -739,7 +738,9 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // testAddRoleUserToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - this.privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + UserRep bob = this.privilegeHandler.getUser(certificate, BOB); + bob.addRole(ROLE_USER); + this.privilegeHandler.updateUser(certificate, bob, null); this.privilegeHandler.persist(certificate); logout(); } finally { @@ -751,7 +752,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { try { // add role user login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList<>()); + RoleRep roleRep = new RoleRep(ROLE_USER, Map.of()); Certificate certificate = this.ctx.getCertificate(); this.privilegeHandler.addRole(certificate, roleRep); this.privilegeHandler.persist(certificate); diff --git a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index d5f88c23e..2f3bf4728 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -254,7 +254,7 @@ public class XmlTest { Map groups = xmlHandler.getGroups(); assertNotNull(groups); - assertEquals(1, groups.size()); + assertEquals(2, groups.size()); // group AppUserLocationA Group group = groups.get("AppUserLocationA"); @@ -263,6 +263,12 @@ public class XmlTest { Map properties = group.getProperties(); assertEquals(new HashSet<>(List.of("location")), properties.keySet()); assertEquals("LocationA", properties.get("location")); + + group = groups.get("GroupA"); + assertEquals("GroupA", group.name()); + properties = group.getProperties(); + assertTrue(properties.isEmpty()); + assertTrue(group.roles().isEmpty()); } @Test diff --git a/privilege/src/test/resources/config/PrivilegeGroups.xml b/privilege/src/test/resources/config/PrivilegeGroups.xml index 166062110..a06069366 100644 --- a/privilege/src/test/resources/config/PrivilegeGroups.xml +++ b/privilege/src/test/resources/config/PrivilegeGroups.xml @@ -1,5 +1,6 @@ + AppUser diff --git a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java b/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java deleted file mode 100644 index 180cb9075..000000000 --- a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.roles; - -import li.strolch.privilege.model.PrivilegeRep; -import li.strolch.service.api.ServiceArgument; - -public class PrivilegeAddOrReplacePrivilegeOnRoleArgument extends ServiceArgument { - public String roleName; - public PrivilegeRep privilegeRep; -} diff --git a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java b/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java deleted file mode 100644 index 1d758008d..000000000 --- a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.roles; - -import li.strolch.model.audit.AccessType; -import li.strolch.model.audit.Audit; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.RoleRep; -import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; -import li.strolch.service.api.AbstractService; -import li.strolch.service.api.ServiceResultState; - -/** - * @author Robert von Burg - */ -public class PrivilegeAddOrReplacePrivilegeOnRoleService - extends AbstractService { - - @Override - protected PrivilegeRoleResult getResultInstance() { - return new PrivilegeRoleResult(ServiceResultState.FAILED); - } - - @Override - public PrivilegeAddOrReplacePrivilegeOnRoleArgument getArgumentInstance() { - return new PrivilegeAddOrReplacePrivilegeOnRoleArgument(); - } - - @Override - protected PrivilegeRoleResult internalDoService(PrivilegeAddOrReplacePrivilegeOnRoleArgument arg) { - - li.strolch.runtime.privilege.PrivilegeHandler strolchPrivilegeHandler = getContainer().getPrivilegeHandler(); - PrivilegeHandler privilegeHandler = strolchPrivilegeHandler.getPrivilegeHandler(); - - RoleRep role; - try (StrolchTransaction tx = openArgOrUserTx(arg, PrivilegeHandler.PRIVILEGE_MODIFY_ROLE)) { - tx.setSuppressAudits(true); - - role = privilegeHandler.addOrReplacePrivilegeOnRole(getCertificate(), arg.roleName, arg.privilegeRep); - privilegeHandler.persist(getCertificate()); - - Audit audit = tx - .auditFrom(AccessType.UPDATE, StrolchPrivilegeConstants.PRIVILEGE, StrolchPrivilegeConstants.ROLE, - role.getName()); - tx.getAuditTrail().add(tx, audit); - } - - return new PrivilegeRoleResult(role); - } -} diff --git a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleArgument.java b/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleArgument.java deleted file mode 100644 index 10c4fb1df..000000000 --- a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleArgument.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.roles; - -import li.strolch.service.api.ServiceArgument; - -public class PrivilegeRemovePrivilegeFromRoleArgument extends ServiceArgument { - public String roleName; - public String privilegeName; -} diff --git a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java b/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java deleted file mode 100644 index ca731b3bb..000000000 --- a/service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.roles; - -import li.strolch.model.audit.AccessType; -import li.strolch.model.audit.Audit; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.RoleRep; -import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; -import li.strolch.service.api.AbstractService; -import li.strolch.service.api.ServiceResultState; - -/** - * @author Robert von Burg - */ -public class PrivilegeRemovePrivilegeFromRoleService - extends AbstractService { - - @Override - protected PrivilegeRoleResult getResultInstance() { - return new PrivilegeRoleResult(ServiceResultState.FAILED); - } - - @Override - public PrivilegeRemovePrivilegeFromRoleArgument getArgumentInstance() { - return new PrivilegeRemovePrivilegeFromRoleArgument(); - } - - @Override - protected PrivilegeRoleResult internalDoService(PrivilegeRemovePrivilegeFromRoleArgument arg) { - - li.strolch.runtime.privilege.PrivilegeHandler strolchPrivilegeHandler = getContainer().getPrivilegeHandler(); - PrivilegeHandler privilegeHandler = strolchPrivilegeHandler.getPrivilegeHandler(); - - RoleRep role; - try (StrolchTransaction tx = openArgOrUserTx(arg, StrolchPrivilegeConstants.PRIVILEGE_MODIFY_ROLE)) { - tx.setSuppressAudits(true); - - role = privilegeHandler.removePrivilegeFromRole(getCertificate(), arg.roleName, arg.privilegeName); - privilegeHandler.persist(getCertificate()); - - Audit audit = tx - .auditFrom(AccessType.UPDATE, StrolchPrivilegeConstants.PRIVILEGE, StrolchPrivilegeConstants.ROLE, - role.getName()); - tx.getAuditTrail().add(tx, audit); - } - - return new PrivilegeRoleResult(role); - } -} diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java deleted file mode 100644 index dc5de32d9..000000000 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.users; - -import li.strolch.model.audit.AccessType; -import li.strolch.model.audit.Audit; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.UserRep; -import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; -import li.strolch.service.api.AbstractService; -import li.strolch.service.api.ServiceResultState; - -/** - * @author Robert von Burg - */ -public class PrivilegeAddRoleToUserService - extends AbstractService { - - @Override - protected PrivilegeUserResult getResultInstance() { - return new PrivilegeUserResult(ServiceResultState.FAILED); - } - - @Override - public PrivilegeRoleUserNamesArgument getArgumentInstance() { - return new PrivilegeRoleUserNamesArgument(); - } - - @Override - protected PrivilegeUserResult internalDoService(PrivilegeRoleUserNamesArgument arg) { - - li.strolch.runtime.privilege.PrivilegeHandler strolchPrivilegeHandler = getContainer().getPrivilegeHandler(); - PrivilegeHandler privilegeHandler = strolchPrivilegeHandler.getPrivilegeHandler(); - - UserRep user; - try (StrolchTransaction tx = openArgOrUserTx(arg, PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER)) { - tx.setSuppressAudits(true); - - user = privilegeHandler.addRoleToUser(getCertificate(), arg.username, arg.rolename); - if (privilegeHandler.isPersistOnUserDataChanged()) - privilegeHandler.persist(getCertificate()); - - Audit audit = tx.auditFrom(AccessType.UPDATE, StrolchPrivilegeConstants.PRIVILEGE, - StrolchPrivilegeConstants.USER, user.getUsername()); - tx.getAuditTrail().add(tx, audit); - } - - return new PrivilegeUserResult(user); - } -} diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java deleted file mode 100644 index 6ff55b727..000000000 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.users; - -import li.strolch.model.audit.AccessType; -import li.strolch.model.audit.Audit; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.UserRep; -import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; -import li.strolch.service.api.AbstractService; - -/** - * @author Robert von Burg - */ -public class PrivilegeRemoveRoleFromUserService - extends AbstractService { - - @Override - protected PrivilegeUserResult getResultInstance() { - return new PrivilegeUserResult(); - } - - @Override - public PrivilegeRoleUserNamesArgument getArgumentInstance() { - return new PrivilegeRoleUserNamesArgument(); - } - - @Override - protected PrivilegeUserResult internalDoService(PrivilegeRoleUserNamesArgument arg) { - - li.strolch.runtime.privilege.PrivilegeHandler strolchPrivilegeHandler = getContainer().getPrivilegeHandler(); - PrivilegeHandler privilegeHandler = strolchPrivilegeHandler.getPrivilegeHandler(); - - UserRep user; - try (StrolchTransaction tx = openArgOrUserTx(arg, PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER)) { - tx.setSuppressAudits(true); - - user = privilegeHandler.removeRoleFromUser(getCertificate(), arg.username, arg.rolename); - if (privilegeHandler.isPersistOnUserDataChanged()) - privilegeHandler.persist(getCertificate()); - - Audit audit = tx.auditFrom(AccessType.UPDATE, StrolchPrivilegeConstants.PRIVILEGE, - StrolchPrivilegeConstants.USER, user.getUsername()); - tx.getAuditTrail().add(tx, audit); - } - - return new PrivilegeUserResult(user); - } -} diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserRolesService.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserRolesService.java deleted file mode 100644 index cd63191e6..000000000 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserRolesService.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package li.strolch.service.privilege.users; - -import java.util.HashSet; -import java.util.Set; - -import com.google.gson.JsonArray; -import li.strolch.model.audit.AccessType; -import li.strolch.model.audit.Audit; -import li.strolch.persistence.api.StrolchTransaction; -import li.strolch.privilege.handler.PrivilegeHandler; -import li.strolch.privilege.model.UserRep; -import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; -import li.strolch.service.JsonServiceArgument; -import li.strolch.service.api.AbstractService; -import li.strolch.service.api.ServiceResultState; - -/** - * @author Robert von Burg - */ -public class PrivilegeUpdateUserRolesService extends AbstractService { - - @Override - protected PrivilegeUserResult getResultInstance() { - return new PrivilegeUserResult(ServiceResultState.FAILED); - } - - @Override - public JsonServiceArgument getArgumentInstance() { - return new JsonServiceArgument(); - } - - @Override - protected PrivilegeUserResult internalDoService(JsonServiceArgument arg) { - - li.strolch.runtime.privilege.PrivilegeHandler strolchPrivilegeHandler = getContainer().getPrivilegeHandler(); - PrivilegeHandler privilegeHandler = strolchPrivilegeHandler.getPrivilegeHandler(); - - JsonArray rolesE = arg.jsonElement.getAsJsonArray(); - Set roles = new HashSet<>(); - rolesE.forEach(e -> roles.add(e.getAsString())); - - String username = arg.objectId; - - UserRep user; - try (StrolchTransaction tx = openArgOrUserTx(arg, PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER)) { - tx.setSuppressAudits(true); - - user = privilegeHandler.getUser(getCertificate(), username); - - // first add new roles - boolean changed = false; - for (String role : roles) { - if (!user.hasRole(role)) { - user = privilegeHandler.addRoleToUser(getCertificate(), username, role); - changed = true; - } - } - - // handle removed roles - for (String role : user.getRoles()) { - if (!roles.contains(role)) { - user = privilegeHandler.removeRoleFromUser(getCertificate(), username, role); - changed = true; - } - } - - if (changed) { - - if (privilegeHandler.isPersistOnUserDataChanged()) - privilegeHandler.persist(getCertificate()); - - Audit audit = tx.auditFrom(AccessType.UPDATE, StrolchPrivilegeConstants.PRIVILEGE, - StrolchPrivilegeConstants.USER, user.getUsername()); - tx.getAuditTrail().add(tx, audit); - } - } - - return new PrivilegeUserResult(user); - } -} diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java index 634714e54..d2cff7a1a 100644 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java +++ b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java @@ -49,7 +49,7 @@ public class PrivilegeUpdateUserService extends AbstractService */ @@ -147,50 +145,11 @@ public class PrivilegeRolesService { return handleServiceResult(svcResult); } - @PUT - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Path("{roleName}/privileges") - public Response addOrReplacePrivilegeOnRole(@Context HttpServletRequest request, - @PathParam("roleName") String roleName, String data) { - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - - PrivilegeRep privilegeRep = new PrivilegeElementFromJsonVisitor().privilegeRepFromJson(data); - - ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getComponent(ServiceHandler.class); - PrivilegeAddOrReplacePrivilegeOnRoleService svc = new PrivilegeAddOrReplacePrivilegeOnRoleService(); - PrivilegeAddOrReplacePrivilegeOnRoleArgument arg = new PrivilegeAddOrReplacePrivilegeOnRoleArgument(); - arg.roleName = roleName; - arg.privilegeRep = privilegeRep; - - PrivilegeRoleResult svcResult = svcHandler.doService(cert, svc, arg); - return handleServiceResult(svcResult); - } - - @DELETE - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Path("{roleName}/privileges/{privilege}") - public Response removePrivilegeFromRole(@Context HttpServletRequest request, @PathParam("roleName") String roleName, - @PathParam("privilege") String privilege) { - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - - ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getComponent(ServiceHandler.class); - PrivilegeRemovePrivilegeFromRoleService svc = new PrivilegeRemovePrivilegeFromRoleService(); - PrivilegeRemovePrivilegeFromRoleArgument arg = new PrivilegeRemovePrivilegeFromRoleArgument(); - arg.roleName = roleName; - arg.privilegeName = privilege; - - PrivilegeRoleResult svcResult = svcHandler.doService(cert, svc, arg); - return handleServiceResult(svcResult); - } - private Response handleServiceResult(PrivilegeRoleResult svcResult) { if (svcResult.isOk()) { RoleRep roleRep = svcResult.getRole(); - return Response - .ok(roleRep.accept(new PrivilegeElementToJsonVisitor()).toString(), MediaType.APPLICATION_JSON) - .build(); + return Response.ok(roleRep.accept(new PrivilegeElementToJsonVisitor()).toString(), + MediaType.APPLICATION_JSON).build(); } return ResponseUtil.toResponse(svcResult); } diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java index 3823819c7..1c8586b01 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java @@ -15,19 +15,6 @@ */ package li.strolch.rest.endpoint; -import static jakarta.ws.rs.core.Response.Status.NOT_ACCEPTABLE; -import static java.util.Comparator.comparing; -import static li.strolch.privilege.handler.PrivilegeHandler.PRIVILEGE_GET_USER; -import static li.strolch.rest.helper.ResponseUtil.toResponse; -import static li.strolch.rest.helper.RestfulHelper.toJson; -import static li.strolch.search.SearchBuilder.buildSimpleValueSearch; - -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.Locale; - import com.google.gson.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.*; @@ -50,7 +37,6 @@ import li.strolch.rest.StrolchSessionHandler; import li.strolch.rest.model.QueryData; import li.strolch.search.SearchResult; import li.strolch.search.ValueSearch; -import li.strolch.service.JsonServiceArgument; import li.strolch.service.StringMapArgument; import li.strolch.service.api.ServiceHandler; import li.strolch.service.api.ServiceResult; @@ -58,6 +44,19 @@ import li.strolch.service.privilege.users.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Locale; + +import static jakarta.ws.rs.core.Response.Status.NOT_ACCEPTABLE; +import static java.util.Comparator.comparing; +import static li.strolch.privilege.handler.PrivilegeHandler.PRIVILEGE_GET_USER; +import static li.strolch.rest.helper.ResponseUtil.toResponse; +import static li.strolch.rest.helper.RestfulHelper.toJson; +import static li.strolch.search.SearchBuilder.buildSimpleValueSearch; + /** * @author Robert von Burg */ @@ -191,60 +190,6 @@ public class PrivilegeUsersService { return handleServiceResult(svcResult); } - @PUT - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Path("{username}/roles") - public Response updateRolesOnUser(@PathParam("username") String username, String data, - @Context HttpServletRequest request) { - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - - ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getComponent(ServiceHandler.class); - PrivilegeUpdateUserRolesService svc = new PrivilegeUpdateUserRolesService(); - JsonServiceArgument arg = svc.getArgumentInstance(); - arg.objectId = username; - arg.jsonElement = JsonParser.parseString(data); - - PrivilegeUserResult svcResult = svcHandler.doService(cert, svc, arg); - return handleServiceResult(svcResult); - } - - @PUT - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Path("{username}/roles/{rolename}") - public Response addRoleToUser(@PathParam("username") String username, @PathParam("rolename") String rolename, - @Context HttpServletRequest request) { - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - - ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getComponent(ServiceHandler.class); - PrivilegeAddRoleToUserService svc = new PrivilegeAddRoleToUserService(); - PrivilegeRoleUserNamesArgument arg = new PrivilegeRoleUserNamesArgument(); - arg.username = username; - arg.rolename = rolename; - - PrivilegeUserResult svcResult = svcHandler.doService(cert, svc, arg); - return handleServiceResult(svcResult); - } - - @DELETE - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @Path("{username}/roles/{rolename}") - public Response removeRoleFromUser(@PathParam("username") String username, @PathParam("rolename") String rolename, - @Context HttpServletRequest request) { - Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE); - - ServiceHandler svcHandler = RestfulStrolchComponent.getInstance().getComponent(ServiceHandler.class); - PrivilegeRemoveRoleFromUserService svc = new PrivilegeRemoveRoleFromUserService(); - PrivilegeRoleUserNamesArgument arg = new PrivilegeRoleUserNamesArgument(); - arg.username = username; - arg.rolename = rolename; - - PrivilegeUserResult svcResult = svcHandler.doService(cert, svc, arg); - return handleServiceResult(svcResult); - } - @PUT @Produces(MediaType.APPLICATION_JSON) @Path("{username}/state/{state}") @@ -279,7 +224,7 @@ public class PrivilegeUsersService { Locale locale; try { - locale = new Locale(localeS); + locale = Locale.forLanguageTag(localeS); } catch (Exception e) { String msg = MessageFormat.format("Locale {0} is not valid!", localeS); return toResponse(msg); From e5bc8434c74ff0a95f8e0c0ba5a03ef51cccdbea Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 13:50:07 +0200 Subject: [PATCH 19/39] [Project] Updated PrivilegeAdmin role in all PrivilegeRoles.xml --- .../test/resources/config/PrivilegeRoles.xml | 73 ++++++++----------- .../svctest/config/PrivilegeRoles.xml | 73 ++++++++----------- 2 files changed, 64 insertions(+), 82 deletions(-) diff --git a/privilege/src/test/resources/config/PrivilegeRoles.xml b/privilege/src/test/resources/config/PrivilegeRoles.xml index 0c6b5c06c..2fb60662b 100644 --- a/privilege/src/test/resources/config/PrivilegeRoles.xml +++ b/privilege/src/test/resources/config/PrivilegeRoles.xml @@ -2,61 +2,32 @@ - - li.strolch.service.privilege.users.PrivilegeUpdateUserService - li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService - li.strolch.service.privilege.users.PrivilegeSetUserPasswordService - li.strolch.service.privilege.users.PrivilegeSetUserLocaleService - li.strolch.service.privilege.users.PrivilegeRemoveUserService - li.strolch.service.privilege.users.PrivilegeRemoveRoleFromUserService - li.strolch.service.privilege.users.PrivilegeAddUserService - li.strolch.service.privilege.users.PrivilegeAddRoleToUserService - li.strolch.service.privilege.roles.PrivilegeUpdateRoleService - li.strolch.service.privilege.roles.PrivilegeRemoveRoleService - li.strolch.service.privilege.roles.PrivilegeRemovePrivilegeFromRoleService - li.strolch.service.privilege.roles.PrivilegeAddRoleService - li.strolch.service.privilege.roles.PrivilegeAddOrReplacePrivilegeOnRoleService - - - true - - + true true - - true - - - true - - - true - - Reload + GetCertificates GetPolicies Persist - GetCertificates PersistSessions + Reload - + true - - SYSTEM - DISABLED - ENABLED - - + true true - + + true + + true @@ -65,15 +36,35 @@ true - + true - + true - + true + + SYSTEM + DISABLED + ENABLED + + + true + + + li.strolch.service.privilege.roles.PrivilegeAddRoleService + li.strolch.service.privilege.roles.PrivilegeRemoveRoleService + li.strolch.service.privilege.roles.PrivilegeUpdateRoleService + li.strolch.service.privilege.users.PrivilegeAddUserService + li.strolch.service.privilege.users.PrivilegeRemoveUserService + li.strolch.service.privilege.users.PrivilegeSetUserLocaleService + li.strolch.service.privilege.users.PrivilegeSetUserPasswordService + li.strolch.service.privilege.users.PrivilegeSetUserStateService + li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService + li.strolch.service.privilege.users.PrivilegeUpdateUserService + diff --git a/service/src/test/resources/svctest/config/PrivilegeRoles.xml b/service/src/test/resources/svctest/config/PrivilegeRoles.xml index c50e5c68f..b23138789 100644 --- a/service/src/test/resources/svctest/config/PrivilegeRoles.xml +++ b/service/src/test/resources/svctest/config/PrivilegeRoles.xml @@ -107,61 +107,32 @@ - - li.strolch.service.privilege.users.PrivilegeUpdateUserService - li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService - li.strolch.service.privilege.users.PrivilegeSetUserPasswordService - li.strolch.service.privilege.users.PrivilegeSetUserLocaleService - li.strolch.service.privilege.users.PrivilegeRemoveUserService - li.strolch.service.privilege.users.PrivilegeRemoveRoleFromUserService - li.strolch.service.privilege.users.PrivilegeAddUserService - li.strolch.service.privilege.users.PrivilegeAddRoleToUserService - li.strolch.service.privilege.roles.PrivilegeUpdateRoleService - li.strolch.service.privilege.roles.PrivilegeRemoveRoleService - li.strolch.service.privilege.roles.PrivilegeRemovePrivilegeFromRoleService - li.strolch.service.privilege.roles.PrivilegeAddRoleService - li.strolch.service.privilege.roles.PrivilegeAddOrReplacePrivilegeOnRoleService - - - true - - + true true - - true - - - true - - - true - - Reload + GetCertificates GetPolicies Persist - GetCertificates PersistSessions + Reload - + true - - SYSTEM - DISABLED - ENABLED - - + true true - + + true + + true @@ -170,14 +141,34 @@ true - + true - + true - + true + + SYSTEM + DISABLED + ENABLED + + + true + + + li.strolch.service.privilege.roles.PrivilegeAddRoleService + li.strolch.service.privilege.roles.PrivilegeRemoveRoleService + li.strolch.service.privilege.roles.PrivilegeUpdateRoleService + li.strolch.service.privilege.users.PrivilegeAddUserService + li.strolch.service.privilege.users.PrivilegeRemoveUserService + li.strolch.service.privilege.users.PrivilegeSetUserLocaleService + li.strolch.service.privilege.users.PrivilegeSetUserPasswordService + li.strolch.service.privilege.users.PrivilegeSetUserStateService + li.strolch.service.privilege.users.PrivilegeUpdateUserRolesService + li.strolch.service.privilege.users.PrivilegeUpdateUserService + From f8c77275f2378e86334dd7c833a735c8d169340b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 14:03:27 +0200 Subject: [PATCH 20/39] [Minor] Code cleanup --- .../handler/PrivilegeCrudHandler.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java index c7929108c..b58bc0449 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/PrivilegeCrudHandler.java @@ -38,7 +38,7 @@ public class PrivilegeCrudHandler { public RoleRep getRole(Certificate certificate, String roleName) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE); Role role = this.persistenceHandler.getRole(roleName); @@ -54,7 +54,7 @@ public class PrivilegeCrudHandler { public UserRep getUser(Certificate certificate, String username) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); User user = this.persistenceHandler.getUser(username); @@ -69,7 +69,7 @@ public class PrivilegeCrudHandler { public Map getPolicyDefs(Certificate certificate) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.validateAction(new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_ACTION, DefaultPrivilegeHandler.PRIVILEGE_ACTION_GET_POLICIES)); @@ -83,7 +83,7 @@ public class PrivilegeCrudHandler { public List getRoles(Certificate certificate) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE); Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); @@ -98,7 +98,7 @@ public class PrivilegeCrudHandler { public List getUsers(Certificate certificate) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); Stream usersStream = this.persistenceHandler.getAllUsers().stream(); @@ -113,7 +113,7 @@ public class PrivilegeCrudHandler { public List queryUsers(Certificate certificate, UserRep selectorRep) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_GET_USER); String selUserId = selectorRep.getUserId(); @@ -244,7 +244,7 @@ public class PrivilegeCrudHandler { try { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER); // make sure userId is not set @@ -273,13 +273,13 @@ public class PrivilegeCrudHandler { if (password != null) { // validate password meets basic requirements - privilegeHandler.validatePassword(certificate.getLocale(), password); + this.privilegeHandler.validatePassword(certificate.getLocale(), password); // get new salt for user - byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + byte[] salt = this.privilegeHandler.getEncryptionHandler().nextSalt(); // hash password - passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + passwordCrypt = this.privilegeHandler.getEncryptionHandler().hashPassword(password, salt); history = history.withLastPasswordChange(ZonedDateTime.now()); } @@ -310,7 +310,7 @@ public class PrivilegeCrudHandler { public void addOrUpdateUsers(Certificate certificate, List userReps) throws PrivilegeException { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_USER); List toCreate = new ArrayList<>(); @@ -508,13 +508,13 @@ public class PrivilegeCrudHandler { } else { // validate password meets basic requirements - privilegeHandler.validatePassword(certificate.getLocale(), password); + this.privilegeHandler.validatePassword(certificate.getLocale(), password); // get new salt for user - byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + byte[] salt = this.privilegeHandler.getEncryptionHandler().nextSalt(); // hash password - passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + passwordCrypt = this.privilegeHandler.getEncryptionHandler().hashPassword(password, salt); history = history.withLastPasswordChange(ZonedDateTime.now()); } @@ -544,7 +544,7 @@ public class PrivilegeCrudHandler { public UserRep removeUser(Certificate certificate, String username) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_USER); // validate user exists @@ -559,7 +559,7 @@ public class PrivilegeCrudHandler { new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); // delegate user removal to persistence handler - privilegeHandler.invalidSessionsFor(existingUser); + this.privilegeHandler.invalidSessionsFor(existingUser); this.persistenceHandler.removeUser(username); this.privilegeHandler.persistModelAsync(); @@ -571,7 +571,7 @@ public class PrivilegeCrudHandler { public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_LOCALE); // get User @@ -603,7 +603,7 @@ public class PrivilegeCrudHandler { public void requirePasswordChange(Certificate certificate, String username) throws PrivilegeException { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REQUIRE_PASSWORD_CHANGE); // get User @@ -637,7 +637,7 @@ public class PrivilegeCrudHandler { try { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD); // get User @@ -651,13 +651,13 @@ public class PrivilegeCrudHandler { if (password != null) { // validate password meets basic requirements - privilegeHandler.validatePassword(certificate.getLocale(), password); + this.privilegeHandler.validatePassword(certificate.getLocale(), password); // get new salt for user - byte[] salt = privilegeHandler.getEncryptionHandler().nextSalt(); + byte[] salt = this.privilegeHandler.getEncryptionHandler().nextSalt(); // hash password - passwordCrypt = privilegeHandler.getEncryptionHandler().hashPassword(password, salt); + passwordCrypt = this.privilegeHandler.getEncryptionHandler().hashPassword(password, salt); history = history.withLastPasswordChange(ZonedDateTime.now()); } @@ -680,7 +680,7 @@ public class PrivilegeCrudHandler { this.privilegeHandler.persistModelAsync(); if (certificate.getUsage() == Usage.SET_PASSWORD) - privilegeHandler.invalidate(certificate); + this.privilegeHandler.invalidate(certificate); if (password == null) DefaultPrivilegeHandler.logger.info("Cleared password for " + newUser.getUsername()); @@ -695,7 +695,7 @@ public class PrivilegeCrudHandler { public UserRep setUserState(Certificate certificate, String username, UserState state) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_SET_USER_STATE); // get User @@ -725,7 +725,7 @@ public class PrivilegeCrudHandler { public RoleRep addRole(Certificate certificate, RoleRep roleRep) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_ADD_ROLE); // first validate role @@ -759,7 +759,7 @@ public class PrivilegeCrudHandler { public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_MODIFY_ROLE); // first validate role @@ -800,13 +800,13 @@ public class PrivilegeCrudHandler { public RoleRep removeRole(Certificate certificate, String roleName) { // validate user actually has this type of privilege - PrivilegeContext prvCtx = privilegeHandler.validate(certificate); + PrivilegeContext prvCtx = this.privilegeHandler.validate(certificate); prvCtx.assertHasPrivilege(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_ROLE); // validate no user is using this role Set roles = new HashSet<>(Collections.singletonList(roleName)); UserRep selector = new UserRep(null, null, null, null, null, null, roles, null, null, null); - List usersWithRole = privilegeHandler.queryUsers(certificate, selector); + List usersWithRole = this.privilegeHandler.queryUsers(certificate, selector); if (!usersWithRole.isEmpty()) { String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; From 4b3a537171ddcd3a9549d095ced2b52da953de37 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 14:23:59 +0200 Subject: [PATCH 21/39] [Major] Refactored SessionHandler by moving to module agent --- .../DefaultStrolchSessionHandler.java | 8 +++--- .../sessions}/StrolchSessionHandler.java | 3 +-- .../runtime/sessions}/UserSession.java | 2 +- .../users/PrivilegeRemoveUserCommand.java | 3 +++ .../strolch/rest/RestfulStrolchComponent.java | 1 + .../rest/endpoint/AuthenticationService.java | 2 +- .../rest/endpoint/PrivilegeUsersService.java | 2 +- .../rest/endpoint/UserSessionsService.java | 4 +-- .../filters/AuthenticationRequestFilter.java | 26 +++++++++---------- .../config/StrolchConfiguration.xml | 4 +-- .../li/strolch/websocket/WebSocketClient.java | 2 +- 11 files changed, 28 insertions(+), 29 deletions(-) rename {web-rest/src/main/java/li/strolch/rest => agent/src/main/java/li/strolch/runtime/sessions}/DefaultStrolchSessionHandler.java (97%) rename {web-rest/src/main/java/li/strolch/rest => agent/src/main/java/li/strolch/runtime/sessions}/StrolchSessionHandler.java (99%) rename {web-rest/src/main/java/li/strolch/rest/model => agent/src/main/java/li/strolch/runtime/sessions}/UserSession.java (98%) diff --git a/web-rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java similarity index 97% rename from web-rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java rename to agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java index df3f3eef9..118819033 100644 --- a/web-rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java +++ b/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.rest; +package li.strolch.runtime.sessions; import static java.util.function.Function.identity; import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_GET_SESSION; @@ -21,7 +21,6 @@ import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIV import java.text.MessageFormat; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; @@ -38,7 +37,6 @@ import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.SimpleRestrictable; import li.strolch.privilege.model.Usage; -import li.strolch.rest.model.UserSession; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.utils.dbc.DBC; @@ -321,8 +319,8 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St } private void checkSessionsForTimeout() { - ZonedDateTime maxKeepAliveTime = ZonedDateTime.now().minus(this.maxKeepAliveMinutes, ChronoUnit.MINUTES); - ZonedDateTime timeOutTime = ZonedDateTime.now().minus(this.sessionTtlMinutes, ChronoUnit.MINUTES); + ZonedDateTime maxKeepAliveTime = ZonedDateTime.now().minusMinutes(this.maxKeepAliveMinutes); + ZonedDateTime timeOutTime = ZonedDateTime.now().minusMinutes(this.sessionTtlMinutes); Map certificateMap = getCertificateMapCopy(); for (Certificate certificate : certificateMap.values()) { diff --git a/web-rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java b/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java similarity index 99% rename from web-rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java rename to agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java index d3359ed0c..c3c0267a5 100644 --- a/web-rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java +++ b/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.rest; +package li.strolch.runtime.sessions; import java.util.List; import java.util.Locale; @@ -24,7 +24,6 @@ import li.strolch.privilege.base.PrivilegeException; import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Usage; -import li.strolch.rest.model.UserSession; /** * The {@link StrolchSessionHandler} implements session management. It authenticates, validates and invalidates session diff --git a/web-rest/src/main/java/li/strolch/rest/model/UserSession.java b/agent/src/main/java/li/strolch/runtime/sessions/UserSession.java similarity index 98% rename from web-rest/src/main/java/li/strolch/rest/model/UserSession.java rename to agent/src/main/java/li/strolch/runtime/sessions/UserSession.java index c39aef21f..67fc36940 100644 --- a/web-rest/src/main/java/li/strolch/rest/model/UserSession.java +++ b/agent/src/main/java/li/strolch/runtime/sessions/UserSession.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.rest.model; +package li.strolch.runtime.sessions; import java.time.ZonedDateTime; import java.util.Locale; diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java index 4435fbd8a..54c4ab903 100644 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java +++ b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java @@ -7,6 +7,7 @@ import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.service.api.Command; import li.strolch.utils.dbc.DBC; @@ -37,6 +38,8 @@ public class PrivilegeRemoveUserCommand extends Command { if (privilegeHandler.isPersistOnUserDataChanged()) privilegeHandler.persist(tx().getCertificate()); + getComponent(StrolchSessionHandler.class).invalidate(tx().getCertificate()); + Audit audit = tx().auditFrom(AccessType.DELETE, PRIVILEGE, USER, this.username); tx().getAuditTrail().add(tx(), audit); } diff --git a/web-rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java b/web-rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java index 51283268b..d25d616ab 100644 --- a/web-rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java +++ b/web-rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java @@ -25,6 +25,7 @@ import li.strolch.rest.filters.AccessControlResponseFilter; import li.strolch.rest.filters.HttpCacheResponseFilter; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.service.api.ServiceHandler; import li.strolch.utils.dbc.DBC; diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index 288c7f49c..d288b94b3 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -41,7 +41,7 @@ import li.strolch.privilege.model.Privilege; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.privilege.model.Usage; import li.strolch.rest.RestfulStrolchComponent; -import li.strolch.rest.StrolchSessionHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.rest.helper.ResponseUtil; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.utils.helper.ExceptionHelper; diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java index 1c8586b01..087e78511 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java @@ -33,7 +33,7 @@ import li.strolch.privilege.model.UserRep; import li.strolch.privilege.model.UserState; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; -import li.strolch.rest.StrolchSessionHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.rest.model.QueryData; import li.strolch.search.SearchResult; import li.strolch.search.ValueSearch; diff --git a/web-rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java b/web-rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java index 16d5e2264..d1497a9db 100644 --- a/web-rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java +++ b/web-rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java @@ -37,10 +37,10 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; -import li.strolch.rest.StrolchSessionHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.rest.helper.ResponseUtil; import li.strolch.rest.model.QueryData; -import li.strolch.rest.model.UserSession; +import li.strolch.runtime.sessions.UserSession; import li.strolch.search.SearchResult; import li.strolch.search.ValueSearch; import org.slf4j.Logger; diff --git a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java index bd1d0cc2a..770937818 100644 --- a/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java +++ b/web-rest/src/main/java/li/strolch/rest/filters/AuthenticationRequestFilter.java @@ -15,17 +15,22 @@ */ package li.strolch.rest.filters; -import static li.strolch.rest.StrolchRestfulConstants.*; -import static li.strolch.utils.helper.StringHelper.*; - import jakarta.annotation.Priority; import jakarta.servlet.http.HttpServletRequest; - import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.core.*; import jakarta.ws.rs.ext.Provider; +import li.strolch.exception.StrolchAccessDeniedException; +import li.strolch.exception.StrolchNotAuthenticatedException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.Usage; +import li.strolch.rest.RestfulStrolchComponent; +import li.strolch.rest.StrolchRestfulConstants; +import li.strolch.runtime.sessions.StrolchSessionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -33,15 +38,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import li.strolch.exception.StrolchAccessDeniedException; -import li.strolch.exception.StrolchNotAuthenticatedException; -import li.strolch.privilege.model.Certificate; -import li.strolch.privilege.model.Usage; -import li.strolch.rest.RestfulStrolchComponent; -import li.strolch.rest.StrolchRestfulConstants; -import li.strolch.rest.StrolchSessionHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static li.strolch.rest.StrolchRestfulConstants.*; +import static li.strolch.utils.helper.StringHelper.*; /** * This authentication request filter secures any requests to a Strolch server, by verifying that the request contains @@ -212,7 +210,7 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter { logger.error( "No Authorization header or cookie on request to URL " + requestContext.getUriInfo().getPath()); requestContext.abortWith( - Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) + Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN) .entity("Missing Authorization!").build()); return null; } diff --git a/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml b/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml index 94726b0d8..3a9712454 100644 --- a/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml +++ b/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml @@ -39,8 +39,8 @@ SessionHandler - li.strolch.rest.StrolchSessionHandler - li.strolch.rest.DefaultStrolchSessionHandler + li.strolch.runtime.sessions.StrolchSessionHandler + li.strolch.runtime.sessions.DefaultStrolchSessionHandler 1 diff --git a/websocket/src/main/java/li/strolch/websocket/WebSocketClient.java b/websocket/src/main/java/li/strolch/websocket/WebSocketClient.java index 52fa2502b..54a635f78 100644 --- a/websocket/src/main/java/li/strolch/websocket/WebSocketClient.java +++ b/websocket/src/main/java/li/strolch/websocket/WebSocketClient.java @@ -23,7 +23,7 @@ import li.strolch.agent.api.StrolchAgent; import li.strolch.exception.StrolchNotAuthenticatedException; import li.strolch.model.Tags; import li.strolch.privilege.model.Certificate; -import li.strolch.rest.StrolchSessionHandler; +import li.strolch.runtime.sessions.StrolchSessionHandler; import li.strolch.utils.helper.ExceptionHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 03decdcaf8c7671ff008008e14d47557f0d8fb1f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 15:08:19 +0200 Subject: [PATCH 22/39] [Fix] Fixed broken test --- .../test/java/li/strolch/privilege/test/XmlTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index 2f3bf4728..06f09948d 100644 --- a/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java +++ b/privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -292,7 +292,7 @@ public class XmlTest { // PrivilegeAdmin Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); - assertEquals(18, privilegeAdmin.getPrivilegeNames().size()); + assertEquals(16, privilegeAdmin.getPrivilegeNames().size()); Privilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); assertFalse(privilegeAction.isAllAllowed()); assertEquals(5, privilegeAction.getAllowList().size()); @@ -304,11 +304,10 @@ public class XmlTest { assertEquals(0, privilegeAddRole.getAllowList().size()); assertEquals(0, privilegeAddRole.getDenyList().size()); - Privilege privilegeRemRoleFromUser = privilegeAdmin.getPrivilege( - PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); - assertTrue(privilegeRemRoleFromUser.isAllAllowed()); - assertEquals(0, privilegeRemRoleFromUser.getAllowList().size()); - assertEquals(0, privilegeRemRoleFromUser.getDenyList().size()); + Privilege privilegeRemRole = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_REMOVE_ROLE); + assertTrue(privilegeRemRole.isAllAllowed()); + assertEquals(0, privilegeRemRole.getAllowList().size()); + assertEquals(0, privilegeRemRole.getDenyList().size()); // AppUser Role appUser = findRole("AppUser", roles); From 0f3767a73b9cdb28fb14b3bac96404b1604f740b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 15:50:45 +0200 Subject: [PATCH 23/39] [New] Added SessionHandler.refreshSessions(), removed session.reload property --- .../DefaultStrolchSessionHandler.java | 90 ++++++++----------- .../sessions/StrolchSessionHandler.java | 5 ++ 2 files changed, 42 insertions(+), 53 deletions(-) diff --git a/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java b/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java index 118819033..d080e039b 100644 --- a/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java +++ b/agent/src/main/java/li/strolch/runtime/sessions/DefaultStrolchSessionHandler.java @@ -15,19 +15,6 @@ */ package li.strolch.runtime.sessions; -import static java.util.function.Function.identity; -import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_GET_SESSION; -import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_INVALIDATE_SESSION; - -import java.text.MessageFormat; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchNotAuthenticatedException; @@ -43,6 +30,19 @@ import li.strolch.utils.dbc.DBC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.text.MessageFormat; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static java.util.function.Function.identity; +import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_GET_SESSION; +import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_INVALIDATE_SESSION; + /** * @author Robert von Burg */ @@ -50,12 +50,10 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St public static final String PARAM_SESSION_TTL_MINUTES = "session.ttl.minutes"; public static final String PARAM_SESSION_MAX_KEEP_ALIVE_MINUTES = "session.maxKeepAlive.minutes"; - public static final String PARAM_SESSION_RELOAD_SESSIONS = "session.reload"; private static final Logger logger = LoggerFactory.getLogger(DefaultStrolchSessionHandler.class); private PrivilegeHandler privilegeHandler; private final Map certificateMap; - private boolean reloadSessions; private int sessionTtlMinutes; private int maxKeepAliveMinutes; @@ -72,6 +70,29 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St return this.sessionTtlMinutes; } + @Override + public void refreshSessions() { + Map certificates; + try { + certificates = runAsAgentWithResult(ctx -> { + Certificate cert = ctx.getCertificate(); + return this.privilegeHandler.getPrivilegeHandler().getCertificates(cert).stream() + .filter(c -> !c.getUserState().isSystem()) + .collect(Collectors.toMap(Certificate::getAuthToken, identity())); + }); + } catch (Exception e) { + throw new IllegalStateException("Failed to refresh sessions!", e); + } + + synchronized (this.certificateMap) { + this.certificateMap.clear(); + this.certificateMap.putAll(certificates); + } + checkSessionsForTimeout(); + logger.info("Restored " + certificates.size() + " sessions of which " + + (certificates.size() - this.certificateMap.size()) + " had timed out and were removed."); + } + @Override public int getSessionMaxKeepAliveMinutes() { return this.maxKeepAliveMinutes; @@ -87,30 +108,14 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St this.sessionTtlMinutes = configuration.getInt(PARAM_SESSION_TTL_MINUTES, 30); this.maxKeepAliveMinutes = configuration.getInt(PARAM_SESSION_MAX_KEEP_ALIVE_MINUTES, Math.max(this.sessionTtlMinutes, 30)); - this.reloadSessions = configuration.getBoolean(PARAM_SESSION_RELOAD_SESSIONS, false); super.initialize(configuration); } @Override public void start() throws Exception { this.privilegeHandler = getContainer().getComponent(PrivilegeHandler.class); - this.certificateMap.clear(); - if (this.reloadSessions) { - Map certificates = runAsAgentWithResult(ctx -> { - Certificate cert = ctx.getCertificate(); - return this.privilegeHandler.getPrivilegeHandler() - .getCertificates(cert) - .stream() - .filter(c -> !c.getUserState().isSystem()) - .collect(Collectors.toMap(Certificate::getAuthToken, identity())); - }); - this.certificateMap.putAll(certificates); - - checkSessionsForTimeout(); - logger.info("Restored " + certificates.size() + " sessions of which " + (certificates.size() - - this.certificateMap.size()) + " had timed out and were removed."); - } + refreshSessions(); this.validateSessionsTask = getScheduledExecutor("SessionHandler").scheduleWithFixedDelay( this::checkSessionsForTimeout, 5, 1, TimeUnit.MINUTES); @@ -124,27 +129,6 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St if (this.validateSessionsTask != null) this.validateSessionsTask.cancel(true); - if (this.reloadSessions) { - - if (this.privilegeHandler != null) - persistSessions(); - - } else { - Map certificateMap; - synchronized (this.certificateMap) { - certificateMap = new HashMap<>(this.certificateMap); - this.certificateMap.clear(); - } - for (Certificate certificate : certificateMap.values()) { - try { - this.privilegeHandler.invalidate(certificate); - } catch (Exception e) { - logger.error("Failed to invalidate certificate " + certificate, e); - } - } - - } - this.privilegeHandler = null; super.stop(); } diff --git a/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java b/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java index c3c0267a5..02d83fc4e 100644 --- a/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java +++ b/agent/src/main/java/li/strolch/runtime/sessions/StrolchSessionHandler.java @@ -33,6 +33,11 @@ import li.strolch.privilege.model.Usage; */ public interface StrolchSessionHandler { + /** + * Refreshes the sessions from the {@link li.strolch.runtime.privilege.PrivilegeHandler} + */ + void refreshSessions(); + /** * Returns the time to live for a session in minutes * From 5e7f5c88be9f5160abb4fbfb69d501897c689dad Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 15:51:19 +0200 Subject: [PATCH 24/39] [Project] Updated session handler properties --- .../withPrivilegeRuntime/config/StrolchConfiguration.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml b/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml index 3a9712454..874425150 100644 --- a/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml +++ b/web-rest/src/test/resources/withPrivilegeRuntime/config/StrolchConfiguration.xml @@ -42,7 +42,8 @@ li.strolch.runtime.sessions.StrolchSessionHandler li.strolch.runtime.sessions.DefaultStrolchSessionHandler - 1 + 1440 + 10080 From f2f5a4481eac23b11d900981f82824f0588b34d6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Oct 2023 15:51:48 +0200 Subject: [PATCH 25/39] [Fix] PrivilegeRemoveUserCommand refreshes sessions, and not invalidates its own session --- .../service/privilege/users/PrivilegeRemoveUserCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java index 54c4ab903..6064c3ddc 100644 --- a/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java +++ b/service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserCommand.java @@ -38,7 +38,7 @@ public class PrivilegeRemoveUserCommand extends Command { if (privilegeHandler.isPersistOnUserDataChanged()) privilegeHandler.persist(tx().getCertificate()); - getComponent(StrolchSessionHandler.class).invalidate(tx().getCertificate()); + getComponent(StrolchSessionHandler.class).refreshSessions(); Audit audit = tx().auditFrom(AccessType.DELETE, PRIVILEGE, USER, this.username); tx().getAuditTrail().add(tx(), audit); From 6d0b3f5fb9e0df6fae89f910b14d75bc9512979e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 6 Oct 2023 17:53:10 +0200 Subject: [PATCH 26/39] [Minor] Rewrote StringHelper.toPrettyHexString() --- .../li/strolch/utils/helper/StringHelper.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/utils/src/main/java/li/strolch/utils/helper/StringHelper.java b/utils/src/main/java/li/strolch/utils/helper/StringHelper.java index 21fa617f5..d2029c32d 100644 --- a/utils/src/main/java/li/strolch/utils/helper/StringHelper.java +++ b/utils/src/main/java/li/strolch/utils/helper/StringHelper.java @@ -85,23 +85,16 @@ public class StringHelper { } public static String toPrettyHexString(byte[] raw, int srcPos, int length) { - byte[] hex = new byte[3 * length + (length / 8)]; - int index = srcPos; - - for (int i = srcPos; i < length; i++) { - byte b = raw[i]; - int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; - hex[index++] = ' '; - - if ((i + 1) % 8 == 0) { - hex[index++] = ' '; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(String.format("%02x", raw[i + srcPos])); + sb.append(' '); + if ((i + srcPos) % 8 == 0) { + sb.append(' '); } } - return new String(hex, StandardCharsets.US_ASCII); - + return sb.toString(); } public static byte[] fromPrettyHexString(String prettyHex) { From be0c3a806cf827d1598847a7a79b31f657307490 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 9 Oct 2023 14:44:35 +0200 Subject: [PATCH 27/39] [Minor] Code cleanup --- utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java b/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java index 54eb033c2..dfabc6100 100644 --- a/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java +++ b/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java @@ -36,7 +36,7 @@ public class NetworkHelper { public static String formatMacAddress(byte[] bytes) { StringBuilder sb = new StringBuilder(17); for (byte b : bytes) { - if (sb.length() > 0) + if (!sb.isEmpty()) sb.append(':'); sb.append(String.format("%02x", b)); } From 87a90b5a12204b742ddf74e7323a0e8cb63d6cba Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 9 Oct 2023 17:23:56 +0200 Subject: [PATCH 28/39] [Minor] Update logging in BaseLdapPrivilegeHandler --- .../strolch/privilege/handler/BaseLdapPrivilegeHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 2dfddc305..024ed5a22 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -129,9 +129,11 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { return user; + } catch (AccessDeniedException e) { + throw e; } catch (Exception e) { - logger.error("Could not login with user: " + username + this.domain + " on Ldap", e); - throw new AccessDeniedException("Could not login with user: " + username + this.domain + " on Ldap", e); + logger.error("Could not login with user: " + username + " on Ldap", e); + throw new AccessDeniedException("Could not login with user: " + username + " on Ldap", e); } finally { if (ctx != null) { try { From 4e85333da336dcd11a373c2303eb67b17f6c283b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 10 Oct 2023 11:03:22 +0200 Subject: [PATCH 29/39] [Major] Handling PartialResultException in BaseLdapPrivilegeHandler --- .../handler/BaseLdapPrivilegeHandler.java | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 024ed5a22..3136cd68c 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -5,12 +5,14 @@ import li.strolch.privilege.model.UserState; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.helper.ExceptionHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; +import javax.naming.PartialResultException; import javax.naming.directory.*; import java.util.Hashtable; import java.util.Locale; @@ -18,8 +20,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; -import static li.strolch.utils.helper.StringHelper.isNotEmpty; -import static li.strolch.utils.helper.StringHelper.trimOrEmpty; +import static li.strolch.utils.helper.StringHelper.*; public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { @@ -29,6 +30,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { private String searchBase; private String additionalFilter; private String domain; + private String domainPrefix; @Override public void initialize(ScheduledExecutorService executorService, Map parameterMap, @@ -55,6 +57,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { } logger.info("domain: " + this.domain); + this.domainPrefix = this.domain + "\\"; } } @@ -66,7 +69,16 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { if (internalUser != null && internalUser.getUserState() != UserState.REMOTE) return super.checkCredentialsAndUserState(username, password); - String userPrincipalName = username + "@" + this.domain; + String userPrincipalName; + if (isEmpty(this.domain)) { + userPrincipalName = username; + } else { + if (!this.domainPrefix.isEmpty() && username.startsWith(this.domainPrefix)) { + logger.warn("Trimming domain from given username, to first search in sAMAccountName"); + username = username.substring(this.domainPrefix.length()); + } + userPrincipalName = username + "@" + this.domain; + } // Set up the environment for creating the initial context Hashtable env = new Hashtable<>(); @@ -78,6 +90,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, userPrincipalName); env.put(Context.SECURITY_CREDENTIALS, new String(password)); + env.put(Context.REFERRAL, "ignore"); logger.info("User {} tries to login on ldap {}", username, this.providerUrl); @@ -87,35 +100,45 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { ctx = new InitialDirContext(env); //Create the search controls - SearchControls searchCtls = new SearchControls(); + SearchControls searchControls = new SearchControls(); //Specify the search scope - searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); String searchFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s)%s)".formatted( username, this.additionalFilter); // Search for objects using the filter - NamingEnumeration answer = ctx.search(this.searchBase, searchFilter, searchCtls); + NamingEnumeration answer = ctx.search(this.searchBase, searchFilter, searchControls); - if (!answer.hasMore()) { + SearchResult searchResult = null; + while (searchResult == null) { + try { + if (!answer.hasMore()) { - logger.warn("No LDAP data retrieved using sAMAccountName, trying with userPrincipalName..."); - searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=%s)%s)".formatted( - userPrincipalName, this.additionalFilter); - answer = ctx.search(this.searchBase, searchFilter, searchCtls); + logger.warn("No LDAP data retrieved using sAMAccountName, trying with userPrincipalName..."); + searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=%s)%s)".formatted( + userPrincipalName, this.additionalFilter); + answer = ctx.search(this.searchBase, searchFilter, searchControls); - if (!answer.hasMore()) - throw new AccessDeniedException("Could not login user: " + username + - " on Ldap: no LDAP Data, for either sAMAccountName or userPrincipalName searches. Domain used is " + - this.domain); + if (!answer.hasMore()) + throw new AccessDeniedException("Could not login user: " + username + + " on Ldap: no LDAP Data, for either sAMAccountName or userPrincipalName searches. Domain used is " + + this.domain); + } + + searchResult = answer.next(); + if (answer.hasMore()) + throw new AccessDeniedException( + "Could not login with user: " + username + " on Ldap: Multiple LDAP Data"); + } catch (PartialResultException e) { + if (ExceptionHelper.getExceptionMessage(e).contains("Unprocessed Continuation Reference(s)")) + logger.warn("Ignoring partial result exception, as we are not following referrals!"); + else + throw e; + } } - SearchResult searchResult = answer.next(); - if (answer.hasMore()) - throw new AccessDeniedException( - "Could not login with user: " + username + " on Ldap: Multiple LDAP Data"); - User user = buildUserFromSearchResult(username, searchResult); // persist this user From 9cb12e5a5841025540f2cda09498f4cf0f748cc3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 10 Oct 2023 11:28:05 +0200 Subject: [PATCH 30/39] [Major] Refactoring in BaseLdapPrivilegeHandler --- .../handler/BaseLdapPrivilegeHandler.java | 133 ++++++++++-------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 3136cd68c..93e264868 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -20,11 +20,14 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; -import static li.strolch.utils.helper.StringHelper.*; +import static li.strolch.utils.helper.StringHelper.trimOrEmpty; public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(BaseLdapPrivilegeHandler.class); + public static final String LDAP_FILTER_TEMPLATE = "(&(objectCategory=person)(objectClass=user)(%s=%s)%s)"; + public static final String SAM_ACCOUNT_NAME = "sAMAccountName"; + public static final String USER_PRINCIPAL_NAME = "userPrincipalName"; private String providerUrl; private String searchBase; @@ -46,18 +49,19 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { this.searchBase = parameterMap.get("searchBase"); logger.info("searchBase: " + this.searchBase); this.additionalFilter = trimOrEmpty(parameterMap.get("additionalFilter")); - if (isNotEmpty(this.additionalFilter)) + if (!this.additionalFilter.isEmpty()) logger.info("additionalFilter: " + this.additionalFilter); - this.domain = parameterMap.get("domain"); - if (isNotEmpty(this.domain)) { + this.domain = trimOrEmpty(parameterMap.get("domain")); + if (!this.domain.isEmpty()) { if (this.domain.startsWith("@")) { logger.warn( "Remove the @ symbol from the domain property! Will be added automatically where required."); this.domain = this.domain.substring(1); } + this.domainPrefix = this.domain + '\\'; logger.info("domain: " + this.domain); - this.domainPrefix = this.domain + "\\"; + logger.info("domain prefix: " + this.domainPrefix); } } @@ -70,7 +74,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { return super.checkCredentialsAndUserState(username, password); String userPrincipalName; - if (isEmpty(this.domain)) { + if (this.domain.isEmpty()) { userPrincipalName = username; } else { if (!this.domainPrefix.isEmpty() && username.startsWith(this.domainPrefix)) { @@ -80,65 +84,13 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { userPrincipalName = username + "@" + this.domain; } - // Set up the environment for creating the initial context - Hashtable env = new Hashtable<>(); - - env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, this.providerUrl); - - // Authenticate - env.put(Context.SECURITY_AUTHENTICATION, "simple"); - env.put(Context.SECURITY_PRINCIPAL, userPrincipalName); - env.put(Context.SECURITY_CREDENTIALS, new String(password)); - env.put(Context.REFERRAL, "ignore"); - logger.info("User {} tries to login on ldap {}", username, this.providerUrl); // Create the initial context DirContext ctx = null; try { - ctx = new InitialDirContext(env); - - //Create the search controls - SearchControls searchControls = new SearchControls(); - - //Specify the search scope - searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - String searchFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s)%s)".formatted( - username, this.additionalFilter); - - // Search for objects using the filter - NamingEnumeration answer = ctx.search(this.searchBase, searchFilter, searchControls); - - SearchResult searchResult = null; - while (searchResult == null) { - try { - if (!answer.hasMore()) { - - logger.warn("No LDAP data retrieved using sAMAccountName, trying with userPrincipalName..."); - searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=%s)%s)".formatted( - userPrincipalName, this.additionalFilter); - answer = ctx.search(this.searchBase, searchFilter, searchControls); - - if (!answer.hasMore()) - throw new AccessDeniedException("Could not login user: " + username + - " on Ldap: no LDAP Data, for either sAMAccountName or userPrincipalName searches. Domain used is " + - this.domain); - } - - searchResult = answer.next(); - if (answer.hasMore()) - throw new AccessDeniedException( - "Could not login with user: " + username + " on Ldap: Multiple LDAP Data"); - } catch (PartialResultException e) { - if (ExceptionHelper.getExceptionMessage(e).contains("Unprocessed Continuation Reference(s)")) - logger.warn("Ignoring partial result exception, as we are not following referrals!"); - else - throw e; - } - } - + ctx = new InitialDirContext(buildLdapEnv(password, userPrincipalName)); + SearchResult searchResult = searchLdap(username, ctx, userPrincipalName); User user = buildUserFromSearchResult(username, searchResult); // persist this user @@ -168,6 +120,65 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { } } + private Hashtable buildLdapEnv(char[] password, String userPrincipalName) { + + // Set up the environment for creating the initial context + Hashtable env = new Hashtable<>(); + + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, this.providerUrl); + + // Authenticate + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, userPrincipalName); + env.put(Context.SECURITY_CREDENTIALS, new String(password)); + env.put(Context.REFERRAL, "ignore"); + return env; + } + + private SearchResult searchLdap(String username, DirContext ctx, String userPrincipalName) throws NamingException { + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + // the first search is using sAMAccountName + NamingEnumeration answer = ctx.search(this.searchBase, + LDAP_FILTER_TEMPLATE.formatted(username, SAM_ACCOUNT_NAME, this.additionalFilter), searchControls); + + SearchResult searchResult = null; + while (searchResult == null) { + try { + + // and if we don't find anything, then we search with userPrincipalName + if (!answer.hasMore()) { + + logger.warn("No LDAP data retrieved using " + SAM_ACCOUNT_NAME + ", trying with " + + USER_PRINCIPAL_NAME + "..."); + answer = ctx.search(this.searchBase, + LDAP_FILTER_TEMPLATE.formatted(USER_PRINCIPAL_NAME, userPrincipalName, + this.additionalFilter), searchControls); + + if (!answer.hasMore()) + throw new AccessDeniedException("Could not login user: " + username + + " on Ldap: no LDAP Data, for either sAMAccountName or userPrincipalName searches. Domain used is " + + this.domain); + } + + searchResult = answer.next(); + if (answer.hasMore()) + throw new AccessDeniedException( + "Could not login with user: " + username + " on Ldap: Multiple LDAP Data"); + + } catch (PartialResultException e) { + if (ExceptionHelper.getExceptionMessage(e).contains("Unprocessed Continuation Reference(s)")) + logger.warn("Ignoring partial result exception, as we are not following referrals!"); + else + throw e; + } + } + + return searchResult; + } + protected User buildUserFromSearchResult(String username, SearchResult sr) throws Exception { Attributes attrs = sr.getAttributes(); @@ -194,7 +205,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { Set strolchRoles) throws Exception; protected String validateLdapUsername(String username, Attributes attrs) throws NamingException { - Attribute sAMAccountName = attrs.get("sAMAccountName"); + Attribute sAMAccountName = attrs.get(SAM_ACCOUNT_NAME); if (sAMAccountName == null || !username.equalsIgnoreCase(sAMAccountName.get().toString())) throw new AccessDeniedException( "Could not login with user: " + username + this.domain + " on Ldap: Wrong LDAP Data"); From 6fc822fe523d1c9bc1d760438699d1d9bd785648 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 10 Oct 2023 11:57:59 +0200 Subject: [PATCH 31/39] [Major] Refactoring in BaseLdapPrivilegeHandler --- .../li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 93e264868..711b62d74 100644 --- a/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -142,7 +142,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { // the first search is using sAMAccountName NamingEnumeration answer = ctx.search(this.searchBase, - LDAP_FILTER_TEMPLATE.formatted(username, SAM_ACCOUNT_NAME, this.additionalFilter), searchControls); + LDAP_FILTER_TEMPLATE.formatted(SAM_ACCOUNT_NAME, username, this.additionalFilter), searchControls); SearchResult searchResult = null; while (searchResult == null) { From da946f1acb8d548c7069895b0b32f5ca54f1fa63 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 10 Oct 2023 16:23:33 +0200 Subject: [PATCH 32/39] [New] Added new NetworkHelper.streamStrolchNetworkInterface() This now streams over all interfaces, no matter if it is running or not. --- .../strolch/utils/helper/NetworkHelper.java | 109 ++++++++++++++++-- .../utils/helper/StrolchNetworkInterface.java | 12 ++ 2 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 utils/src/main/java/li/strolch/utils/helper/StrolchNetworkInterface.java diff --git a/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java b/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java index dfabc6100..0cec120ae 100644 --- a/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java +++ b/utils/src/main/java/li/strolch/utils/helper/NetworkHelper.java @@ -1,12 +1,19 @@ package li.strolch.utils.helper; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.nio.file.Path; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static java.nio.file.Files.readAllLines; +import static java.nio.file.Files.readString; +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterators.spliteratorUnknownSize; public class NetworkHelper { @@ -33,6 +40,15 @@ public class NetworkHelper { return inet4Addresses; } + public static String formatMacAddress(NetworkInterface networkInterface) { + try { + return formatMacAddress(networkInterface.getHardwareAddress()); + } catch (SocketException e) { + throw new IllegalStateException( + "Failed to get hardware address for network interface " + networkInterface.getDisplayName(), e); + } + } + public static String formatMacAddress(byte[] bytes) { StringBuilder sb = new StringBuilder(17); for (byte b : bytes) { @@ -42,4 +58,83 @@ public class NetworkHelper { } return sb.toString(); } + + public static void main(String[] args) throws IOException { + + Stream interfaceStream = streamStrolchNetworkInterface(); + + interfaceStream.forEach(t -> System.out.printf("%20s: %s%n", t.name(), t.hwAddress())); + } + + public static Stream streamStrolchNetworkInterface() { + + if (!System.getProperty("os.name").equals("Linux")) { + Enumeration interfaces = null; + try { + interfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new IllegalStateException("Failed to read network interfaces ", e); + } + return StreamSupport.stream(spliteratorUnknownSize(interfaces.asIterator(), ORDERED), false).map(ni -> { + try { + List addresses = Collections.list(ni.getInetAddresses()); + if (addresses.isEmpty()) + addresses = List.of(Inet4Address.getByName("0.0.0.0")); + return new StrolchNetworkInterface(ni.getName(), formatMacAddress(ni), addresses); + } catch (UnknownHostException e) { + throw new IllegalStateException("Failed to read information for device " + ni.getName(), e); + } + }); + } + + // Read all available device names + Pattern pattern = Pattern.compile("^ *(.*):"); + List kernelInterfaceLines; + try { + kernelInterfaceLines = readAllLines(Path.of("/proc/net/dev")); + } catch (IOException e) { + throw new IllegalStateException("Failed to read /proc/net/dev", e); + } + + return kernelInterfaceLines.stream().map(line -> { + Matcher m = pattern.matcher(line); + if (m.find()) + return m.group(1).trim(); + return null; + }).filter(Objects::nonNull).filter(device -> { + if (device.startsWith("br-")) + return false; + if (device.startsWith("veth")) + return false; + if (device.startsWith("docker")) + return false; + return true; + }).map(device -> { + try { + String hwAddress = readString(Path.of("/sys/class/net/" + device + "/address")).trim(); + File operStateFile = new File("/sys/class/net/" + device + "/operstate"); + File carrierFile = new File("/sys/class/net/" + device + "/carrier"); + boolean hasNoCarrier; + if (operStateFile.exists()) { + hasNoCarrier = operStateFile.exists() && readString(operStateFile.toPath()).trim().equals("down"); + } else { + hasNoCarrier = readString(carrierFile.toPath()).trim().equals("0"); + } + if (hasNoCarrier) + return new StrolchNetworkInterface(device, hwAddress, List.of(Inet4Address.getByName("0.0.0.0"))); + + NetworkInterface ni = NetworkInterface.getByName(device); + if (ni.isLoopback() || ni.isPointToPoint() || ni.isVirtual()) + return null; + + List addresses = Collections.list(ni.getInetAddresses()); + if (addresses.isEmpty()) + addresses = List.of(Inet4Address.getByName("0.0.0.0")); + return new StrolchNetworkInterface(ni.getName(), hwAddress, addresses); + + } catch (IOException e) { + throw new IllegalStateException("Failed to read information for device " + device, e); + } + }).filter(Objects::nonNull); + } } diff --git a/utils/src/main/java/li/strolch/utils/helper/StrolchNetworkInterface.java b/utils/src/main/java/li/strolch/utils/helper/StrolchNetworkInterface.java new file mode 100644 index 000000000..4262691d0 --- /dev/null +++ b/utils/src/main/java/li/strolch/utils/helper/StrolchNetworkInterface.java @@ -0,0 +1,12 @@ +package li.strolch.utils.helper; + +import java.net.InetAddress; +import java.util.List; + +public record StrolchNetworkInterface(String name, String hwAddress, List inetAddresses) { + public StrolchNetworkInterface(String name, String hwAddress, List inetAddresses) { + this.name = name; + this.hwAddress = hwAddress; + this.inetAddresses = List.copyOf(inetAddresses); + } +} From 380f5b65972fa4efac2467aa273da272661046da Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Oct 2023 16:22:05 +0200 Subject: [PATCH 33/39] [New] State.canSetToError() is also for STOPPED --- model/src/main/java/li/strolch/model/State.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/model/src/main/java/li/strolch/model/State.java b/model/src/main/java/li/strolch/model/State.java index 0c1c3181f..0f5d8aa9f 100644 --- a/model/src/main/java/li/strolch/model/State.java +++ b/model/src/main/java/li/strolch/model/State.java @@ -213,10 +213,10 @@ public enum State { } /** - * @return true if {@link #PLANNED} or {@link #EXECUTION} or {@link #WARNING} or {@link #ERROR} + * @return true if {@link #PLANNED} or {@link #EXECUTION} or {@link #WARNING} or {@link #ERROR} or {@link #STOPPED} */ public boolean canSetToError() { - return this == PLANNED || this == EXECUTION || this == WARNING || this == ERROR; + return this == PLANNED || this == EXECUTION || this == WARNING || this == ERROR || this == STOPPED; } /** @@ -282,8 +282,8 @@ public enum State { // execution if (states.contains(EXECUTABLE) || states.contains(EXECUTION)) return EXECUTION; - if (states.contains(EXECUTED) && (states.contains(CREATED) || states.contains(PLANNING) || states.contains( - PLANNED))) + if (states.contains(EXECUTED) && + (states.contains(CREATED) || states.contains(PLANNING) || states.contains(PLANNED))) return EXECUTION; // executed From 0b2df5a3b49e43810e1599c69a8bca951de54099 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Oct 2023 16:22:41 +0200 Subject: [PATCH 34/39] [New] Added check to not set Action to execution state if already executed in ExecutionPolicy.setActionState() --- .../strolch/execution/policy/ExecutionPolicy.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java b/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java index 25963e605..927f3abf2 100644 --- a/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java +++ b/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java @@ -20,6 +20,7 @@ import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegedRunnable; import li.strolch.runtime.privilege.PrivilegedRunnableWithResult; +import li.strolch.utils.time.PeriodDuration; import java.time.Duration; import java.util.concurrent.ThreadLocalRandom; @@ -246,6 +247,9 @@ public abstract class ExecutionPolicy extends StrolchPolicy { * @param state the new state to set */ protected void setActionState(Action action, State state) { + if (action.getState().inClosedPhase()) + throw new IllegalStateException("Action " + action.getLocator() + " has state " + action.getState() + + " and can not be changed to " + state); action.setState(state); @@ -270,6 +274,17 @@ public abstract class ExecutionPolicy extends StrolchPolicy { return action.findObjectivesParam(PARAM_DURATION, true); } + /** + * Delays the given {@link Runnable} by the given {@link PeriodDuration} + * + * @param duration the duration to delay + * @param runnable the action to delay + */ + public void delay(PeriodDuration duration, Runnable runnable) { + long delayMs = duration.toMillis(); + getDelayedExecutionTimer().delay(delayMs, runnable); + } + /** * Delays the given {@link Runnable} by the given {@link Duration} * From 1ce54118885be3c5aff8bad9c44cda959f584eeb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Oct 2023 16:32:14 +0200 Subject: [PATCH 35/39] [Fix] SimpleExecution used wrong locator for changing states --- .../java/li/strolch/execution/policy/SimpleExecution.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/src/main/java/li/strolch/execution/policy/SimpleExecution.java b/service/src/main/java/li/strolch/execution/policy/SimpleExecution.java index 89b408702..461f69b82 100644 --- a/service/src/main/java/li/strolch/execution/policy/SimpleExecution.java +++ b/service/src/main/java/li/strolch/execution/policy/SimpleExecution.java @@ -92,7 +92,7 @@ public class SimpleExecution extends ExecutionPolicy { protected void toWarning(LogMessage message) { cancelWarningTask(); addMessage(message); - getExecutionHandler().toWarning(this.realm, message.getLocator()); + getExecutionHandler().toWarning(this.realm, this.actionLoc); } protected void toExecuted() throws Exception { @@ -126,7 +126,7 @@ public class SimpleExecution extends ExecutionPolicy { stop(); logger.error("Action " + message.getLocator() + " failed because of: " + message.formatMessage()); addMessage(message); - getExecutionHandler().toError(this.realm, message.getLocator()); + getExecutionHandler().toError(this.realm, this.actionLoc); } protected void setActionStateWithValueChange(Action action, State execution, double value) { From 256ab666b3ae11d402a68f29095a53f483b87c38 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 30 Oct 2023 12:03:04 +0100 Subject: [PATCH 36/39] [Minor] Don't allow to call Activity.ensureModifiable() no a sub activity --- model/src/main/java/li/strolch/model/activity/Activity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/model/src/main/java/li/strolch/model/activity/Activity.java b/model/src/main/java/li/strolch/model/activity/Activity.java index 5afce6e22..98188d75f 100644 --- a/model/src/main/java/li/strolch/model/activity/Activity.java +++ b/model/src/main/java/li/strolch/model/activity/Activity.java @@ -740,6 +740,8 @@ public class Activity extends AbstractStrolchRootElement @Override public Activity ensureModifiable() { + if (!this.isRootElement()) + throw new IllegalStateException("Only call this method on the root element!"); if (isReadOnly()) return getClone(true); return this; From e80d0a79445adb3dcdc2f403cf2b9697d3e092c3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 30 Oct 2023 12:03:37 +0100 Subject: [PATCH 37/39] [New] ExecutionHandler.addForExecution() now returns the controller --- .../execution/EventBasedExecutionHandler.java | 54 ++++++++++--------- .../strolch/execution/ExecutionHandler.java | 4 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java b/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java index 3506558bb..58d5a0160 100644 --- a/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java +++ b/service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java @@ -1,15 +1,5 @@ package li.strolch.execution; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static li.strolch.model.StrolchModelConstants.*; -import static li.strolch.runtime.StrolchConstants.SYSTEM_USER_AGENT; -import static li.strolch.utils.collections.SynchronizedCollections.synchronizedMapOfMaps; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.ObserverEvent; import li.strolch.agent.api.StrolchRealm; @@ -28,6 +18,16 @@ import li.strolch.privilege.model.Certificate; import li.strolch.privilege.model.PrivilegeContext; import li.strolch.utils.collections.MapOfMaps; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static li.strolch.model.StrolchModelConstants.*; +import static li.strolch.runtime.StrolchConstants.SYSTEM_USER_AGENT; +import static li.strolch.utils.collections.SynchronizedCollections.synchronizedMapOfMaps; + /** * The event based execution handler waits for events in that the {@link ExecutionPolicy} implementations must call the * relevant methods when the work is complete. Afterwards the next {@link Action} in the procedure is executed @@ -51,12 +51,12 @@ public class EventBasedExecutionHandler extends ExecutionHandler { @Override public boolean isControlling(Activity activity) { - return this.controllers.containsElement(getDefaultRealm(), activity.getLocator()); + return this.controllers.containsElement(getDefaultRealm(), activity.getRootElement().getLocator()); } @Override public boolean isControlling(String realm, Activity activity) { - return this.controllers.containsElement(realm, activity.getLocator()); + return this.controllers.containsElement(realm, activity.getRootElement().getLocator()); } @Override @@ -83,12 +83,12 @@ public class EventBasedExecutionHandler extends ExecutionHandler { @Override public Controller getController(Activity activity) { - return getController(getDefaultRealm(), activity.getLocator()); + return getController(getDefaultRealm(), activity.getRootElement().getLocator()); } @Override public Controller getController(String realm, Activity activity) { - return this.controllers.getElement(realm, activity.getLocator()); + return this.controllers.getElement(realm, activity.getRootElement().getLocator()); } @Override @@ -171,26 +171,28 @@ public class EventBasedExecutionHandler extends ExecutionHandler { } @Override - public void addForExecution(Activity activity) { - addForExecution(getDefaultRealm(), activity); + public Controller addForExecution(Activity activity) { + return addForExecution(getDefaultRealm(), activity); } @Override - public void addForExecution(String realm, Activity activity) { + public Controller addForExecution(String realm, Activity activity) { ExecutionHandlerState state = this.statesByRealm.getOrDefault(realm, ExecutionHandlerState.Running); if (state == ExecutionHandlerState.HaltNew) throw new IllegalStateException( "ExecutionHandler state is " + state + ", can not add activities for execution!"); - if (this.controllers.containsElement(realm, activity.getLocator())) - throw new IllegalStateException(activity.getLocator() + " is already registered for execution!"); + Locator locator = activity.getRootElement().getLocator(); + if (this.controllers.containsElement(realm, locator)) + throw new IllegalStateException(locator + " is already registered for execution!"); - logger.info("Added " + activity.getLocator() + " @ " + realm); + logger.info("Added " + locator + " @ " + realm); Controller controller = newController(realm, activity); - this.controllers.addElement(realm, activity.getLocator(), controller); + this.controllers.addElement(realm, locator, controller); notifyObserverAdd(controller); triggerExecution(realm); + return controller; } @Override @@ -205,10 +207,11 @@ public class EventBasedExecutionHandler extends ExecutionHandler { throw new IllegalStateException( "ExecutionHandler state is " + state + ", can not add activities for execution!"); - Controller controller = this.controllers.getElement(realm, activity.getLocator()); + Locator locator = activity.getRootElement().getLocator(); + Controller controller = this.controllers.getElement(realm, locator); if (controller == null) { controller = newController(realm, activity); - this.controllers.addElement(realm, activity.getLocator(), controller); + this.controllers.addElement(realm, locator, controller); notifyObserverAdd(controller); } @@ -296,7 +299,8 @@ public class EventBasedExecutionHandler extends ExecutionHandler { if (activity.isReadOnly()) activity = activity.getClone(true); - logger.info("Restarting Execution of " + activity.getLocator() + " on realm " + realmName); + Locator locator = activity.getRootElement().getLocator(); + logger.info("Restarting Execution of " + locator + " on realm " + realmName); // in execution actions need to be in state STOPPED to restart activity.findActionsDeep(a -> a.getState().inExecutionPhase()).forEach(a -> { @@ -309,7 +313,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { // register for execution Controller controller = newController(realmName, activity); - this.controllers.addElement(realmName, activity.getLocator(), controller); + this.controllers.addElement(realmName, locator, controller); }); // commit changes to state diff --git a/service/src/main/java/li/strolch/execution/ExecutionHandler.java b/service/src/main/java/li/strolch/execution/ExecutionHandler.java index e88eddb17..97bbe1d7e 100644 --- a/service/src/main/java/li/strolch/execution/ExecutionHandler.java +++ b/service/src/main/java/li/strolch/execution/ExecutionHandler.java @@ -219,7 +219,7 @@ public abstract class ExecutionHandler extends StrolchComponent { * @throws IllegalStateException * if the default realm is not set! */ - public abstract void addForExecution(Activity activity); + public abstract Controller addForExecution(Activity activity); /** * Registers the given {@link Activity} for execution. Execution is started when the concrete implementation deems @@ -230,7 +230,7 @@ public abstract class ExecutionHandler extends StrolchComponent { * @param activity * the {@link Activity} */ - public abstract void addForExecution(String realm, Activity activity); + public abstract Controller addForExecution(String realm, Activity activity); /** * Registers the given {@link Activity} for execution on the default realm, and submits it for execution immediately From 857affbdeac7cb83ab56d56fcdeaccee27eedb36 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 30 Oct 2023 12:04:26 +0100 Subject: [PATCH 38/39] [New] ExecutionPolicy now controls delayed task life cycle --- .../strolch/execution/policy/ExecutionPolicy.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java b/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java index 927f3abf2..277aab548 100644 --- a/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java +++ b/service/src/main/java/li/strolch/execution/policy/ExecutionPolicy.java @@ -23,6 +23,9 @@ import li.strolch.runtime.privilege.PrivilegedRunnableWithResult; import li.strolch.utils.time.PeriodDuration; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -54,6 +57,8 @@ public abstract class ExecutionPolicy extends StrolchPolicy { protected Locator resourceLoc; protected Locator actionLoc; + protected List> futures; + /** * The TX for this execution policy. The TX needs to be updated when this execution policy has a longer life time * than the actual TX @@ -64,6 +69,7 @@ public abstract class ExecutionPolicy extends StrolchPolicy { super(tx); this.tx = tx; this.realm = tx.getRealmName(); + this.futures = new ArrayList<>(); } /** @@ -226,6 +232,7 @@ public abstract class ExecutionPolicy extends StrolchPolicy { public void stop() { this.stopped = true; try { + this.futures.forEach(future -> future.cancel(false)); handleStopped(); } catch (Exception e) { logger.error("Stopping failed for " + this.actionLoc, e); @@ -282,7 +289,7 @@ public abstract class ExecutionPolicy extends StrolchPolicy { */ public void delay(PeriodDuration duration, Runnable runnable) { long delayMs = duration.toMillis(); - getDelayedExecutionTimer().delay(delayMs, runnable); + this.futures.add(getDelayedExecutionTimer().delay(delayMs, runnable)); } /** @@ -293,7 +300,7 @@ public abstract class ExecutionPolicy extends StrolchPolicy { */ public void delay(Duration duration, Runnable runnable) { long delayMs = duration.toMillis(); - getDelayedExecutionTimer().delay(delayMs, runnable); + this.futures.add(getDelayedExecutionTimer().delay(delayMs, runnable)); } /** @@ -336,7 +343,7 @@ public abstract class ExecutionPolicy extends StrolchPolicy { delayMs = 20; } logger.info("Delaying runnable " + runnable + " by " + formatMillisecondsDuration(delayMs)); - getDelayedExecutionTimer().delay(delayMs, runnable); + this.futures.add(getDelayedExecutionTimer().delay(delayMs, runnable)); } /** From 8e7ee2341a089576fc2597a1e5ea7668f3a2fbe7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 30 Oct 2023 12:04:42 +0100 Subject: [PATCH 39/39] [New] Added StrolchPolicy.isTxOpen() --- .../main/java/li/strolch/policy/StrolchPolicy.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/li/strolch/policy/StrolchPolicy.java b/agent/src/main/java/li/strolch/policy/StrolchPolicy.java index 7b169637e..149cae9c4 100644 --- a/agent/src/main/java/li/strolch/policy/StrolchPolicy.java +++ b/agent/src/main/java/li/strolch/policy/StrolchPolicy.java @@ -15,8 +15,6 @@ */ package li.strolch.policy; -import static li.strolch.model.StrolchModelConstants.PolicyConstants.PARAM_ORDER; - import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchComponent; @@ -27,6 +25,8 @@ import li.strolch.service.api.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static li.strolch.model.StrolchModelConstants.PolicyConstants.PARAM_ORDER; + /** * Interface for all Strolch policies, which are instantiated by the {@link PolicyHandler} * @@ -103,6 +103,14 @@ public abstract class StrolchPolicy { return this.tx; } + /** + * Returns true if this TX is still open, or committing, and thus can still be used + * @return true if this TX is still open, or committing, and thus can still be used + */ + protected boolean isTxOpen() { + return this.tx.isOpen() || this.tx.isCommitting(); + } + protected Order getOrder(IActivityElement element) { return tx().getOrderByRelation(element.getRootElement(), PARAM_ORDER, true); }