From c8ba4e5d8bf8072a14d2b320c11a017dd7499b94 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sat, 12 Apr 2025 14:01:19 +0300 Subject: [PATCH 01/26] SED-3921: new settings with multitenancy capabilities (entity, accessor and manager) --- .../step/core/export/ExportManagerTest.java | 28 ++- .../EncryptedEntityImportBiConsumer.java | 73 ++++++ .../EncyptedEntityExportBiConsumer.java | 54 ++++ .../ParameterManagerControllerPlugin.java | 75 +----- .../parametermanager/ParameterServices.java | 26 +- .../UserSettingManagerControllerPlugin.java | 104 ++++++++ .../usersettings/UserSettingServices.java | 36 +++ .../accessors/RemoteUserSettingAccessor.java | 34 +++ .../step/core/EncryptedTrackedObject.java | 82 +++++++ .../main/java/step/parameter/Parameter.java | 60 +---- .../java/step/usersettings/UserSetting.java | 97 ++++++++ .../AbstractEncryptedValuesManager.java | 230 ++++++++++++++++++ .../java/step/parameter/ParameterManager.java | 198 +-------------- .../usersettings/UserSettingAccessor.java | 24 ++ .../step/usersettings/UserSettingManager.java | 45 ++++ 15 files changed, 817 insertions(+), 349 deletions(-) create mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java create mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java create mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java create mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java create mode 100644 step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java create mode 100644 step-core-model/src/main/java/step/core/EncryptedTrackedObject.java create mode 100644 step-core-model/src/main/java/step/usersettings/UserSetting.java create mode 100644 step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java create mode 100644 step-core/src/main/java/step/usersettings/UserSettingAccessor.java create mode 100644 step-core/src/main/java/step/usersettings/UserSettingManager.java diff --git a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java index 9baeef262a..c02847a940 100644 --- a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java +++ b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java @@ -71,7 +71,8 @@ import step.planbuilder.FunctionArtefacts; import step.plugins.functions.types.CompositeFunction; import step.plugins.functions.types.CompositeFunctionType; -import step.plugins.parametermanager.ParameterManagerControllerPlugin; +import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; +import step.plugins.parametermanager.EncyptedEntityExportBiConsumer; import step.resources.*; import java.io.File; @@ -84,6 +85,9 @@ import static org.junit.Assert.*; import static step.planbuilder.BaseArtefacts.callPlan; import static step.planbuilder.BaseArtefacts.sequence; +import static step.plugins.parametermanager.EncryptedEntityImportBiConsumer.*; +import static step.plugins.parametermanager.EncyptedEntityExportBiConsumer.EXPORT_ENCRYPT_PARAM_WARN; +import static step.plugins.parametermanager.EncyptedEntityExportBiConsumer.EXPORT_PROTECT_PARAM_WARN; public class ExportManagerTest { @@ -149,8 +153,8 @@ private void newContext(EncryptionManager encryptionManager) { .register(new ResourceEntity(resourceAccessor, resourceManager, fileResolver, entityManager)) .register(new Entity<>(EntityManager.resourceRevisions, resourceRevisionAccessor, ResourceRevision.class)); - entityManager.registerExportHook(new ParameterManagerControllerPlugin.ParameterExportBiConsumer()); - entityManager.registerImportHook(new ParameterManagerControllerPlugin.ParameterImportBiConsumer(encryptionManager)); + entityManager.registerExportHook(new EncyptedEntityExportBiConsumer(Parameter.class)); + entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); entityManager.registerImportHook(new ResourceImporter(resourceManager)); migrationManager = new MigrationManager(); @@ -293,8 +297,8 @@ public void testExportAllPlansWithParameters() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(2,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[1]); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[1]); + assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); EncryptionManager encryptionManager = new EncryptionManager() { @Override @@ -322,7 +326,7 @@ public boolean isFirstStart() { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); Plan actualPlan = planAccessor.get(plan.getId()); Plan actualPlan2 = planAccessor.get(plan2.getId()); @@ -365,7 +369,7 @@ public void testImportNewEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); //Override previous encryption manager to simulate new instance EncryptionManager encryptionManagerNewInstance = new EncryptionManager() { @@ -391,7 +395,7 @@ public boolean isFirstStart() { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_DECRYPT_FAIL_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_DECRYPT_FAIL_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtectedEncrypted.getId()); assertEquals(savedParamProtectedEncrypted.getId(), actualParamProtectedEncrypted.getId()); @@ -422,7 +426,7 @@ public void testImportNoEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); // Create a new context without encryption manager newContext(null); @@ -430,7 +434,7 @@ public void testImportNoEncryptionManager() throws Exception { ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_DECRYPT_NO_EM_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_DECRYPT_NO_EM_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtectedEncrypted.getId()); assertEquals(savedParamProtectedEncrypted.getId(), actualParamProtectedEncrypted.getId()); @@ -459,13 +463,13 @@ public void testImportProtectedToEncrypted() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[0]); ImportManager importManager = createNewContextAndGetImportManager(); ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); ImportResult importResult = importManager.importAll(importConfiguration); assertEquals(1,importResult.getMessages().size()); - assertEquals(ParameterManagerControllerPlugin.IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); + assertEquals(IMPORT_RESET_WARN,importResult.getMessages().toArray()[0]); Parameter actualParamProtectedEncrypted = parameterAccessor.get(savedParamProtected.getId()); assertEquals(savedParamProtected.getId(), actualParamProtectedEncrypted.getId()); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java new file mode 100644 index 0000000000..c13294bf2a --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.parametermanager; + +import step.core.EncryptedTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.core.encryption.EncryptionManager; +import step.core.encryption.EncryptionManagerException; +import step.core.imports.ImportContext; +import step.parameter.Parameter; +import step.parameter.ParameterManager; + +import java.util.function.BiConsumer; + +public class EncryptedEntityImportBiConsumer implements BiConsumer { + + public static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; + public static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; + public static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; + + private final EncryptionManager encryptionManager; + private final Class clazz; + + public EncryptedEntityImportBiConsumer(EncryptionManager encryptionManager, Class clazz) { + this.encryptionManager = encryptionManager; + this.clazz = clazz; + } + + @Override + public void accept(Object object_, ImportContext importContext) { + if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { + EncryptedTrackedObject param = (EncryptedTrackedObject) object_; + //if importing protected and encrypted value + if (param.getProtectedValue() != null && param.getProtectedValue()) { + if (param.getValue() == null) { + //if we have a valid encryption manager and can still decrypt keep encrypted value, else reset + if (encryptionManager != null && param.getEncryptedValue() != null) { + try { + encryptionManager.decrypt(param.getEncryptedValue()); + } catch (EncryptionManagerException e) { + param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setEncryptedValue(null); + importContext.addMessage(IMPORT_DECRYPT_FAIL_WARN); + } + } else { + param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setEncryptedValue(null); + importContext.addMessage(IMPORT_DECRYPT_NO_EM_WARN); + } + } else { + param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + importContext.addMessage(IMPORT_RESET_WARN); + } + } + } + } +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java new file mode 100644 index 0000000000..3528c5aeb2 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.parametermanager; + +import step.core.EncryptedTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.core.export.ExportContext; +import step.parameter.Parameter; +import step.parameter.ParameterManager; + +import java.util.function.BiConsumer; + +public class EncyptedEntityExportBiConsumer implements BiConsumer { + + public static String EXPORT_PROTECT_PARAM_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; + public static String EXPORT_ENCRYPT_PARAM_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; + private final Class clazz; + + public EncyptedEntityExportBiConsumer(Class clazz) { + this.clazz = clazz; + } + + @Override + public void accept(Object object_, ExportContext exportContext) { + if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { + Parameter param = (Parameter) object_; + //if protected and not encrypted, mask value by changing it to reset value + if (param.getProtectedValue() != null && param.getProtectedValue()) { + if (param.getValue() != null) { + param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + exportContext.addMessage(EXPORT_PROTECT_PARAM_WARN); + } else { + exportContext.addMessage(EXPORT_ENCRYPT_PARAM_WARN); + } + } + } + } +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index 3886c33838..92bf2bbff5 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -27,24 +27,20 @@ import step.core.collections.Filters; import step.core.collections.filters.Equals; import step.core.deployment.ObjectHookControllerPlugin; -import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; -import step.core.encryption.EncryptionManagerException; import step.core.entities.Entity; -import step.core.export.ExportContext; -import step.core.imports.ImportContext; import step.core.plugins.AbstractControllerPlugin; import step.core.plugins.Plugin; import step.engine.plugins.ExecutionEnginePlugin; import step.framework.server.tables.Table; import step.framework.server.tables.TableRegistry; +import step.parameter.AbstractEncryptedValuesManager; import step.parameter.Parameter; import step.parameter.ParameterManager; import step.plugins.encryption.EncryptionManagerDependencyPlugin; import step.plugins.screentemplating.*; import java.util.Set; -import java.util.function.BiConsumer; import java.util.stream.Collectors; import static step.parameter.Parameter.PARAMETER_PROTECTED_VALUE_FIELD; @@ -70,7 +66,7 @@ public void serverStart(GlobalContext context) { context.put("ParameterAccessor", parameterAccessor); context.get(TableRegistry.class).register(ENTITY_PARAMETERS, new Table<>(collection, "param-read", true) - .withResultItemTransformer((p,s) -> ParameterServices.maskProtectedValue(p)) + .withResultItemTransformer((p,s) -> AbstractEncryptedValuesManager.maskProtectedValue(p)) .withDerivedTableFiltersFactory(lf -> { Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); @@ -84,8 +80,8 @@ public void serverStart(GlobalContext context) { Parameter.ENTITY_NAME, parameterAccessor, Parameter.class)); - context.getEntityManager().registerExportHook(new ParameterExportBiConsumer()); - context.getEntityManager().registerImportHook(new ParameterImportBiConsumer(encryptionManager)); + context.getEntityManager().registerExportHook(new EncyptedEntityExportBiConsumer(Parameter.class)); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); context.getServiceRegistrationCallback().registerService(ParameterServices.class); } @@ -110,68 +106,5 @@ public ExecutionEnginePlugin getExecutionEnginePlugin() { return new ParameterManagerPlugin(parameterManager); } - public static String EXPORT_PROTECT_PARAM_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; - public static String EXPORT_ENCRYPT_PARAM_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; - public static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; - public static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; - public static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; - - public static class ParameterExportBiConsumer implements BiConsumer { - - @Override - public void accept(Object object_, ExportContext exportContext) { - if (object_ instanceof Parameter) { - Parameter param = (Parameter) object_; - //if protected and not encrypted, mask value by changing it to reset value - if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() != null) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - exportContext.addMessage(EXPORT_PROTECT_PARAM_WARN); - } else { - exportContext.addMessage(EXPORT_ENCRYPT_PARAM_WARN); - } - } - } - } - } - - public static class ParameterImportBiConsumer implements BiConsumer { - - private final EncryptionManager encryptionManager; - - public ParameterImportBiConsumer(EncryptionManager encryptionManager) { - this.encryptionManager = encryptionManager; - } - - @Override - public void accept(Object object_, ImportContext importContext) { - if (object_ instanceof Parameter) { - Parameter param = (Parameter) object_; - //if importing protected and encrypted value - if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() == null) { - //if we have a valid encryption manager and can still decrypt keep encrypted value, else reset - if (encryptionManager != null && param.getEncryptedValue() != null) { - try { - encryptionManager.decrypt(param.getEncryptedValue()); - } catch (EncryptionManagerException e) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_FAIL_WARN); - } - } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_NO_EM_WARN); - } - } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - importContext.addMessage(IMPORT_RESET_WARN); - } - } - } - } - } - } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index ab13e0167c..c3e8185a55 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -28,25 +28,17 @@ import step.core.GlobalContext; import step.core.accessors.Accessor; import step.core.deployment.ControllerServiceException; -import step.core.dynamicbeans.DynamicValue; -import step.core.encryption.EncryptionManagerException; import step.framework.server.access.AuthorizationManager; import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; -import step.parameter.Parameter; -import step.parameter.ParameterManager; -import step.parameter.ParameterManagerException; -import step.parameter.ParameterScope; +import step.parameter.*; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static step.parameter.ParameterManager.PROTECTED_VALUE; - @Path("/parameters") @Tag(name = "Parameters") @Tag(name = "Entity=Parameter") @@ -106,7 +98,7 @@ public Parameter save(Parameter newParameter) { private Parameter save(Parameter newParameter, Parameter sourceParameter) { assertRights(newParameter); try { - return maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername())); + return AbstractEncryptedValuesManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername())); } catch (ParameterManagerException e) { throw new ControllerServiceException(e.getMessage()); } @@ -150,19 +142,11 @@ public void delete(String id) { @Override public Parameter get(String id) { Parameter parameter = parameterAccessor.get(new ObjectId(id)); - return maskProtectedValue(parameter); - } - - public static Parameter maskProtectedValue(Parameter parameter) { - if(parameter != null && isProtected(parameter) && - !ParameterManager.RESET_VALUE.equals(parameter.getValue())) { - parameter.setValue(new DynamicValue<>(PROTECTED_VALUE)); - } - return parameter; + return AbstractEncryptedValuesManager.maskProtectedValue(parameter); } protected List maskProtectedValues(Stream stream) { - return stream.map(ParameterServices::maskProtectedValue).collect(Collectors.toList()); + return stream.map(AbstractEncryptedValuesManager::maskProtectedValue).collect(Collectors.toList()); } @POST @@ -171,7 +155,7 @@ protected List maskProtectedValues(Stream stream) { @Produces(MediaType.APPLICATION_JSON) @Secured(right="{entity}-read") public Parameter getParameterByAttributes(Map attributes) { - return maskProtectedValue(parameterAccessor.findByAttributes(attributes)); + return AbstractEncryptedValuesManager.maskProtectedValue(parameterAccessor.findByAttributes(attributes)); } @Override diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java new file mode 100644 index 0000000000..fa043043e0 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.usersettings; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.GlobalContext; +import step.core.accessors.AbstractAccessor; +import step.core.accessors.Accessor; +import step.core.collections.Collection; +import step.core.collections.Filters; +import step.core.collections.filters.Equals; +import step.core.deployment.ObjectHookControllerPlugin; +import step.core.encryption.EncryptionManager; +import step.core.entities.Entity; +import step.core.plugins.AbstractControllerPlugin; +import step.core.plugins.Plugin; +import step.framework.server.tables.Table; +import step.framework.server.tables.TableRegistry; +import step.parameter.AbstractEncryptedValuesManager; +import step.plugins.encryption.EncryptionManagerDependencyPlugin; +import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; +import step.plugins.parametermanager.EncyptedEntityExportBiConsumer; +import step.plugins.screentemplating.ScreenTemplatePlugin; +import step.usersettings.UserSetting; +import step.usersettings.UserSettingManager; + +import java.util.Set; +import java.util.stream.Collectors; + +import static step.core.EncryptedTrackedObject.PARAMETER_PROTECTED_VALUE_FIELD; +import static step.core.EncryptedTrackedObject.PARAMETER_VALUE_FIELD; + +@Plugin(dependencies= {ObjectHookControllerPlugin.class, ScreenTemplatePlugin.class, EncryptionManagerDependencyPlugin.class}) +public class UserSettingManagerControllerPlugin extends AbstractControllerPlugin { + + public static Logger logger = LoggerFactory.getLogger(UserSettingManagerControllerPlugin.class); + + private UserSettingManager userSettingManager; + private EncryptionManager encryptionManager; + + @Override + public void serverStart(GlobalContext context) { + // The encryption manager might be null + encryptionManager = context.get(EncryptionManager.class); + + Collection collection = context.getCollectionFactory().getCollection(UserSetting.ENTITY_NAME, UserSetting.class); + + Accessor userSettingAccessor = new AbstractAccessor<>(collection); + context.put("UserSettingAccessor", userSettingAccessor); + + context.get(TableRegistry.class).register(UserSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) + .withResultItemTransformer((p,s) -> AbstractEncryptedValuesManager.maskProtectedValue(p)) + .withDerivedTableFiltersFactory(lf -> { + Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); + return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); + })); + + UserSettingManager userSettingManager = new UserSettingManager(userSettingAccessor, encryptionManager, context.getConfiguration(), context.getDynamicBeanResolver()); + context.put(UserSettingManager.class, userSettingManager); + this.userSettingManager = userSettingManager; + + context.getEntityManager().register(new Entity<>( + UserSetting.ENTITY_NAME, + userSettingAccessor, + UserSetting.class)); + context.getEntityManager().registerExportHook(new EncyptedEntityExportBiConsumer(UserSetting.class)); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, UserSetting.class)); + + context.getServiceRegistrationCallback().registerService(UserSettingServices.class); + } + + @Override + public void initializeData(GlobalContext context) throws Exception { + + if(encryptionManager != null) { + if(encryptionManager.isFirstStart()) { + logger.info("First start of the encryption manager. Encrypting all protected parameters..."); + userSettingManager.encryptAllParameters(); + } + if(encryptionManager.isKeyPairChanged()) { + logger.info("Key pair of encryption manager changed. Resetting all protected parameters..."); + userSettingManager.resetAllProtectedParameters(); + } + } + } + +} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java new file mode 100644 index 0000000000..0101618f81 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.usersettings; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Path; +import step.controller.services.entities.AbstractEntityServices; +import step.framework.server.security.SecuredContext; +import step.usersettings.UserSetting; + +@Path("/userSettings") +@Tag(name = "UserSettings") +@Tag(name = "Entity=UserSetting") +@SecuredContext(key = "entity", value = "usersetting") +public class UserSettingServices extends AbstractEntityServices { + + public UserSettingServices() { + super(UserSetting.ENTITY_NAME); + } +} diff --git a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java new file mode 100644 index 0000000000..847ae67b8a --- /dev/null +++ b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.client.accessors; + +import step.client.collections.remote.RemoteCollectionFactory; +import step.core.accessors.AbstractAccessor; +import step.usersettings.UserSetting; +import step.usersettings.UserSettingAccessor; + +public class RemoteUserSettingAccessor extends AbstractAccessor implements UserSettingAccessor { + + public RemoteUserSettingAccessor(RemoteCollectionFactory remoteCollectionFactory) { + super(remoteCollectionFactory.getCollection( UserSetting.ENTITY_NAME, UserSetting.class)); + } + + +} + diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java new file mode 100644 index 0000000000..f26e3fc35f --- /dev/null +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core; + +import step.commons.activation.ActivableObject; +import step.core.accessors.AbstractTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.core.objectenricher.EnricheableObject; +import step.parameter.Parameter; +import step.parameter.ParameterScope; + +public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements ActivableObject, EnricheableObject { + + public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; + public static final String PARAMETER_VALUE_FIELD = "value"; + + protected Boolean protectedValue = false; + /** + * When running with an encryption manager, the value of protected + * {@link Parameter}s is encrypted and the encrypted value is stored into this + * field + */ + protected String encryptedValue; + protected DynamicValue value; + protected String key; + + public EncryptedTrackedObject() { + super(); + } + + public DynamicValue getValue() { + return value; + } + + public void setValue(DynamicValue value) { + this.value = value; + } + + public Boolean getProtectedValue() { + return protectedValue; + } + + public void setProtectedValue(Boolean protectedValue) { + this.protectedValue = protectedValue; + } + + public String getEncryptedValue() { + return encryptedValue; + } + + public void setEncryptedValue(String encryptedValue) { + this.encryptedValue = encryptedValue; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public abstract ParameterScope getScope(); + + public abstract String getScopeEntity(); +} diff --git a/step-core-model/src/main/java/step/parameter/Parameter.java b/step-core-model/src/main/java/step/parameter/Parameter.java index 3dfc007cb6..6ede0be520 100644 --- a/step-core-model/src/main/java/step/parameter/Parameter.java +++ b/step-core-model/src/main/java/step/parameter/Parameter.java @@ -18,38 +18,20 @@ ******************************************************************************/ package step.parameter; -import step.commons.activation.ActivableObject; import step.commons.activation.Expression; -import step.core.accessors.AbstractTrackedObject; +import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -import step.core.objectenricher.EnricheableObject; -public class Parameter extends AbstractTrackedObject implements ActivableObject, EnricheableObject { +public class Parameter extends EncryptedTrackedObject { public static final String ENTITY_NAME = "parameters"; - public static final String PARAMETER_VALUE_FIELD = "value"; - public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; - - protected String key; - - protected DynamicValue value; - protected String description; protected Expression activationExpression; protected Integer priority; - - protected Boolean protectedValue = false; - - /** - * When running with an encryption manager, the value of protected - * {@link Parameter}s is encrypted and the encrypted value is stored into this - * field - */ - protected String encryptedValue; - + protected ParameterScope scope; protected String scopeEntity; @@ -65,22 +47,6 @@ public Parameter(Expression activationExpression, String key, String value, Stri this.activationExpression = activationExpression; } - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public DynamicValue getValue() { - return value; - } - - public void setValue(DynamicValue value) { - this.value = value; - } - @Override public Expression getActivationExpression() { return activationExpression; @@ -107,25 +73,10 @@ public void setDescription(String description) { this.description = description; } - public Boolean getProtectedValue() { - return protectedValue; - } - - public void setProtectedValue(Boolean protectedValue) { - this.protectedValue = protectedValue; - } - - public String getEncryptedValue() { - return encryptedValue; - } - - public void setEncryptedValue(String encryptedValue) { - this.encryptedValue = encryptedValue; - } - /** * @return the {@link ParameterScope} of this parameter */ + @Override public ParameterScope getScope() { return scope; } @@ -135,9 +86,10 @@ public void setScope(ParameterScope scope) { } /** - * @return the name of the entity this parameter is restricted to. For instance: if the scope of a Parameter + * @return the name of the entity this parameter is restricted to. For instance: if the scope of a Parameter * is set to FUNCTION, the scopeEntity represent the name of the Function for which this parameter applies */ + @Override public String getScopeEntity() { return scopeEntity; } diff --git a/step-core-model/src/main/java/step/usersettings/UserSetting.java b/step-core-model/src/main/java/step/usersettings/UserSetting.java new file mode 100644 index 0000000000..28d98f9395 --- /dev/null +++ b/step-core-model/src/main/java/step/usersettings/UserSetting.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.usersettings; + +import step.commons.activation.Expression; +import step.core.EncryptedTrackedObject; +import step.core.dynamicbeans.DynamicValue; +import step.parameter.ParameterScope; + +public class UserSetting extends EncryptedTrackedObject { + + public static final String ENTITY_NAME = "usersettings"; + + protected String description; + + protected Expression activationExpression; + + protected Integer priority; + + + /** + * When running with an encryption manager, the value of protected + * {@link UserSetting}s is encrypted and the encrypted value is stored into this + * field + */ + protected String encryptedValue; + + public UserSetting() { + super(); + } + + public UserSetting(Expression activationExpression, String key, String value, String description) { + super(); + this.key = key; + this.value = new DynamicValue<>(value); + this.description = description; + this.activationExpression = activationExpression; + } + + @Override + public Expression getActivationExpression() { + return activationExpression; + } + + @Override + public Integer getPriority() { + return priority; + } + + public void setActivationExpression(Expression activationExpression) { + this.activationExpression = activationExpression; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + + @Override + public ParameterScope getScope() { + return ParameterScope.GLOBAL; + } + + @Override + public String getScopeEntity() { + return null; + } + + @Override + public String toString() { + return "UserSetting [key=" + key + "]"; + } +} diff --git a/step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java new file mode 100644 index 0000000000..d74e8b761a --- /dev/null +++ b/step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.parameter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.commons.activation.Activator; +import step.core.EncryptedTrackedObject; +import step.core.accessors.Accessor; +import step.core.dynamicbeans.DynamicBeanResolver; +import step.core.dynamicbeans.DynamicValue; +import step.core.encryption.EncryptionManager; +import step.core.encryption.EncryptionManagerException; +import step.core.objectenricher.ObjectPredicate; +import step.core.plugins.exceptions.PluginCriticalException; + +import javax.script.Bindings; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import java.util.*; +import java.util.stream.Collectors; + +public abstract class AbstractEncryptedValuesManager { + + protected static Logger logger = LoggerFactory.getLogger(AbstractEncryptedValuesManager.class); + + public static final String RESET_VALUE = "####change me####"; + public static final String PROTECTED_VALUE = "******"; + + protected final DynamicBeanResolver dynamicBeanResolver; + protected final EncryptionManager encryptionManager; + protected final String defaultScriptEngine; + + public AbstractEncryptedValuesManager(EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { + this.encryptionManager = encryptionManager; + this.defaultScriptEngine = defaultScriptEngine; + this.dynamicBeanResolver = dynamicBeanResolver; + } + + public static T maskProtectedValue(T parameter) { + if(parameter != null && isProtected(parameter) & !RESET_VALUE.equals(parameter.getValue().getValue())) { + parameter.setValue(new DynamicValue<>(PROTECTED_VALUE)); + } + return parameter; + } + + public T save(T newParameter, T sourceParameter, String modificationUser) { + if (isProtected(newParameter) && newParameter.getValue() != null && newParameter.getValue().isDynamic()) { + throw new ParameterManagerException("Protected parameters do not support values with dynamic expression."); + } + + ParameterScope scope = newParameter.getScope(); + if(scope != null && scope.equals(ParameterScope.GLOBAL) && newParameter.getScopeEntity() != null) { + throw new ParameterManagerException("Scope entity cannot be set for parameters with GLOBAL scope."); + } + + if(sourceParameter != null && isProtected(sourceParameter)) { + // protected value should not be changed + newParameter.setProtectedValue(true); + // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) + DynamicValue newParameterValue = newParameter.getValue(); + if(newParameterValue != null && !newParameterValue.isDynamic() && newParameterValue.get().equals(PROTECTED_VALUE)) { + newParameter.setValue(sourceParameter.getValue()); + } + } + + try { + newParameter = this.encryptParameterValueIfEncryptionManagerAvailable(newParameter); + } catch (EncryptionManagerException e) { + throw new ParameterManagerException("Error while encrypting parameter value", e); + } + + Date lastModificationDate = new Date(); + if (sourceParameter == null) { + newParameter.setCreationDate(lastModificationDate); + newParameter.setCreationUser(modificationUser); + } + newParameter.setLastModificationDate(lastModificationDate); + newParameter.setLastModificationUser(modificationUser); + + return getAccessor().save(newParameter); + } + + protected abstract Accessor getAccessor(); + + public static boolean isProtected(T p) { + return p.getProtectedValue() != null && p.getProtectedValue(); + } + + public T encryptParameterValueIfEncryptionManagerAvailable(T parameter) throws EncryptionManagerException { + if(encryptionManager != null) { + if(isProtected(parameter)) { + DynamicValue value = parameter.getValue(); + if(value != null && value.get() != null) { + parameter.setValue(null); + String encryptedValue = encryptionManager.encrypt(value.get()); + parameter.setEncryptedValue(encryptedValue); + } + } + } + return parameter; + } + + public Map getAllParameterValues(Map contextBindings, ObjectPredicate objectPredicate) { + return getAllParameters(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); + } + + public Map getAllParameters(Map contextBindings, ObjectPredicate objectPredicate) { + Map result = new HashMap<>(); + Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; + + Map> parameterMap = new HashMap<>(); + getAccessor().getAll().forEachRemaining(p->{ + if(objectPredicate==null || objectPredicate.test(p)) { + List parameters = parameterMap.get(p.getKey()); + if(parameters==null) { + parameters = new ArrayList<>(); + parameterMap.put(p.getKey(), parameters); + } + parameters.add(p); + try { + Activator.compileActivationExpression(p, defaultScriptEngine); + } catch (ScriptException e) { + logger.error("Error while compiling activation expression of parameter "+p, e); + } + } + }); + + + for(String key:parameterMap.keySet()) { + List parameters = parameterMap.get(key); + T bestMatch = Activator.findBestMatch(bindings, parameters, defaultScriptEngine); + if(bestMatch!=null) { + result.put(key, bestMatch); + } + } + resolveAllParameters(result, contextBindings); + return result; + } + + private void resolveAllParameters(Map allParameters, Map contextBindings) { + List unresolvedParamKeys = new ArrayList<>(allParameters.keySet()); + List resolvedParamKeys = new ArrayList<>(); + HashMap bindings = new HashMap<>(contextBindings); + int unresolvedCountBeforeIteration; + do { + unresolvedCountBeforeIteration = unresolvedParamKeys.size(); + unresolvedParamKeys.forEach(k -> { + T parameter = allParameters.get(k); + Boolean protectedValue = parameter.getProtectedValue(); + boolean isProtected = isProtected(parameter); + DynamicValue parameterValue = parameter.getValue(); + if (!isProtected && parameterValue != null) { + try { + if (parameterValue.isDynamic()) { + dynamicBeanResolver.evaluate(parameter, bindings); + } + String resolvedValue = parameter.getValue().get(); //throw an error if evaluation failed + bindings.put(k, resolvedValue); + resolvedParamKeys.add(k); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Could not (yet) resolve parameter dynamic value " + parameter); + } + } + } else { + //value is not set or parameter is protected, resolution is skipped + resolvedParamKeys.add(k); + if (logger.isDebugEnabled()) { + logger.debug("Following parameters won't be resolved (null or protected value) " + parameter); + } + } + }); + unresolvedParamKeys.removeAll(resolvedParamKeys); + } while (!unresolvedParamKeys.isEmpty() && unresolvedParamKeys.size() < unresolvedCountBeforeIteration); + if (!unresolvedParamKeys.isEmpty()) { + throw new PluginCriticalException("Error while resolving parameters, following parameters could not be resolved: " + unresolvedParamKeys); + } + } + + public void encryptAllParameters() { + getAccessor().getAll().forEachRemaining(p->{ + if(isProtected(p)) { + logger.info("Encrypting parameter "+p); + try { + T encryptedParameter = encryptParameterValueIfEncryptionManagerAvailable(p); + getAccessor().save(encryptedParameter); + } catch (EncryptionManagerException e) { + logger.error("Error while encrypting parameter "+p.getKey()); + } + } + }); + } + + public void resetAllProtectedParameters() { + getAccessor().getAll().forEachRemaining(p->{ + if(isProtected(p)) { + logger.info("Resetting parameter "+p); + p.setValue(new DynamicValue<>(RESET_VALUE)); + p.setEncryptedValue(null); + getAccessor().save(p); + } + }); + } + + public EncryptionManager getEncryptionManager() { + return encryptionManager; + } + + public String getDefaultScriptEngine() { + return defaultScriptEngine; + } + +} diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index 0b24197b32..0c16eeb3bc 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -18,220 +18,36 @@ ******************************************************************************/ package step.parameter; -import java.util.*; -import java.util.stream.Collectors; - -import javax.script.Bindings; -import javax.script.ScriptException; -import javax.script.SimpleBindings; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ch.exense.commons.app.Configuration; import step.commons.activation.Activator; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; -import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; -import step.core.encryption.EncryptionManagerException; -import step.core.objectenricher.ObjectPredicate; -import step.core.plugins.exceptions.PluginCriticalException; - -public class ParameterManager { - - public static final String RESET_VALUE = "####change me####"; - public static final String PROTECTED_VALUE = "******"; - private static Logger logger = LoggerFactory.getLogger(ParameterManager.class); - - private Accessor parameterAccessor; - private EncryptionManager encryptionManager; +public class ParameterManager extends AbstractEncryptedValuesManager { - private String defaultScriptEngine; - private final DynamicBeanResolver dynamicBeanResolver; + private final Accessor parameterAccessor; public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { this(parameterAccessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); } public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { + super(encryptionManager, defaultScriptEngine, dynamicBeanResolver); this.parameterAccessor = parameterAccessor; - this.encryptionManager = encryptionManager; - this.defaultScriptEngine = defaultScriptEngine; - this.dynamicBeanResolver = dynamicBeanResolver; } public static ParameterManager copy(ParameterManager from, Accessor parameterAccessor){ return new ParameterManager(parameterAccessor, from.encryptionManager, from.defaultScriptEngine, from.dynamicBeanResolver); } - public Parameter save(Parameter newParameter, Parameter sourceParameter, String modificationUser) { - if (isProtected(newParameter) && newParameter.getValue() != null && newParameter.getValue().isDynamic()) { - throw new ParameterManagerException("Protected parameters do not support values with dynamic expression."); - } - - ParameterScope scope = newParameter.getScope(); - if(scope != null && scope.equals(ParameterScope.GLOBAL) && newParameter.getScopeEntity() != null) { - throw new ParameterManagerException("Scope entity cannot be set for parameters with GLOBAL scope."); - } - - if(sourceParameter != null && isProtected(sourceParameter)) { - // protected value should not be changed - newParameter.setProtectedValue(true); - // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) - DynamicValue newParameterValue = newParameter.getValue(); - if(newParameterValue != null && !newParameterValue.isDynamic() && newParameterValue.get().equals(PROTECTED_VALUE)) { - newParameter.setValue(sourceParameter.getValue()); - } - } - - try { - newParameter = this.encryptParameterValueIfEncryptionManagerAvailable(newParameter); - } catch (EncryptionManagerException e) { - throw new ParameterManagerException("Error while encrypting parameter value", e); - } - - Date lastModificationDate = new Date(); - if (sourceParameter == null) { - newParameter.setCreationDate(lastModificationDate); - newParameter.setCreationUser(modificationUser); - } - newParameter.setLastModificationDate(lastModificationDate); - newParameter.setLastModificationUser(modificationUser); - - return parameterAccessor.save(newParameter); - } - - public Map getAllParameterValues(Map contextBindings, ObjectPredicate objectPredicate) { - return getAllParameters(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().value.get())); - } - - public Map getAllParameters(Map contextBindings, ObjectPredicate objectPredicate) { - Map result = new HashMap<>(); - Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; - - Map> parameterMap = new HashMap>(); - parameterAccessor.getAll().forEachRemaining(p->{ - if(objectPredicate==null || objectPredicate.test(p)) { - List parameters = parameterMap.get(p.key); - if(parameters==null) { - parameters = new ArrayList<>(); - parameterMap.put(p.key, parameters); - } - parameters.add(p); - try { - Activator.compileActivationExpression(p, defaultScriptEngine); - } catch (ScriptException e) { - logger.error("Error while compiling activation expression of parameter "+p, e); - } - } - }); - - - for(String key:parameterMap.keySet()) { - List parameters = parameterMap.get(key); - Parameter bestMatch = Activator.findBestMatch(bindings, parameters, defaultScriptEngine); - if(bestMatch!=null) { - result.put(key, bestMatch); - } - } - resolveAllParameters(result, contextBindings); - return result; - } - - private void resolveAllParameters(Map allParameters, Map contextBindings) { - List unresolvedParamKeys = new ArrayList<>(allParameters.keySet()); - List resolvedParamKeys = new ArrayList<>(); - HashMap bindings = new HashMap<>(contextBindings); - int unresolvedCountBeforeIteration; - do { - unresolvedCountBeforeIteration = unresolvedParamKeys.size(); - unresolvedParamKeys.forEach(k -> { - Parameter parameter = allParameters.get(k); - Boolean protectedValue = parameter.getProtectedValue(); - boolean isProtected = parameter.getProtectedValue() != null && parameter.getProtectedValue(); - DynamicValue parameterValue = parameter.getValue(); - if (!isProtected && parameterValue != null) { - try { - if (parameterValue.isDynamic()) { - dynamicBeanResolver.evaluate(parameter, bindings); - } - String resolvedValue = parameter.value.get(); //throw an error if evaluation failed - bindings.put(k, resolvedValue); - resolvedParamKeys.add(k); - } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("Could not (yet) resolve parameter dynamic value " + parameter); - } - } - } else { - //value is not set or parameter is protected, resolution is skipped - resolvedParamKeys.add(k); - if (logger.isDebugEnabled()) { - logger.debug("Following parameters won't be resolved (null or protected value) " + parameter); - } - } - }); - unresolvedParamKeys.removeAll(resolvedParamKeys); - } while (!unresolvedParamKeys.isEmpty() && unresolvedParamKeys.size() < unresolvedCountBeforeIteration); - if (!unresolvedParamKeys.isEmpty()) { - throw new PluginCriticalException("Error while resolving parameters, following parameters could not be resolved: " + unresolvedParamKeys); - } - } - - public void encryptAllParameters() { - parameterAccessor.getAll().forEachRemaining(p->{ - if(isProtected(p)) { - logger.info("Encrypting parameter "+p); - try { - Parameter encryptedParameter = encryptParameterValueIfEncryptionManagerAvailable(p); - parameterAccessor.save(encryptedParameter); - } catch (EncryptionManagerException e) { - logger.error("Error while encrypting parameter "+p.getKey()); - } - } - }); - } - - public void resetAllProtectedParameters() { - parameterAccessor.getAll().forEachRemaining(p->{ - if(isProtected(p)) { - logger.info("Resetting parameter "+p); - p.setValue(new DynamicValue<>(RESET_VALUE)); - p.setEncryptedValue(null); - parameterAccessor.save(p); - } - }); - } - - private boolean isProtected(Parameter p) { - return p.getProtectedValue() != null && p.getProtectedValue(); - } - - public Parameter encryptParameterValueIfEncryptionManagerAvailable(Parameter parameter) throws EncryptionManagerException { - if(encryptionManager != null) { - if(isProtected(parameter)) { - DynamicValue value = parameter.getValue(); - if(value != null && value.get() != null) { - parameter.setValue(null); - String encryptedValue = encryptionManager.encrypt(value.get()); - parameter.setEncryptedValue(encryptedValue); - } - } - } - return parameter; - } - - public String getDefaultScriptEngine() { - return defaultScriptEngine; + @Override + protected Accessor getAccessor() { + return parameterAccessor; } public Accessor getParameterAccessor() { - return parameterAccessor; + return getAccessor(); } - public EncryptionManager getEncryptionManager() { - return encryptionManager; - } } diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessor.java b/step-core/src/main/java/step/usersettings/UserSettingAccessor.java new file mode 100644 index 0000000000..6d85cd5217 --- /dev/null +++ b/step-core/src/main/java/step/usersettings/UserSettingAccessor.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.usersettings; + +import step.core.accessors.Accessor; + +public interface UserSettingAccessor extends Accessor { +} diff --git a/step-core/src/main/java/step/usersettings/UserSettingManager.java b/step-core/src/main/java/step/usersettings/UserSettingManager.java new file mode 100644 index 0000000000..2006efb193 --- /dev/null +++ b/step-core/src/main/java/step/usersettings/UserSettingManager.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.usersettings; + +import ch.exense.commons.app.Configuration; +import step.commons.activation.Activator; +import step.core.accessors.Accessor; +import step.core.dynamicbeans.DynamicBeanResolver; +import step.core.encryption.EncryptionManager; +import step.parameter.AbstractEncryptedValuesManager; + +public class UserSettingManager extends AbstractEncryptedValuesManager { + + private final Accessor userSettingAccessor; + + public UserSettingManager(Accessor parameterAccessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { + this(parameterAccessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); + } + + public UserSettingManager(Accessor userSettingAccessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { + super(encryptionManager, defaultScriptEngine, dynamicBeanResolver); + this.userSettingAccessor = userSettingAccessor; + } + + @Override + protected Accessor getAccessor() { + return userSettingAccessor; + } +} From b9103ae061b5c4f9cd8933683717b8b8bb451d59 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Mon, 14 Apr 2025 18:43:22 +0300 Subject: [PATCH 02/26] SED-3921: hooks for user settings (draft) --- .../parametermanager/ParameterServices.java | 3 -- .../usersettings/UserSettingServices.java | 1 + .../accessors/RemoteUserSettingAccessor.java | 1 + .../core/controller/ControllerSetting.java | 4 ++- .../ControllerSettingAccessorImpl.java | 2 +- .../controller/SettingAccessorWithHook.java | 30 +++++++++++++++++++ .../step/core/EncryptedTrackedObject.java | 3 +- .../src/main/java/step/core/ValueWithKey.java | 23 ++++++++++++++ .../usersettings/UserSettingAccessorImpl.java | 29 ++++++++++++++++++ 9 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java create mode 100644 step-core-model/src/main/java/step/core/ValueWithKey.java create mode 100644 step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index c3e8185a55..160dff8ee7 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -116,9 +116,6 @@ protected boolean hasGlobalParamRight() { return authorizationManager.checkRightInContext(getSession(), "param-global-write"); } - protected static boolean isProtected(Parameter oldParameter) { - return oldParameter.getProtectedValue()!=null && oldParameter.getProtectedValue(); - } @Override public Parameter clone(String id) { diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java index 0101618f81..081354e6c3 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java @@ -24,6 +24,7 @@ import step.framework.server.security.SecuredContext; import step.usersettings.UserSetting; +// TODO: think about permissions @Path("/userSettings") @Tag(name = "UserSettings") @Tag(name = "Entity=UserSetting") diff --git a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java index 847ae67b8a..cc33815117 100644 --- a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java +++ b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java @@ -23,6 +23,7 @@ import step.usersettings.UserSetting; import step.usersettings.UserSettingAccessor; +// TODO: maybe not required public class RemoteUserSettingAccessor extends AbstractAccessor implements UserSettingAccessor { public RemoteUserSettingAccessor(RemoteCollectionFactory remoteCollectionFactory) { diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java index b6b3444ed7..c7c642a835 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSetting.java @@ -18,9 +18,10 @@ ******************************************************************************/ package step.core.controller; +import step.core.ValueWithKey; import step.core.accessors.AbstractIdentifiableObject; -public class ControllerSetting extends AbstractIdentifiableObject { +public class ControllerSetting extends AbstractIdentifiableObject implements ValueWithKey { protected String key; @@ -36,6 +37,7 @@ public ControllerSetting() { super(); } + @Override public String getKey() { return key; } diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java index d939721d28..a4624130ce 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java @@ -31,7 +31,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -public class ControllerSettingAccessorImpl extends AbstractAccessor implements ControllerSettingAccessor { +public class ControllerSettingAccessorImpl extends SettingAccessorWithHook implements ControllerSettingAccessor { private static final Logger log = LoggerFactory.getLogger(ControllerSettingAccessorImpl.class); diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java b/step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java new file mode 100644 index 0000000000..42c90be01e --- /dev/null +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.controller; + +import step.core.ValueWithKey; +import step.core.accessors.AbstractAccessor; +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; + +public class SettingAccessorWithHook extends AbstractAccessor { + public SettingAccessorWithHook(Collection collectionDriver) { + super(collectionDriver); + } +} diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java index f26e3fc35f..4172dd7dd5 100644 --- a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -25,7 +25,7 @@ import step.parameter.Parameter; import step.parameter.ParameterScope; -public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements ActivableObject, EnricheableObject { +public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements ActivableObject, EnricheableObject, ValueWithKey { public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; public static final String PARAMETER_VALUE_FIELD = "value"; @@ -68,6 +68,7 @@ public void setEncryptedValue(String encryptedValue) { this.encryptedValue = encryptedValue; } + @Override public String getKey() { return key; } diff --git a/step-core-model/src/main/java/step/core/ValueWithKey.java b/step-core-model/src/main/java/step/core/ValueWithKey.java new file mode 100644 index 0000000000..9771dd9810 --- /dev/null +++ b/step-core-model/src/main/java/step/core/ValueWithKey.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core; + +public interface ValueWithKey { + String getKey(); +} diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java new file mode 100644 index 0000000000..3a0fa3c550 --- /dev/null +++ b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.usersettings; + +import step.core.accessors.AbstractAccessor; +import step.core.collections.Collection; + +public class UserSettingAccessorImpl extends AbstractAccessor { + + public UserSettingAccessorImpl(Collection collectionDriver) { + super(collectionDriver); + } +} From 92639ae87ebdc89c6580b811c2cf4d32d1faa33b Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 15 Apr 2025 19:09:09 +0300 Subject: [PATCH 03/26] SED-3921: hooks for user settings --- .../controller/ControllerSettingAccessor.java | 8 +- .../ControllerSettingAccessorImpl.java | 228 +--------------- .../controller/ControllerSettingHook.java | 8 - ...ontrollerSettingHookRollbackException.java | 11 - .../InMemoryControllerSettingAccessor.java | 2 +- .../housekeeping/HousekeepingJobsManager.java | 14 +- .../ControllerSettingAccessorImplTest.java | 12 +- .../AbstractSettingAccessorWithHook.java | 253 ++++++++++++++++++ .../settings}/SettingAccessorWithHook.java | 14 +- .../java/step/core/settings/SettingHook.java | 8 + .../SettingHookRollbackException.java | 11 + .../usersettings/UserSettingAccessorImpl.java | 5 +- 12 files changed, 302 insertions(+), 272 deletions(-) delete mode 100644 step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java delete mode 100644 step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java create mode 100644 step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java rename {step-controller/step-controller-server/src/main/java/step/core/controller => step-core/src/main/java/step/core/settings}/SettingAccessorWithHook.java (73%) create mode 100644 step-core/src/main/java/step/core/settings/SettingHook.java create mode 100644 step-core/src/main/java/step/core/settings/SettingHookRollbackException.java diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java index 99e92a9cd0..f74f00ae20 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessor.java @@ -1,10 +1,9 @@ package step.core.controller; import step.core.accessors.Accessor; +import step.core.settings.SettingAccessorWithHook; -import java.util.List; - -public interface ControllerSettingAccessor extends Accessor { +public interface ControllerSettingAccessor extends Accessor, SettingAccessorWithHook { ControllerSetting getSettingByKey(String key); @@ -16,8 +15,5 @@ public interface ControllerSettingAccessor extends Accessor { ControllerSetting createSettingIfNotExisting(String settingSchedulerEnabled, String string); - void addHook(String key, ControllerSettingHook hook); - - boolean removeHook(String key, ControllerSettingHook hook); } diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java index a4624130ce..e2f730b53d 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingAccessorImpl.java @@ -18,239 +18,15 @@ ******************************************************************************/ package step.core.controller; -import org.bson.types.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import step.core.accessors.AbstractAccessor; import step.core.collections.Collection; -import step.core.collections.Filters; +import step.core.settings.AbstractSettingAccessorWithHook; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -public class ControllerSettingAccessorImpl extends SettingAccessorWithHook implements ControllerSettingAccessor { - - private static final Logger log = LoggerFactory.getLogger(ControllerSettingAccessorImpl.class); - - private final Map> hooksMap = new ConcurrentHashMap<>(); +public class ControllerSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ControllerSettingAccessor { public ControllerSettingAccessorImpl(Collection collectionDriver) { super(collectionDriver); } - public ControllerSetting getSettingByKey(String key) { - return collectionDriver.find(Filters.equals("key", key), null, null, null, 0).findFirst().orElse(null); - } - - @Override - public void addHook(String key, ControllerSettingHook hook) { - this.hooksMap.computeIfAbsent(key, k -> new ArrayList<>()); - List list = this.hooksMap.get(key); - list.add(hook); - } - - @Override - public boolean removeHook(String key, ControllerSettingHook hook) { - List hooks = getHooksBySettingKey(key); - if (hooks != null) { - return hooks.remove(hook); - } else { - return false; - } - } - - @Override - public ControllerSetting save(ControllerSetting entity) { - // we can change the key of existing setting - in this case we notify hooks about deleted/created setting - ControllerSetting oldValue = getOldValue(entity); - ControllerSetting res = super.save(entity); - - if (oldValueHasAnotherKey(oldValue, entity)) { - callHooksForChangedKey(oldValue); - } - - List hooks = getHooksBySettingKey(entity.getKey()); - if (hooks != null) { - try { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingSave(res, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(res.getId(), oldValue, ex); - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - - return res; - } - - private ControllerSetting getOldValue(ControllerSetting newValue) { - if (newValue.getId() != null) { - return get(newValue.getId()); - } - return null; - } - - private boolean oldValueHasAnotherKey(ControllerSetting oldValue, ControllerSetting newValue) { - if (newValue != null) { - if (oldValue != null) { - return !Objects.equals(oldValue.getKey(), newValue.getKey()); - } - } - return false; - } - - @Override - public void save(Iterable entities) { - List oldValues = new ArrayList<>(); - List oldValuesWithChangedKeys = new ArrayList<>(); - for (ControllerSetting newValue : entities) { - ControllerSetting oldValue = getOldValue(newValue); - if (oldValue != null) { - oldValues.add(oldValue); - if (oldValueHasAnotherKey(oldValue, newValue)) { - oldValuesWithChangedKeys.add(oldValue); - } - } - } - - super.save(entities); - - try { - for (ControllerSetting entity : entities) { - ControllerSetting oldValueWithChangedKey = oldValuesWithChangedKeys.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null); - - callHooksForChangedKey(oldValueWithChangedKey); - - List hooks = getHooksBySettingKey(entity.getKey()); - if (hooks != null) { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingSave(entity, hook, false); - } - } - } - } catch (Exception ex) { - // rollback save on hook failure - for (ControllerSetting entity : entities) { - try { - rollbackOldValue( - entity.getId(), - oldValues.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null), - ex - ); - } catch (Exception ex2) { - // just print errors in log during rollback - log.error("Controller setting hook error", ex); - } - } - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - - @Override - public void remove(ObjectId id) { - ControllerSetting toBeDeleted = get(id); - super.remove(id); - List hooks = getHooksBySettingKey(toBeDeleted.getKey()); - - if (hooks != null) { - try { - for (ControllerSettingHook hook : hooks) { - callHookOnSettingRemove(toBeDeleted, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(id, toBeDeleted, ex); - - // notify the caller about rollback - throw new ControllerSettingHookRollbackException("Controller setting rollback", ex); - } - } - } - - protected void rollbackOldValue(ObjectId settingId, ControllerSetting oldValue, Exception ex) { - if (oldValue != null) { - // if some hook fails, we try to revert the operation - super.save(oldValue); - - // notify already called hooks about reverted operation - for (ControllerSettingHook calledHook : getHooksBySettingKey(oldValue.getKey())) { - // ignore errors in this case, because otherwise we can get the infinite error loop - callHookOnSettingSave(oldValue, calledHook, true); - } - } else { - ControllerSetting toBeRemoved = get(settingId); - - if (toBeRemoved != null) { - super.remove(settingId); - - // notify already called hooks about reverted operation - for (ControllerSettingHook calledHook : getHooksBySettingKey(toBeRemoved.getKey())) { - // ignore errors in this case, because otherwise we can get the infinite error loop - callHookOnSettingRemove(toBeRemoved, calledHook, true); - } - } - } - - } - - /** - * Calls the onSettingRemove hooks when key is changed in some controller setting (the value with old key is handled as removed) - */ - protected void callHooksForChangedKey(ControllerSetting oldValueWithChangedKey) { - if (oldValueWithChangedKey != null) { - List hooksOnDelete = getHooksBySettingKey(oldValueWithChangedKey.getKey()); - if (hooksOnDelete != null) { - try { - for (ControllerSettingHook hook : hooksOnDelete) { - callHookOnSettingRemove(oldValueWithChangedKey, hook, false); - } - } catch (Exception ex) { - rollbackOldValue(oldValueWithChangedKey.getId(), oldValueWithChangedKey, ex); - throw ex; - } - } - } - } - - protected void callHookOnSettingRemove(ControllerSetting deletedSetting, ControllerSettingHook hook, boolean ignoreError) { - try { - hook.onSettingRemove(deletedSetting.getId(), deletedSetting); - } catch (Exception ex) { - if (ignoreError) { - log.error("Controller setting hook error", ex); - } else { - throw ex; - } - } - } - - protected void callHookOnSettingSave(ControllerSetting res, ControllerSettingHook hook, boolean ignoreError) { - try { - hook.onSettingSave(res); - } catch (Exception ex) { - if (ignoreError) { - log.error("Controller setting hook error", ex); - } else { - throw ex; - } - } - } - - protected List getHooksBySettingKey(String settingKey) { - return this.hooksMap.get(settingKey); - } - - protected Map> getHooksMap() { - return hooksMap; - } - // TODO: the following methods should be moved to a ControllerSettingManager. // They actually don't belong to an accessor which role should be limited to // retrieval and persistence of data diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java deleted file mode 100644 index cf8c905366..0000000000 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHook.java +++ /dev/null @@ -1,8 +0,0 @@ -package step.core.controller; - -import org.bson.types.ObjectId; - -public interface ControllerSettingHook { - void onSettingSave(ControllerSetting setting); - void onSettingRemove(ObjectId settingId, ControllerSetting removed); -} diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java b/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java deleted file mode 100644 index 4f2ee46641..0000000000 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/ControllerSettingHookRollbackException.java +++ /dev/null @@ -1,11 +0,0 @@ -package step.core.controller; - -public class ControllerSettingHookRollbackException extends RuntimeException { - public ControllerSettingHookRollbackException(String message) { - super(message); - } - - public ControllerSettingHookRollbackException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java b/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java index 611b4a63bf..5848a0697b 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java +++ b/step-controller/step-controller-server/src/main/java/step/core/controller/InMemoryControllerSettingAccessor.java @@ -5,7 +5,7 @@ public class InMemoryControllerSettingAccessor extends ControllerSettingAccessorImpl implements ControllerSettingAccessor { public InMemoryControllerSettingAccessor() { - super(new InMemoryCollection()); + super(new InMemoryCollection<>()); } protected ControllerSetting copy(ControllerSetting controllerSetting){ diff --git a/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java b/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java index 0ca34ddaa6..6362c03d80 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java +++ b/step-controller/step-controller-server/src/main/java/step/core/scheduler/housekeeping/HousekeepingJobsManager.java @@ -8,8 +8,8 @@ import org.slf4j.LoggerFactory; import step.core.controller.ControllerSetting; import step.core.controller.ControllerSettingAccessor; -import step.core.controller.ControllerSettingHook; -import step.core.controller.ControllerSettingHookRollbackException; +import step.core.settings.SettingHook; +import step.core.settings.SettingHookRollbackException; import java.util.Map; import java.util.Properties; @@ -26,7 +26,7 @@ public class HousekeepingJobsManager { private final ControllerSettingAccessor controllerSettingAccessor; private final HousekeepingJobFactory housekeepingJobFactory; - private final Map hooks = new ConcurrentHashMap<>(); + private final Map hooks = new ConcurrentHashMap<>(); public HousekeepingJobsManager(Configuration configuration, ControllerSettingAccessor controllerSettingAccessor) throws SchedulerException { @@ -47,7 +47,7 @@ public void registerManagedJob(ManagedHousekeepingJob managedJob) throws Schedul this.housekeepingJobFactory.registerJob(managedJob.getJobClass(), managedJob.getJobSupplier()); // when housekeeping cron expression is changed, we want to reschedule a job - ControllerSettingHook hook = new ControllerSettingHook() { + SettingHook hook = new SettingHook<>() { @Override public void onSettingSave(ControllerSetting setting) { try { @@ -57,7 +57,7 @@ public void onSettingSave(ControllerSetting setting) { log.error("Cannot reschedule a housekeeping job. The controller setting won't be changed", ex); // this will cause the rollback in ControllerSettingAccessor - setting won't be changed - throw new ControllerSettingHookRollbackException("Unable to schedule the housekeeping job", ex); + throw new SettingHookRollbackException("Unable to schedule the housekeeping job", ex); } } @@ -70,7 +70,7 @@ public void onSettingRemove(ObjectId id, ControllerSetting deleted) { log.error("Cannot unschedule a housekeeping job. The controller setting won't be changed"); // this will cause the rollback in ControllerSettingAccessor - setting won't be changed - throw new ControllerSettingHookRollbackException("Unable to unschedule the housekeeping job", ex); + throw new SettingHookRollbackException("Unable to unschedule the housekeeping job", ex); } } @@ -141,7 +141,7 @@ private void unscheduleJob(ManagedHousekeepingJob job) throws SchedulerException } public void shutdown() throws SchedulerException { - for (Map.Entry hookEntry : hooks.entrySet()) { + for (Map.Entry hookEntry : hooks.entrySet()) { controllerSettingAccessor.removeHook(hookEntry.getKey(), hookEntry.getValue()); } scheduler.shutdown(); diff --git a/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java b/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java index cff6664cb0..96187232ff 100644 --- a/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java +++ b/step-controller/step-controller-server/src/test/java/step/core/controller/ControllerSettingAccessorImplTest.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import step.core.collections.Filters; import step.core.collections.inmemory.InMemoryCollection; +import step.core.settings.SettingHook; +import step.core.settings.SettingHookRollbackException; import java.util.*; import java.util.stream.Collectors; @@ -57,7 +59,7 @@ public void testSave() { // both hooks should be called 'on save' and 'on delete', because the rollback operation performs delete for just saved record accessor.save(notSaved); Assert.fail("Exception not thrown"); - } catch (ControllerSettingHookRollbackException ex) { + } catch (SettingHookRollbackException ex) { // ok } @@ -125,8 +127,10 @@ public void testRemove() { // second hook should throw exception on 'bad_value' - this causes operation rollback and the value should NOT be deleted // both hooks should be called 'on save' and 'on delete', because the rollback operation performs compensating save for just deleted record accessor.remove(saved1.getId()); - } catch (ControllerSettingHookRollbackException ex) { + Assert.fail("Exception is missing"); + } catch (SettingHookRollbackException ex) { // ok + log.info("Settings exception: {}", ex.getMessage()); } // value should not be removed @@ -175,7 +179,7 @@ public void testSaveCollection() { // both hooks should be called 'on save' and 'on delete', because the rollback operation performs delete for just saved record accessor.save(Arrays.asList(notSaved1, notSaved2)); Assert.fail("Exception not thrown"); - } catch (ControllerSettingHookRollbackException ex) { + } catch (SettingHookRollbackException ex) { // ok } @@ -201,7 +205,7 @@ public boolean compareSettings(ControllerSetting a, ControllerSetting b) { return Objects.equals(a.getKey(), b.getKey()) && Objects.equals(a.getValue(), b.getValue()); } - private static class TestHook implements ControllerSettingHook { + private static class TestHook implements SettingHook { protected List hookedOnSave = new ArrayList<>(); protected List hookedOnDelete = new ArrayList<>(); diff --git a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java new file mode 100644 index 0000000000..6f30db5d24 --- /dev/null +++ b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.core.settings; + +import org.bson.types.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.ValueWithKey; +import step.core.accessors.AbstractAccessor; +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.Filters; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AbstractSettingAccessorWithHook extends AbstractAccessor implements SettingAccessorWithHook { + + protected static final Logger log = LoggerFactory.getLogger(AbstractSettingAccessorWithHook.class); + + private final Map>> hooksMap = new ConcurrentHashMap<>(); + + public AbstractSettingAccessorWithHook(Collection collectionDriver) { + super(collectionDriver); + } + + public T getSettingByKey(String key) { + return collectionDriver.find(Filters.equals("key", key), null, null, null, 0).findFirst().orElse(null); + } + + public void addHook(String key, SettingHook hook) { + this.hooksMap.computeIfAbsent(key, k -> new ArrayList<>()); + List> list = this.hooksMap.get(key); + list.add(hook); + } + + public boolean removeHook(String key, SettingHook hook) { + List> hooks = getHooksBySettingKey(key); + if (hooks != null) { + return hooks.remove(hook); + } else { + return false; + } + } + + @Override + public void remove(ObjectId id) { + T toBeDeleted = get(id); + super.remove(id); + List> hooks = getHooksBySettingKey(toBeDeleted.getKey()); + + if (hooks != null) { + try { + for (SettingHook hook : hooks) { + callHookOnSettingRemove(toBeDeleted, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(id, toBeDeleted, ex); + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + } + + protected List> getHooksBySettingKey(String settingKey) { + return this.hooksMap.get(settingKey); + } + + protected Map>> getHooksMap() { + return hooksMap; + } + + protected void callHookOnSettingRemove(T deletedSetting, SettingHook hook, boolean ignoreError) { + try { + hook.onSettingRemove(deletedSetting.getId(), deletedSetting); + } catch (Exception ex) { + if (ignoreError) { + log.error("Controller setting hook error", ex); + } else { + throw ex; + } + } + } + + protected void callHookOnSettingSave(T res, SettingHook hook, boolean ignoreError) { + try { + hook.onSettingSave(res); + } catch (Exception ex) { + if (ignoreError) { + log.error("Controller setting hook error", ex); + } else { + throw ex; + } + } + } + + protected void rollbackOldValue(ObjectId settingId, T oldValue, Exception ex) { + if (oldValue != null) { + // if some hook fails, we try to revert the operation + super.save(oldValue); + + // notify already called hooks about reverted operation + for (SettingHook calledHook : getHooksBySettingKey(oldValue.getKey())) { + // ignore errors in this case, because otherwise we can get the infinite error loop + callHookOnSettingSave(oldValue, calledHook, true); + } + } else { + T toBeRemoved = get(settingId); + + if (toBeRemoved != null) { + super.remove(settingId); + + // notify already called hooks about reverted operation + for (SettingHook calledHook : getHooksBySettingKey(toBeRemoved.getKey())) { + // ignore errors in this case, because otherwise we can get the infinite error loop + callHookOnSettingRemove(toBeRemoved, calledHook, true); + } + } + } + + } + + /** + * Calls the onSettingRemove hooks when key is changed in some controller setting (the value with old key is handled as removed) + */ + protected void callHooksForChangedKey(T oldValueWithChangedKey) { + if (oldValueWithChangedKey != null) { + List> hooksOnDelete = getHooksBySettingKey(oldValueWithChangedKey.getKey()); + if (hooksOnDelete != null) { + try { + for (SettingHook hook : hooksOnDelete) { + callHookOnSettingRemove(oldValueWithChangedKey, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(oldValueWithChangedKey.getId(), oldValueWithChangedKey, ex); + throw ex; + } + } + } + } + + protected T getOldValue(T newValue) { + if (newValue.getId() != null) { + return get(newValue.getId()); + } + return null; + } + + protected boolean oldValueHasAnotherKey(T oldValue, T newValue) { + if (newValue != null) { + if (oldValue != null) { + return !Objects.equals(oldValue.getKey(), newValue.getKey()); + } + } + return false; + } + + @Override + public void save(Iterable entities) { + List oldValues = new ArrayList<>(); + List oldValuesWithChangedKeys = new ArrayList<>(); + for (T newValue : entities) { + T oldValue = getOldValue(newValue); + if (oldValue != null) { + oldValues.add(oldValue); + if (oldValueHasAnotherKey(oldValue, newValue)) { + oldValuesWithChangedKeys.add(oldValue); + } + } + } + + super.save(entities); + + try { + for (T entity : entities) { + T oldValueWithChangedKey = oldValuesWithChangedKeys.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null); + + callHooksForChangedKey(oldValueWithChangedKey); + + List> hooks = getHooksBySettingKey(entity.getKey()); + if (hooks != null) { + for (SettingHook hook : hooks) { + callHookOnSettingSave(entity, hook, false); + } + } + } + } catch (Exception ex) { + // rollback save on hook failure + for (T entity : entities) { + try { + rollbackOldValue( + entity.getId(), + oldValues.stream().filter(v -> Objects.equals(v.getId(), entity.getId())).findFirst().orElse(null), + ex + ); + } catch (Exception ex2) { + // just print errors in log during rollback + log.error("Controller setting hook error", ex); + } + } + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + + @Override + public T save(T entity) { + // we can change the key of existing setting - in this case we notify hooks about deleted/created setting + T oldValue = getOldValue(entity); + T res = super.save(entity); + + if (oldValueHasAnotherKey(oldValue, entity)) { + callHooksForChangedKey(oldValue); + } + + List> hooks = getHooksBySettingKey(entity.getKey()); + if (hooks != null) { + try { + for (SettingHook hook : hooks) { + callHookOnSettingSave(res, hook, false); + } + } catch (Exception ex) { + rollbackOldValue(res.getId(), oldValue, ex); + + // notify the caller about rollback + throw new SettingHookRollbackException("Controller setting rollback", ex); + } + } + + return res; + } +} diff --git a/step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java similarity index 73% rename from step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java rename to step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java index 42c90be01e..854c04a331 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/controller/SettingAccessorWithHook.java +++ b/step-core/src/main/java/step/core/settings/SettingAccessorWithHook.java @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.core.controller; +package step.core.settings; import step.core.ValueWithKey; -import step.core.accessors.AbstractAccessor; import step.core.accessors.AbstractIdentifiableObject; -import step.core.collections.Collection; -public class SettingAccessorWithHook extends AbstractAccessor { - public SettingAccessorWithHook(Collection collectionDriver) { - super(collectionDriver); - } +public interface SettingAccessorWithHook { + + void addHook(String key, SettingHook hook); + + boolean removeHook(String key, SettingHook hook); + } diff --git a/step-core/src/main/java/step/core/settings/SettingHook.java b/step-core/src/main/java/step/core/settings/SettingHook.java new file mode 100644 index 0000000000..b2d3468953 --- /dev/null +++ b/step-core/src/main/java/step/core/settings/SettingHook.java @@ -0,0 +1,8 @@ +package step.core.settings; + +import org.bson.types.ObjectId; + +public interface SettingHook { + void onSettingSave(T setting); + void onSettingRemove(ObjectId settingId, T removed); +} diff --git a/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java b/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java new file mode 100644 index 0000000000..fe986d524c --- /dev/null +++ b/step-core/src/main/java/step/core/settings/SettingHookRollbackException.java @@ -0,0 +1,11 @@ +package step.core.settings; + +public class SettingHookRollbackException extends RuntimeException { + public SettingHookRollbackException(String message) { + super(message); + } + + public SettingHookRollbackException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java index 3a0fa3c550..64b57e7d11 100644 --- a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java +++ b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java @@ -18,10 +18,11 @@ ******************************************************************************/ package step.usersettings; -import step.core.accessors.AbstractAccessor; import step.core.collections.Collection; +import step.core.settings.AbstractSettingAccessorWithHook; -public class UserSettingAccessorImpl extends AbstractAccessor { +// TODO: is step-core a good place for these classes? +public class UserSettingAccessorImpl extends AbstractSettingAccessorWithHook { public UserSettingAccessorImpl(Collection collectionDriver) { super(collectionDriver); From 9edffe670d24b59369b37c146e176fe5d0e7e3e5 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 15 Apr 2025 19:16:56 +0300 Subject: [PATCH 04/26] SED-3921: fix --- ...lerPlugin.java => UserSettingControllerPlugin.java} | 10 +++++----- .../step/plugins/usersettings/UserSettingServices.java | 3 +++ .../step/usersettings/UserSettingAccessorImpl.java | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/{UserSettingManagerControllerPlugin.java => UserSettingControllerPlugin.java} (94%) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java similarity index 94% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java index fa043043e0..8c9f0d2440 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java @@ -21,8 +21,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import step.core.GlobalContext; -import step.core.accessors.AbstractAccessor; -import step.core.accessors.Accessor; import step.core.collections.Collection; import step.core.collections.Filters; import step.core.collections.filters.Equals; @@ -39,6 +37,8 @@ import step.plugins.parametermanager.EncyptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; import step.usersettings.UserSetting; +import step.usersettings.UserSettingAccessor; +import step.usersettings.UserSettingAccessorImpl; import step.usersettings.UserSettingManager; import java.util.Set; @@ -48,9 +48,9 @@ import static step.core.EncryptedTrackedObject.PARAMETER_VALUE_FIELD; @Plugin(dependencies= {ObjectHookControllerPlugin.class, ScreenTemplatePlugin.class, EncryptionManagerDependencyPlugin.class}) -public class UserSettingManagerControllerPlugin extends AbstractControllerPlugin { +public class UserSettingControllerPlugin extends AbstractControllerPlugin { - public static Logger logger = LoggerFactory.getLogger(UserSettingManagerControllerPlugin.class); + public static Logger logger = LoggerFactory.getLogger(UserSettingControllerPlugin.class); private UserSettingManager userSettingManager; private EncryptionManager encryptionManager; @@ -62,7 +62,7 @@ public void serverStart(GlobalContext context) { Collection collection = context.getCollectionFactory().getCollection(UserSetting.ENTITY_NAME, UserSetting.class); - Accessor userSettingAccessor = new AbstractAccessor<>(collection); + UserSettingAccessor userSettingAccessor = new UserSettingAccessorImpl(collection); context.put("UserSettingAccessor", userSettingAccessor); context.get(TableRegistry.class).register(UserSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java index 081354e6c3..34f70630ba 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java @@ -34,4 +34,7 @@ public class UserSettingServices extends AbstractEntityServices { public UserSettingServices() { super(UserSetting.ENTITY_NAME); } + + // TODO: we have to add some specific endpoints here + } diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java index 64b57e7d11..20cf5a373b 100644 --- a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java +++ b/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java @@ -22,7 +22,7 @@ import step.core.settings.AbstractSettingAccessorWithHook; // TODO: is step-core a good place for these classes? -public class UserSettingAccessorImpl extends AbstractSettingAccessorWithHook { +public class UserSettingAccessorImpl extends AbstractSettingAccessorWithHook implements UserSettingAccessor { public UserSettingAccessorImpl(Collection collectionDriver) { super(collectionDriver); From 9d6d20c7cbabd284eeabd46b2a4511fa47faa7c1 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 15 Apr 2025 19:18:07 +0300 Subject: [PATCH 05/26] SED-3921: rename --- .../src/test/java/step/core/export/ExportManagerTest.java | 8 ++++---- ...Consumer.java => EncryptedEntityExportBiConsumer.java} | 4 ++-- .../ParameterManagerControllerPlugin.java | 2 +- .../plugins/usersettings/UserSettingControllerPlugin.java | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/{EncyptedEntityExportBiConsumer.java => EncryptedEntityExportBiConsumer.java} (92%) diff --git a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java index c02847a940..f541c50e35 100644 --- a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java +++ b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java @@ -72,7 +72,7 @@ import step.plugins.functions.types.CompositeFunction; import step.plugins.functions.types.CompositeFunctionType; import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; -import step.plugins.parametermanager.EncyptedEntityExportBiConsumer; +import step.plugins.parametermanager.EncryptedEntityExportBiConsumer; import step.resources.*; import java.io.File; @@ -86,8 +86,8 @@ import static step.planbuilder.BaseArtefacts.callPlan; import static step.planbuilder.BaseArtefacts.sequence; import static step.plugins.parametermanager.EncryptedEntityImportBiConsumer.*; -import static step.plugins.parametermanager.EncyptedEntityExportBiConsumer.EXPORT_ENCRYPT_PARAM_WARN; -import static step.plugins.parametermanager.EncyptedEntityExportBiConsumer.EXPORT_PROTECT_PARAM_WARN; +import static step.plugins.parametermanager.EncryptedEntityExportBiConsumer.EXPORT_ENCRYPT_PARAM_WARN; +import static step.plugins.parametermanager.EncryptedEntityExportBiConsumer.EXPORT_PROTECT_PARAM_WARN; public class ExportManagerTest { @@ -153,7 +153,7 @@ private void newContext(EncryptionManager encryptionManager) { .register(new ResourceEntity(resourceAccessor, resourceManager, fileResolver, entityManager)) .register(new Entity<>(EntityManager.resourceRevisions, resourceRevisionAccessor, ResourceRevision.class)); - entityManager.registerExportHook(new EncyptedEntityExportBiConsumer(Parameter.class)); + entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class)); entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); entityManager.registerImportHook(new ResourceImporter(resourceManager)); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java similarity index 92% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java index 3528c5aeb2..36dd2b8c8f 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncyptedEntityExportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java @@ -26,13 +26,13 @@ import java.util.function.BiConsumer; -public class EncyptedEntityExportBiConsumer implements BiConsumer { +public class EncryptedEntityExportBiConsumer implements BiConsumer { public static String EXPORT_PROTECT_PARAM_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; public static String EXPORT_ENCRYPT_PARAM_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; private final Class clazz; - public EncyptedEntityExportBiConsumer(Class clazz) { + public EncryptedEntityExportBiConsumer(Class clazz) { this.clazz = clazz; } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index 92bf2bbff5..4bc06ff228 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -80,7 +80,7 @@ public void serverStart(GlobalContext context) { Parameter.ENTITY_NAME, parameterAccessor, Parameter.class)); - context.getEntityManager().registerExportHook(new EncyptedEntityExportBiConsumer(Parameter.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class)); context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); context.getServiceRegistrationCallback().registerService(ParameterServices.class); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java index 8c9f0d2440..134130c1e7 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java @@ -34,7 +34,7 @@ import step.parameter.AbstractEncryptedValuesManager; import step.plugins.encryption.EncryptionManagerDependencyPlugin; import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; -import step.plugins.parametermanager.EncyptedEntityExportBiConsumer; +import step.plugins.parametermanager.EncryptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; import step.usersettings.UserSetting; import step.usersettings.UserSettingAccessor; @@ -80,7 +80,7 @@ public void serverStart(GlobalContext context) { UserSetting.ENTITY_NAME, userSettingAccessor, UserSetting.class)); - context.getEntityManager().registerExportHook(new EncyptedEntityExportBiConsumer(UserSetting.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(UserSetting.class)); context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, UserSetting.class)); context.getServiceRegistrationCallback().registerService(UserSettingServices.class); From 0424cfb120de58568bdfd9e4e5d79bdcad8a5ed9 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Thu, 17 Apr 2025 08:49:08 +0300 Subject: [PATCH 06/26] SED-3921: change path for services --- .../java/step/plugins/usersettings/UserSettingServices.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java index 34f70630ba..28704f2608 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java @@ -25,7 +25,7 @@ import step.usersettings.UserSetting; // TODO: think about permissions -@Path("/userSettings") +@Path("/user-settings") @Tag(name = "UserSettings") @Tag(name = "Entity=UserSetting") @SecuredContext(key = "entity", value = "usersetting") From 9a7d99b1bb99d92e205c42427f3ba7682ed72c5b Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Wed, 23 Apr 2025 00:21:47 +0300 Subject: [PATCH 07/26] SED-3921: project settings (improvements, part #1) --- .../ParameterManagerControllerPlugin.java | 6 +- .../parametermanager/ParameterServices.java | 4 +- .../ProjectSettingControllerPlugin.java} | 48 +++--- .../ProjectSettingServices.java} | 20 ++- .../ParameterManagerTest.java | 10 +- ...java => RemoteProjectSettingAccessor.java} | 10 +- .../ProjectSetting.java} | 14 +- .../AbstractEncryptedValuesManager.java | 149 +++++++++--------- .../EncryptedValueManagerException.java | 11 ++ .../java/step/parameter/ParameterManager.java | 6 + .../parameter/ParameterManagerException.java | 13 -- .../ProjectSettingAccessor.java} | 4 +- .../ProjectSettingAccessorImpl.java} | 6 +- .../ProjectSettingManager.java} | 25 +-- .../ParameterManagerPlugin.java | 2 +- .../AutomationPackageParameterHook.java | 7 +- 16 files changed, 173 insertions(+), 162 deletions(-) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/{usersettings/UserSettingControllerPlugin.java => projectsettings/ProjectSettingControllerPlugin.java} (67%) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/{usersettings/UserSettingServices.java => projectsettings/ProjectSettingServices.java} (72%) rename step-controller/step-controller-remote-client/src/main/java/step/client/accessors/{RemoteUserSettingAccessor.java => RemoteProjectSettingAccessor.java} (71%) rename step-core-model/src/main/java/step/{usersettings/UserSetting.java => projectsettings/ProjectSetting.java} (84%) rename step-core/src/main/java/step/{parameter => encryption}/AbstractEncryptedValuesManager.java (50%) create mode 100644 step-core/src/main/java/step/encryption/EncryptedValueManagerException.java delete mode 100644 step-core/src/main/java/step/parameter/ParameterManagerException.java rename step-core/src/main/java/step/{usersettings/UserSettingAccessor.java => projectsettings/ProjectSettingAccessor.java} (89%) rename step-core/src/main/java/step/{usersettings/UserSettingAccessorImpl.java => projectsettings/ProjectSettingAccessorImpl.java} (81%) rename step-core/src/main/java/step/{usersettings/UserSettingManager.java => projectsettings/ProjectSettingManager.java} (55%) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index 4bc06ff228..a2a64f6a05 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -34,7 +34,7 @@ import step.engine.plugins.ExecutionEnginePlugin; import step.framework.server.tables.Table; import step.framework.server.tables.TableRegistry; -import step.parameter.AbstractEncryptedValuesManager; +import step.encryption.AbstractEncryptedValuesManager; import step.parameter.Parameter; import step.parameter.ParameterManager; import step.plugins.encryption.EncryptionManagerDependencyPlugin; @@ -92,11 +92,11 @@ public void initializeData(GlobalContext context) throws Exception { if(encryptionManager != null) { if(encryptionManager.isFirstStart()) { logger.info("First start of the encryption manager. Encrypting all protected parameters..."); - parameterManager.encryptAllParameters(); + parameterManager.encryptAll(); } if(encryptionManager.isKeyPairChanged()) { logger.info("Key pair of encryption manager changed. Resetting all protected parameters..."); - parameterManager.resetAllProtectedParameters(); + parameterManager.resetAllProtectedValues(); } } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index 160dff8ee7..f6262bd65f 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -28,6 +28,8 @@ import step.core.GlobalContext; import step.core.accessors.Accessor; import step.core.deployment.ControllerServiceException; +import step.encryption.AbstractEncryptedValuesManager; +import step.encryption.EncryptedValueManagerException; import step.framework.server.access.AuthorizationManager; import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; @@ -99,7 +101,7 @@ private Parameter save(Parameter newParameter, Parameter sourceParameter) { assertRights(newParameter); try { return AbstractEncryptedValuesManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername())); - } catch (ParameterManagerException e) { + } catch (EncryptedValueManagerException e) { throw new ControllerServiceException(e.getMessage()); } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java similarity index 67% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 134130c1e7..505fdc13df 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.plugins.usersettings; +package step.plugins.projectsettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,15 +31,15 @@ import step.core.plugins.Plugin; import step.framework.server.tables.Table; import step.framework.server.tables.TableRegistry; -import step.parameter.AbstractEncryptedValuesManager; +import step.encryption.AbstractEncryptedValuesManager; import step.plugins.encryption.EncryptionManagerDependencyPlugin; import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; import step.plugins.parametermanager.EncryptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; -import step.usersettings.UserSetting; -import step.usersettings.UserSettingAccessor; -import step.usersettings.UserSettingAccessorImpl; -import step.usersettings.UserSettingManager; +import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; +import step.projectsettings.ProjectSettingAccessorImpl; +import step.projectsettings.ProjectSettingManager; import java.util.Set; import java.util.stream.Collectors; @@ -48,11 +48,11 @@ import static step.core.EncryptedTrackedObject.PARAMETER_VALUE_FIELD; @Plugin(dependencies= {ObjectHookControllerPlugin.class, ScreenTemplatePlugin.class, EncryptionManagerDependencyPlugin.class}) -public class UserSettingControllerPlugin extends AbstractControllerPlugin { +public class ProjectSettingControllerPlugin extends AbstractControllerPlugin { - public static Logger logger = LoggerFactory.getLogger(UserSettingControllerPlugin.class); + public static Logger logger = LoggerFactory.getLogger(ProjectSettingControllerPlugin.class); - private UserSettingManager userSettingManager; + private ProjectSettingManager projectSettingManager; private EncryptionManager encryptionManager; @Override @@ -60,30 +60,30 @@ public void serverStart(GlobalContext context) { // The encryption manager might be null encryptionManager = context.get(EncryptionManager.class); - Collection collection = context.getCollectionFactory().getCollection(UserSetting.ENTITY_NAME, UserSetting.class); + Collection collection = context.getCollectionFactory().getCollection(ProjectSetting.ENTITY_NAME, ProjectSetting.class); - UserSettingAccessor userSettingAccessor = new UserSettingAccessorImpl(collection); - context.put("UserSettingAccessor", userSettingAccessor); + ProjectSettingAccessor projectSettingAccessor = new ProjectSettingAccessorImpl(collection); + context.put("ProjectSettingAccessor", projectSettingAccessor); - context.get(TableRegistry.class).register(UserSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) + context.get(TableRegistry.class).register(ProjectSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) .withResultItemTransformer((p,s) -> AbstractEncryptedValuesManager.maskProtectedValue(p)) .withDerivedTableFiltersFactory(lf -> { Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); })); - UserSettingManager userSettingManager = new UserSettingManager(userSettingAccessor, encryptionManager, context.getConfiguration(), context.getDynamicBeanResolver()); - context.put(UserSettingManager.class, userSettingManager); - this.userSettingManager = userSettingManager; + ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration(), context.getDynamicBeanResolver()); + context.put(ProjectSettingManager.class, projectSettingManager); + this.projectSettingManager = projectSettingManager; context.getEntityManager().register(new Entity<>( - UserSetting.ENTITY_NAME, - userSettingAccessor, - UserSetting.class)); - context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(UserSetting.class)); - context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, UserSetting.class)); + ProjectSetting.ENTITY_NAME, + projectSettingAccessor, + ProjectSetting.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class)); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class)); - context.getServiceRegistrationCallback().registerService(UserSettingServices.class); + context.getServiceRegistrationCallback().registerService(ProjectSettingServices.class); } @Override @@ -92,11 +92,11 @@ public void initializeData(GlobalContext context) throws Exception { if(encryptionManager != null) { if(encryptionManager.isFirstStart()) { logger.info("First start of the encryption manager. Encrypting all protected parameters..."); - userSettingManager.encryptAllParameters(); + projectSettingManager.encryptAll(); } if(encryptionManager.isKeyPairChanged()) { logger.info("Key pair of encryption manager changed. Resetting all protected parameters..."); - userSettingManager.resetAllProtectedParameters(); + projectSettingManager.resetAllProtectedValues(); } } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java similarity index 72% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 28704f2608..b93cf7d493 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/usersettings/UserSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -16,25 +16,23 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.plugins.usersettings; +package step.plugins.projectsettings; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Path; import step.controller.services.entities.AbstractEntityServices; import step.framework.server.security.SecuredContext; -import step.usersettings.UserSetting; +import step.projectsettings.ProjectSetting; // TODO: think about permissions -@Path("/user-settings") -@Tag(name = "UserSettings") -@Tag(name = "Entity=UserSetting") -@SecuredContext(key = "entity", value = "usersetting") -public class UserSettingServices extends AbstractEntityServices { +@Path("/project-settings") +@Tag(name = "ProjectSettings") +@Tag(name = "Entity=ProjectSetting") +@SecuredContext(key = "entity", value = "projectsetting") +public class ProjectSettingServices extends AbstractEntityServices { - public UserSettingServices() { - super(UserSetting.ENTITY_NAME); + public ProjectSettingServices() { + super(ProjectSetting.ENTITY_NAME); } - // TODO: we have to add some specific endpoints here - } diff --git a/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java b/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java index c2dc1499d4..a8f8f8910c 100644 --- a/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java +++ b/step-controller/step-controller-base-plugins/src/test/java/step/plugins/parametermanager/ParameterManagerTest.java @@ -94,12 +94,12 @@ public void test1Common(Configuration configuration) throws ScriptException { Map bindings = new HashMap(); bindings.put("user", "poire"); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); Assert.assertEquals(params.get("key1"),"poirier"); Assert.assertEquals(params.get("key2"),"defaultValue2"); Assert.assertEquals(params.get("key3"),"value3"); - params = m.getAllParameterValues(bindings, t -> false); + params = m.getAllValues(bindings, t -> false); Assert.assertEquals(0, params.size()); } @@ -125,12 +125,12 @@ public void testPerf() throws ScriptException { bindings.put("user", "user"+nIt); long t1 = System.currentTimeMillis(); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); logger.info("ms:"+(System.currentTimeMillis()-t1)); Assert.assertEquals(params.get("key1"),"value"+nIt); t1 = System.currentTimeMillis(); - params = m.getAllParameterValues(bindings, null); + params = m.getAllValues(bindings, null); logger.info("ms:"+(System.currentTimeMillis()-t1)); Assert.assertEquals(params.get("key1"),"value"+nIt); @@ -161,7 +161,7 @@ public void run() { Random r = new Random(); int userId = r.nextInt(nIt)+1; bindings.put("user", "user"+userId); - Map params = m.getAllParameterValues(bindings, null); + Map params = m.getAllValues(bindings, null); Assert.assertEquals(params.get("key1"),"value"+userId); } } diff --git a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java similarity index 71% rename from step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java rename to step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java index cc33815117..8fb08e51fc 100644 --- a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteUserSettingAccessor.java +++ b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java @@ -20,14 +20,14 @@ import step.client.collections.remote.RemoteCollectionFactory; import step.core.accessors.AbstractAccessor; -import step.usersettings.UserSetting; -import step.usersettings.UserSettingAccessor; +import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; // TODO: maybe not required -public class RemoteUserSettingAccessor extends AbstractAccessor implements UserSettingAccessor { +public class RemoteProjectSettingAccessor extends AbstractAccessor implements ProjectSettingAccessor { - public RemoteUserSettingAccessor(RemoteCollectionFactory remoteCollectionFactory) { - super(remoteCollectionFactory.getCollection( UserSetting.ENTITY_NAME, UserSetting.class)); + public RemoteProjectSettingAccessor(RemoteCollectionFactory remoteCollectionFactory) { + super(remoteCollectionFactory.getCollection( ProjectSetting.ENTITY_NAME, ProjectSetting.class)); } diff --git a/step-core-model/src/main/java/step/usersettings/UserSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java similarity index 84% rename from step-core-model/src/main/java/step/usersettings/UserSetting.java rename to step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index 28d98f9395..c4c68580d1 100644 --- a/step-core-model/src/main/java/step/usersettings/UserSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -16,16 +16,16 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.usersettings; +package step.projectsettings; import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; import step.parameter.ParameterScope; -public class UserSetting extends EncryptedTrackedObject { +public class ProjectSetting extends EncryptedTrackedObject { - public static final String ENTITY_NAME = "usersettings"; + public static final String ENTITY_NAME = "projectsettings"; protected String description; @@ -36,16 +36,16 @@ public class UserSetting extends EncryptedTrackedObject { /** * When running with an encryption manager, the value of protected - * {@link UserSetting}s is encrypted and the encrypted value is stored into this + * {@link ProjectSetting}s is encrypted and the encrypted value is stored into this * field */ protected String encryptedValue; - public UserSetting() { + public ProjectSetting() { super(); } - public UserSetting(Expression activationExpression, String key, String value, String description) { + public ProjectSetting(Expression activationExpression, String key, String value, String description) { super(); this.key = key; this.value = new DynamicValue<>(value); @@ -92,6 +92,6 @@ public String getScopeEntity() { @Override public String toString() { - return "UserSetting [key=" + key + "]"; + return "ProjectSetting [key=" + key + "]"; } } diff --git a/step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java similarity index 50% rename from step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java rename to step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index d74e8b761a..225a68422b 100644 --- a/step-core/src/main/java/step/parameter/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.parameter; +package step.encryption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +29,7 @@ import step.core.encryption.EncryptionManagerException; import step.core.objectenricher.ObjectPredicate; import step.core.plugins.exceptions.PluginCriticalException; +import step.parameter.ParameterScope; import javax.script.Bindings; import javax.script.ScriptException; @@ -53,48 +54,48 @@ public AbstractEncryptedValuesManager(EncryptionManager encryptionManager, Strin this.dynamicBeanResolver = dynamicBeanResolver; } - public static T maskProtectedValue(T parameter) { - if(parameter != null && isProtected(parameter) & !RESET_VALUE.equals(parameter.getValue().getValue())) { - parameter.setValue(new DynamicValue<>(PROTECTED_VALUE)); + public static T maskProtectedValue(T obj) { + if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue().getValue())) { + obj.setValue(new DynamicValue<>(PROTECTED_VALUE)); } - return parameter; + return obj; } - public T save(T newParameter, T sourceParameter, String modificationUser) { - if (isProtected(newParameter) && newParameter.getValue() != null && newParameter.getValue().isDynamic()) { - throw new ParameterManagerException("Protected parameters do not support values with dynamic expression."); + public T save(T newObj, T sourceObj, String modificationUser) { + if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { + throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); } - ParameterScope scope = newParameter.getScope(); - if(scope != null && scope.equals(ParameterScope.GLOBAL) && newParameter.getScopeEntity() != null) { - throw new ParameterManagerException("Scope entity cannot be set for parameters with GLOBAL scope."); + ParameterScope scope = newObj.getScope(); + if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { + throw new EncryptedValueManagerException("Scope entity cannot be set for " + getEntityNameForLogging() + "s with GLOBAL scope."); } - if(sourceParameter != null && isProtected(sourceParameter)) { + if(sourceObj != null && isProtected(sourceObj)) { // protected value should not be changed - newParameter.setProtectedValue(true); + newObj.setProtectedValue(true); // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) - DynamicValue newParameterValue = newParameter.getValue(); - if(newParameterValue != null && !newParameterValue.isDynamic() && newParameterValue.get().equals(PROTECTED_VALUE)) { - newParameter.setValue(sourceParameter.getValue()); + DynamicValue newValue = newObj.getValue(); + if(newValue != null && !newValue.isDynamic() && newValue.get().equals(PROTECTED_VALUE)) { + newObj.setValue(sourceObj.getValue()); } } try { - newParameter = this.encryptParameterValueIfEncryptionManagerAvailable(newParameter); + newObj = this.encryptValueIfEncryptionManagerAvailable(newObj); } catch (EncryptionManagerException e) { - throw new ParameterManagerException("Error while encrypting parameter value", e); + throw new EncryptedValueManagerException("Error while encrypting " + getEntityNameForLogging() + " value", e); } Date lastModificationDate = new Date(); - if (sourceParameter == null) { - newParameter.setCreationDate(lastModificationDate); - newParameter.setCreationUser(modificationUser); + if (sourceObj == null) { + newObj.setCreationDate(lastModificationDate); + newObj.setCreationUser(modificationUser); } - newParameter.setLastModificationDate(lastModificationDate); - newParameter.setLastModificationUser(modificationUser); + newObj.setLastModificationDate(lastModificationDate); + newObj.setLastModificationUser(modificationUser); - return getAccessor().save(newParameter); + return getAccessor().save(newObj); } protected abstract Accessor getAccessor(); @@ -103,115 +104,115 @@ public static boolean isProtected(T p) { return p.getProtectedValue() != null && p.getProtectedValue(); } - public T encryptParameterValueIfEncryptionManagerAvailable(T parameter) throws EncryptionManagerException { + public T encryptValueIfEncryptionManagerAvailable(T obj) throws EncryptionManagerException { if(encryptionManager != null) { - if(isProtected(parameter)) { - DynamicValue value = parameter.getValue(); + if(isProtected(obj)) { + DynamicValue value = obj.getValue(); if(value != null && value.get() != null) { - parameter.setValue(null); + obj.setValue(null); String encryptedValue = encryptionManager.encrypt(value.get()); - parameter.setEncryptedValue(encryptedValue); + obj.setEncryptedValue(encryptedValue); } } } - return parameter; + return obj; } - public Map getAllParameterValues(Map contextBindings, ObjectPredicate objectPredicate) { - return getAllParameters(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); + public Map getAllValues(Map contextBindings, ObjectPredicate objectPredicate) { + return getAllObjects(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); } - public Map getAllParameters(Map contextBindings, ObjectPredicate objectPredicate) { + public Map getAllObjects(Map contextBindings, ObjectPredicate objectPredicate) { Map result = new HashMap<>(); Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; - Map> parameterMap = new HashMap<>(); + Map> objectsMap = new HashMap<>(); getAccessor().getAll().forEachRemaining(p->{ if(objectPredicate==null || objectPredicate.test(p)) { - List parameters = parameterMap.get(p.getKey()); - if(parameters==null) { - parameters = new ArrayList<>(); - parameterMap.put(p.getKey(), parameters); + List objects = objectsMap.get(p.getKey()); + if(objects==null) { + objects = new ArrayList<>(); + objectsMap.put(p.getKey(), objects); } - parameters.add(p); + objects.add(p); try { Activator.compileActivationExpression(p, defaultScriptEngine); } catch (ScriptException e) { - logger.error("Error while compiling activation expression of parameter "+p, e); + logger.error("Error while compiling activation expression of " + getEntityNameForLogging() + " " + p, e); } } }); - for(String key:parameterMap.keySet()) { - List parameters = parameterMap.get(key); - T bestMatch = Activator.findBestMatch(bindings, parameters, defaultScriptEngine); + for(String key:objectsMap.keySet()) { + List objects = objectsMap.get(key); + T bestMatch = Activator.findBestMatch(bindings, objects, defaultScriptEngine); if(bestMatch!=null) { result.put(key, bestMatch); } } - resolveAllParameters(result, contextBindings); + resolveAllValues(result, contextBindings); return result; } - private void resolveAllParameters(Map allParameters, Map contextBindings) { - List unresolvedParamKeys = new ArrayList<>(allParameters.keySet()); - List resolvedParamKeys = new ArrayList<>(); + private void resolveAllValues(Map allObjects, Map contextBindings) { + List unresolvedKeys = new ArrayList<>(allObjects.keySet()); + List resolvedKeys = new ArrayList<>(); HashMap bindings = new HashMap<>(contextBindings); int unresolvedCountBeforeIteration; do { - unresolvedCountBeforeIteration = unresolvedParamKeys.size(); - unresolvedParamKeys.forEach(k -> { - T parameter = allParameters.get(k); - Boolean protectedValue = parameter.getProtectedValue(); - boolean isProtected = isProtected(parameter); - DynamicValue parameterValue = parameter.getValue(); - if (!isProtected && parameterValue != null) { + unresolvedCountBeforeIteration = unresolvedKeys.size(); + unresolvedKeys.forEach(k -> { + T obj = allObjects.get(k); + Boolean protectedValue = obj.getProtectedValue(); + boolean isProtected = isProtected(obj); + DynamicValue value = obj.getValue(); + if (!isProtected && value != null) { try { - if (parameterValue.isDynamic()) { - dynamicBeanResolver.evaluate(parameter, bindings); + if (value.isDynamic()) { + dynamicBeanResolver.evaluate(obj, bindings); } - String resolvedValue = parameter.getValue().get(); //throw an error if evaluation failed + String resolvedValue = obj.getValue().get(); //throw an error if evaluation failed bindings.put(k, resolvedValue); - resolvedParamKeys.add(k); + resolvedKeys.add(k); } catch (Exception e) { if (logger.isDebugEnabled()) { - logger.debug("Could not (yet) resolve parameter dynamic value " + parameter); + logger.debug("Could not (yet) resolve " + getEntityNameForLogging() + " dynamic value " + obj); } } } else { - //value is not set or parameter is protected, resolution is skipped - resolvedParamKeys.add(k); + //value is not set or is protected, resolution is skipped + resolvedKeys.add(k); if (logger.isDebugEnabled()) { - logger.debug("Following parameters won't be resolved (null or protected value) " + parameter); + logger.debug("Following won't be resolved (null or protected value) " + obj); } } }); - unresolvedParamKeys.removeAll(resolvedParamKeys); - } while (!unresolvedParamKeys.isEmpty() && unresolvedParamKeys.size() < unresolvedCountBeforeIteration); - if (!unresolvedParamKeys.isEmpty()) { - throw new PluginCriticalException("Error while resolving parameters, following parameters could not be resolved: " + unresolvedParamKeys); + unresolvedKeys.removeAll(resolvedKeys); + } while (!unresolvedKeys.isEmpty() && unresolvedKeys.size() < unresolvedCountBeforeIteration); + if (!unresolvedKeys.isEmpty()) { + throw new PluginCriticalException("Error while resolving " + getEntityNameForLogging() + "s, following " + getEntityNameForLogging() + " s could not be resolved: " + unresolvedKeys); } } - public void encryptAllParameters() { + public void encryptAll() { getAccessor().getAll().forEachRemaining(p->{ if(isProtected(p)) { - logger.info("Encrypting parameter "+p); + logger.info("Encrypting " + getEntityNameForLogging() + " " + p); try { - T encryptedParameter = encryptParameterValueIfEncryptionManagerAvailable(p); - getAccessor().save(encryptedParameter); + T encrypted = encryptValueIfEncryptionManagerAvailable(p); + getAccessor().save(encrypted); } catch (EncryptionManagerException e) { - logger.error("Error while encrypting parameter "+p.getKey()); + logger.error("Error while encrypting " + getEntityNameForLogging() + " " + p.getKey()); } } }); } - public void resetAllProtectedParameters() { + public void resetAllProtectedValues() { getAccessor().getAll().forEachRemaining(p->{ if(isProtected(p)) { - logger.info("Resetting parameter "+p); + logger.info("Resetting " + getEntityNameForLogging() + " " + p); p.setValue(new DynamicValue<>(RESET_VALUE)); p.setEncryptedValue(null); getAccessor().save(p); @@ -227,4 +228,6 @@ public String getDefaultScriptEngine() { return defaultScriptEngine; } + protected abstract String getEntityNameForLogging(); + } diff --git a/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java b/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java new file mode 100644 index 0000000000..bfb4a83332 --- /dev/null +++ b/step-core/src/main/java/step/encryption/EncryptedValueManagerException.java @@ -0,0 +1,11 @@ +package step.encryption; + +public class EncryptedValueManagerException extends RuntimeException { + public EncryptedValueManagerException(String s) { + super(s); + } + + public EncryptedValueManagerException(String message, Exception e) { + super(message, e); + } +} diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index 0c16eeb3bc..b7408d9d37 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -23,6 +23,7 @@ import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; +import step.encryption.AbstractEncryptedValuesManager; public class ParameterManager extends AbstractEncryptedValuesManager { @@ -46,6 +47,11 @@ protected Accessor getAccessor() { return parameterAccessor; } + @Override + protected String getEntityNameForLogging() { + return "parameter"; + } + public Accessor getParameterAccessor() { return getAccessor(); } diff --git a/step-core/src/main/java/step/parameter/ParameterManagerException.java b/step-core/src/main/java/step/parameter/ParameterManagerException.java deleted file mode 100644 index 08f91366ae..0000000000 --- a/step-core/src/main/java/step/parameter/ParameterManagerException.java +++ /dev/null @@ -1,13 +0,0 @@ -package step.parameter; - -import step.core.encryption.EncryptionManagerException; - -public class ParameterManagerException extends RuntimeException { - public ParameterManagerException(String s) { - super(s); - } - - public ParameterManagerException(String message, Exception e) { - super(message, e); - } -} diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessor.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java similarity index 89% rename from step-core/src/main/java/step/usersettings/UserSettingAccessor.java rename to step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java index 6d85cd5217..ae14a78fdf 100644 --- a/step-core/src/main/java/step/usersettings/UserSettingAccessor.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.usersettings; +package step.projectsettings; import step.core.accessors.Accessor; -public interface UserSettingAccessor extends Accessor { +public interface ProjectSettingAccessor extends Accessor { } diff --git a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java similarity index 81% rename from step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java rename to step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java index 20cf5a373b..c7c55a8bc8 100644 --- a/step-core/src/main/java/step/usersettings/UserSettingAccessorImpl.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.usersettings; +package step.projectsettings; import step.core.collections.Collection; import step.core.settings.AbstractSettingAccessorWithHook; // TODO: is step-core a good place for these classes? -public class UserSettingAccessorImpl extends AbstractSettingAccessorWithHook implements UserSettingAccessor { +public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { - public UserSettingAccessorImpl(Collection collectionDriver) { + public ProjectSettingAccessorImpl(Collection collectionDriver) { super(collectionDriver); } } diff --git a/step-core/src/main/java/step/usersettings/UserSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java similarity index 55% rename from step-core/src/main/java/step/usersettings/UserSettingManager.java rename to step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 2006efb193..027b4f1843 100644 --- a/step-core/src/main/java/step/usersettings/UserSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -16,30 +16,35 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.usersettings; +package step.projectsettings; import ch.exense.commons.app.Configuration; import step.commons.activation.Activator; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; -import step.parameter.AbstractEncryptedValuesManager; +import step.encryption.AbstractEncryptedValuesManager; -public class UserSettingManager extends AbstractEncryptedValuesManager { +public class ProjectSettingManager extends AbstractEncryptedValuesManager { - private final Accessor userSettingAccessor; + private final Accessor accessor; - public UserSettingManager(Accessor parameterAccessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { - this(parameterAccessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); + public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { + this(accessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); } - public UserSettingManager(Accessor userSettingAccessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { + public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { super(encryptionManager, defaultScriptEngine, dynamicBeanResolver); - this.userSettingAccessor = userSettingAccessor; + this.accessor = accessor; } @Override - protected Accessor getAccessor() { - return userSettingAccessor; + protected Accessor getAccessor() { + return accessor; + } + + @Override + protected String getEntityNameForLogging() { + return "project setting"; } } diff --git a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java index 637060be12..f7e7ccc09f 100644 --- a/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java +++ b/step-plans/step-plans-base-artefacts/src/main/java/step/plugins/parametermanager/ParameterManagerPlugin.java @@ -102,7 +102,7 @@ public void executionStart(ExecutionContext context) { // Resolve the active parameters Map contextBindings = ExecutionContextBindings.get(context); ObjectPredicate objectPredicate = context.getObjectPredicate(); - Map allParameters = getParameterManagerFromContext(context).getAllParameters(contextBindings, objectPredicate); + Map allParameters = getParameterManagerFromContext(context).getAllObjects(contextBindings, objectPredicate); // Add all the active parameters to the execution parameter map of the Execution object buildExecutionParametersMapAndUpdateExecution(context, allParameters); diff --git a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java index df9c588665..284416433b 100644 --- a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java +++ b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java @@ -27,11 +27,10 @@ import step.core.accessors.Accessor; import step.core.accessors.InMemoryAccessor; import step.core.accessors.LayeredAccessor; -import step.core.encryption.EncryptionManagerException; import step.core.repositories.ImportResult; import step.parameter.Parameter; import step.parameter.ParameterManager; -import step.parameter.ParameterManagerException; +import step.encryption.EncryptedValueManagerException; import java.util.Iterator; import java.util.List; @@ -75,9 +74,9 @@ public void onCreate(List entities, AutomationPackageContex context.getEnricher().accept(entity); try { getParameterManager(context.getExtensions()).save(entity, null, null); - } catch (ParameterManagerException e) { + } catch (EncryptedValueManagerException e) { log.error("The automation package parameter {} cannot be saved for automation package {}.", entity.getKey(), context.getAutomationPackageArchive().getOriginalFileName(), e); - throw new ParameterManagerException("The automation package parameter " + entity.getKey() + " cannot be saved: " + e.getMessage()); + throw new EncryptedValueManagerException("The automation package parameter " + entity.getKey() + " cannot be saved: " + e.getMessage()); } } } From 4c492f6e148323bf9464813e56796c155b648567 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Wed, 23 Apr 2025 19:32:37 +0300 Subject: [PATCH 08/26] SED-3921: fix warn messages --- .../StepRunnerWithPlansAnnotationTest.java | 2 ++ .../step/core/export/ExportManagerTest.java | 28 ++++++++++------- .../EncryptedEntityExportBiConsumer.java | 22 +++++++++---- .../EncryptedEntityImportBiConsumer.java | 31 +++++++++++++------ .../ParameterManagerControllerPlugin.java | 6 ++-- .../ProjectSettingControllerPlugin.java | 8 ++--- .../AbstractEncryptedValuesManager.java | 2 +- .../java/step/parameter/ParameterManager.java | 2 +- .../ProjectSettingManager.java | 2 +- 9 files changed, 67 insertions(+), 36 deletions(-) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/{parametermanager => encryption}/EncryptedEntityExportBiConsumer.java (66%) rename step-controller/step-controller-base-plugins/src/main/java/step/plugins/{parametermanager => encryption}/EncryptedEntityImportBiConsumer.java (69%) diff --git a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java index 0b99138545..a318baa90f 100644 --- a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java +++ b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java @@ -18,9 +18,11 @@ ******************************************************************************/ package step.junit5.runner; +import step.automation.packages.junit.ExcludePlans; import step.junit.runners.annotations.ExecutionParameters; import step.junit.runners.annotations.Plans; +@ExcludePlans({"JMeter Plan", "testAutomation.plan"}) @Plans({"plans/plan2.plan", "plans/plan3.plan", "plans/assertsTest.plan"}) @ExecutionParameters({"PARAM_EXEC","Value","PARAM_EXEC2","Value","PARAM_EXEC3","Value"}) public class StepRunnerWithPlansAnnotationTest extends StepJUnit5 { diff --git a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java index f541c50e35..c06ee3e3a9 100644 --- a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java +++ b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java @@ -71,8 +71,8 @@ import step.planbuilder.FunctionArtefacts; import step.plugins.functions.types.CompositeFunction; import step.plugins.functions.types.CompositeFunctionType; -import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; -import step.plugins.parametermanager.EncryptedEntityExportBiConsumer; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; import step.resources.*; import java.io.File; @@ -85,12 +85,16 @@ import static org.junit.Assert.*; import static step.planbuilder.BaseArtefacts.callPlan; import static step.planbuilder.BaseArtefacts.sequence; -import static step.plugins.parametermanager.EncryptedEntityImportBiConsumer.*; -import static step.plugins.parametermanager.EncryptedEntityExportBiConsumer.EXPORT_ENCRYPT_PARAM_WARN; -import static step.plugins.parametermanager.EncryptedEntityExportBiConsumer.EXPORT_PROTECT_PARAM_WARN; public class ExportManagerTest { + private static String EXPORT_ENCRYPT_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; + private static String EXPORT_PROTECT_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; + + private static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; + private static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; + private static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; + private PlanAccessor planAccessor; private EntityManager entityManager; private MigrationManager migrationManager; @@ -153,8 +157,8 @@ private void newContext(EncryptionManager encryptionManager) { .register(new ResourceEntity(resourceAccessor, resourceManager, fileResolver, entityManager)) .register(new Entity<>(EntityManager.resourceRevisions, resourceRevisionAccessor, ResourceRevision.class)); - entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class)); - entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); + entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, "parameter")); + entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, "parameter")); entityManager.registerImportHook(new ResourceImporter(resourceManager)); migrationManager = new MigrationManager(); @@ -297,8 +301,8 @@ public void testExportAllPlansWithParameters() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(2,exportResult.getMessages().size()); - assertEquals(EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[1]); - assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_WARN,exportResult.getMessages().toArray()[1]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); EncryptionManager encryptionManager = new EncryptionManager() { @Override @@ -369,7 +373,7 @@ public void testImportNewEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); //Override previous encryption manager to simulate new instance EncryptionManager encryptionManagerNewInstance = new EncryptionManager() { @@ -426,7 +430,7 @@ public void testImportNoEncryptionManager() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(EXPORT_ENCRYPT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_ENCRYPT_WARN,exportResult.getMessages().toArray()[0]); // Create a new context without encryption manager newContext(null); @@ -463,7 +467,7 @@ public void testImportProtectedToEncrypted() throws Exception { ExportResult exportResult = exportManager.exportAll(exportConfig); assertEquals(1,exportResult.getMessages().size()); - assertEquals(EXPORT_PROTECT_PARAM_WARN,exportResult.getMessages().toArray()[0]); + assertEquals(EXPORT_PROTECT_WARN,exportResult.getMessages().toArray()[0]); ImportManager importManager = createNewContextAndGetImportManager(); ImportConfiguration importConfiguration = new ImportConfiguration(testExportFile, dummyObjectEnricher(), null, true); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java similarity index 66% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java index 36dd2b8c8f..a4f510760c 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityExportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.plugins.parametermanager; +package step.plugins.encryption; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; @@ -28,12 +28,14 @@ public class EncryptedEntityExportBiConsumer implements BiConsumer { - public static String EXPORT_PROTECT_PARAM_WARN = "The parameter list contains protected parameter. The values of these parameters won't be exported and will have to be reset at import."; - public static String EXPORT_ENCRYPT_PARAM_WARN = "The parameter list contains encrypted parameters. The values of these parameters will be reset if you import them on an other installation of step."; + private static String EXPORT_PROTECT_WARN = "The %s list contains protected parameter. The values of these %ss won't be exported and will have to be reset at import."; + private static String EXPORT_ENCRYPT_WARN = "The %s list contains encrypted %ss. The values of these %ss will be reset if you import them on an other installation of step."; private final Class clazz; + private final String entityNameForLog; - public EncryptedEntityExportBiConsumer(Class clazz) { + public EncryptedEntityExportBiConsumer(Class clazz, String entityNameForLog) { this.clazz = clazz; + this.entityNameForLog = entityNameForLog; } @Override @@ -44,11 +46,19 @@ public void accept(Object object_, ExportContext exportContext) { if (param.getProtectedValue() != null && param.getProtectedValue()) { if (param.getValue() != null) { param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - exportContext.addMessage(EXPORT_PROTECT_PARAM_WARN); + exportContext.addMessage(getExportProtectParamWarn()); } else { - exportContext.addMessage(EXPORT_ENCRYPT_PARAM_WARN); + exportContext.addMessage(getExportEncryptParamWarn()); } } } } + + private String getExportProtectParamWarn(){ + return String.format(EXPORT_PROTECT_WARN, entityNameForLog, entityNameForLog); + } + + private String getExportEncryptParamWarn(){ + return String.format(EXPORT_ENCRYPT_WARN, entityNameForLog, entityNameForLog, entityNameForLog); + } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java similarity index 69% rename from step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java index c13294bf2a..c93322f3a3 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/EncryptedEntityImportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java @@ -16,30 +16,31 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.plugins.parametermanager; +package step.plugins.encryption; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; import step.core.encryption.EncryptionManagerException; import step.core.imports.ImportContext; -import step.parameter.Parameter; import step.parameter.ParameterManager; import java.util.function.BiConsumer; public class EncryptedEntityImportBiConsumer implements BiConsumer { - public static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted parameter which could not be decrypted. The values of these parameters will be reset."; - public static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted parameters. The values of these parameters will be reset."; - public static String IMPORT_RESET_WARN = "The export file contains protected parameters. Their values must be reset."; + private static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted %s which could not be decrypted. The values of these %ss will be reset."; + private static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted %ss. The values of these %ss will be reset."; + private static String IMPORT_RESET_WARN = "The export file contains protected %ss. Their values must be reset."; private final EncryptionManager encryptionManager; private final Class clazz; + private final String entityNameForLog; - public EncryptedEntityImportBiConsumer(EncryptionManager encryptionManager, Class clazz) { + public EncryptedEntityImportBiConsumer(EncryptionManager encryptionManager, Class clazz, String entityNameForLog) { this.encryptionManager = encryptionManager; this.clazz = clazz; + this.entityNameForLog = entityNameForLog; } @Override @@ -56,18 +57,30 @@ public void accept(Object object_, ImportContext importContext) { } catch (EncryptionManagerException e) { param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_FAIL_WARN); + importContext.addMessage(getImportDecryptFailWarn()); } } else { param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); param.setEncryptedValue(null); - importContext.addMessage(IMPORT_DECRYPT_NO_EM_WARN); + importContext.addMessage(getImportDecryptNoEmWarn()); } } else { param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); - importContext.addMessage(IMPORT_RESET_WARN); + importContext.addMessage(getImportResetWarn()); } } } } + + private String getImportDecryptFailWarn() { + return String.format(IMPORT_DECRYPT_FAIL_WARN, entityNameForLog, entityNameForLog); + } + + private String getImportDecryptNoEmWarn() { + return String.format(IMPORT_DECRYPT_NO_EM_WARN, entityNameForLog, entityNameForLog); + } + + private String getImportResetWarn() { + return String.format(IMPORT_RESET_WARN, entityNameForLog); + } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index a2a64f6a05..0f2b53b66a 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -37,6 +37,8 @@ import step.encryption.AbstractEncryptedValuesManager; import step.parameter.Parameter; import step.parameter.ParameterManager; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; import step.plugins.encryption.EncryptionManagerDependencyPlugin; import step.plugins.screentemplating.*; @@ -80,8 +82,8 @@ public void serverStart(GlobalContext context) { Parameter.ENTITY_NAME, parameterAccessor, Parameter.class)); - context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class)); - context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, parameterManager.getEntityNameForLogging())); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, parameterManager.getEntityNameForLogging())); context.getServiceRegistrationCallback().registerService(ParameterServices.class); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 505fdc13df..46849eb974 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -33,8 +33,8 @@ import step.framework.server.tables.TableRegistry; import step.encryption.AbstractEncryptedValuesManager; import step.plugins.encryption.EncryptionManagerDependencyPlugin; -import step.plugins.parametermanager.EncryptedEntityImportBiConsumer; -import step.plugins.parametermanager.EncryptedEntityExportBiConsumer; +import step.plugins.encryption.EncryptedEntityImportBiConsumer; +import step.plugins.encryption.EncryptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; import step.projectsettings.ProjectSetting; import step.projectsettings.ProjectSettingAccessor; @@ -80,8 +80,8 @@ public void serverStart(GlobalContext context) { ProjectSetting.ENTITY_NAME, projectSettingAccessor, ProjectSetting.class)); - context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class)); - context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class)); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); context.getServiceRegistrationCallback().registerService(ProjectSettingServices.class); } diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index 225a68422b..d8f3fe14f7 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -228,6 +228,6 @@ public String getDefaultScriptEngine() { return defaultScriptEngine; } - protected abstract String getEntityNameForLogging(); + public abstract String getEntityNameForLogging(); } diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index b7408d9d37..2dfb373e56 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -48,7 +48,7 @@ protected Accessor getAccessor() { } @Override - protected String getEntityNameForLogging() { + public String getEntityNameForLogging() { return "parameter"; } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 027b4f1843..40ed5f54c7 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -44,7 +44,7 @@ protected Accessor getAccessor() { } @Override - protected String getEntityNameForLogging() { + public String getEntityNameForLogging() { return "project setting"; } } From a13677eebde880b5285d27ed0d0f910a62bc62ec Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Fri, 25 Apr 2025 19:57:38 +0300 Subject: [PATCH 09/26] SED-3921: move classes --- .../EncryptedEntityExportBiConsumer.java | 13 +++---- .../EncryptedEntityImportBiConsumer.java | 18 ++++------ .../ProjectSettingAccessor.java | 3 +- .../ProjectSettingAccessorImpl.java | 4 +-- .../ProjectSettingControllerPlugin.java | 2 -- .../ProjectSettingServices.java | 3 +- .../RemoteProjectSettingAccessor.java | 35 ------------------- .../step/core/EncryptedTrackedObject.java | 2 -- .../main/java/step/parameter/Parameter.java | 1 - .../step/projectsettings/ProjectSetting.java | 6 ---- .../AbstractEncryptedValuesManager.java | 16 ++++----- .../java/step/parameter/ParameterManager.java | 10 ++++++ 12 files changed, 34 insertions(+), 79 deletions(-) rename {step-core/src/main/java/step => step-controller/step-controller-base-plugins/src/main/java/step/plugins}/projectsettings/ProjectSettingAccessor.java (92%) rename {step-core/src/main/java/step => step-controller/step-controller-base-plugins/src/main/java/step/plugins}/projectsettings/ProjectSettingAccessorImpl.java (93%) delete mode 100644 step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java index a4f510760c..6e4f815ea7 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java @@ -21,15 +21,12 @@ import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; import step.core.export.ExportContext; -import step.parameter.Parameter; -import step.parameter.ParameterManager; +import step.encryption.AbstractEncryptedValuesManager; import java.util.function.BiConsumer; public class EncryptedEntityExportBiConsumer implements BiConsumer { - private static String EXPORT_PROTECT_WARN = "The %s list contains protected parameter. The values of these %ss won't be exported and will have to be reset at import."; - private static String EXPORT_ENCRYPT_WARN = "The %s list contains encrypted %ss. The values of these %ss will be reset if you import them on an other installation of step."; private final Class clazz; private final String entityNameForLog; @@ -41,11 +38,11 @@ public EncryptedEntityExportBiConsumer(Class c @Override public void accept(Object object_, ExportContext exportContext) { if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { - Parameter param = (Parameter) object_; + EncryptedTrackedObject param = (EncryptedTrackedObject) object_; //if protected and not encrypted, mask value by changing it to reset value if (param.getProtectedValue() != null && param.getProtectedValue()) { if (param.getValue() != null) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); exportContext.addMessage(getExportProtectParamWarn()); } else { exportContext.addMessage(getExportEncryptParamWarn()); @@ -55,10 +52,10 @@ public void accept(Object object_, ExportContext exportContext) { } private String getExportProtectParamWarn(){ - return String.format(EXPORT_PROTECT_WARN, entityNameForLog, entityNameForLog); + return String.format("The %s list contains protected %s. The values of these %ss won't be exported and will have to be reset at import.", entityNameForLog, entityNameForLog, entityNameForLog); } private String getExportEncryptParamWarn(){ - return String.format(EXPORT_ENCRYPT_WARN, entityNameForLog, entityNameForLog, entityNameForLog); + return String.format("The %s list contains encrypted %ss. The values of these %ss will be reset if you import them on an other installation of step.", entityNameForLog, entityNameForLog, entityNameForLog); } } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java index c93322f3a3..8f6d741b0c 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java @@ -23,16 +23,12 @@ import step.core.encryption.EncryptionManager; import step.core.encryption.EncryptionManagerException; import step.core.imports.ImportContext; -import step.parameter.ParameterManager; +import step.encryption.AbstractEncryptedValuesManager; import java.util.function.BiConsumer; public class EncryptedEntityImportBiConsumer implements BiConsumer { - private static String IMPORT_DECRYPT_FAIL_WARN = "The export file contains encrypted %s which could not be decrypted. The values of these %ss will be reset."; - private static String IMPORT_DECRYPT_NO_EM_WARN = "The export file contains encrypted %ss. The values of these %ss will be reset."; - private static String IMPORT_RESET_WARN = "The export file contains protected %ss. Their values must be reset."; - private final EncryptionManager encryptionManager; private final Class clazz; private final String entityNameForLog; @@ -55,17 +51,17 @@ public void accept(Object object_, ImportContext importContext) { try { encryptionManager.decrypt(param.getEncryptedValue()); } catch (EncryptionManagerException e) { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); param.setEncryptedValue(null); importContext.addMessage(getImportDecryptFailWarn()); } } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); param.setEncryptedValue(null); importContext.addMessage(getImportDecryptNoEmWarn()); } } else { - param.setValue(new DynamicValue<>(ParameterManager.RESET_VALUE)); + param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); importContext.addMessage(getImportResetWarn()); } } @@ -73,14 +69,14 @@ public void accept(Object object_, ImportContext importContext) { } private String getImportDecryptFailWarn() { - return String.format(IMPORT_DECRYPT_FAIL_WARN, entityNameForLog, entityNameForLog); + return String.format("The export file contains encrypted %s which could not be decrypted. The values of these %ss will be reset.", entityNameForLog, entityNameForLog); } private String getImportDecryptNoEmWarn() { - return String.format(IMPORT_DECRYPT_NO_EM_WARN, entityNameForLog, entityNameForLog); + return String.format("The export file contains encrypted %ss. The values of these %ss will be reset.", entityNameForLog, entityNameForLog); } private String getImportResetWarn() { - return String.format(IMPORT_RESET_WARN, entityNameForLog); + return String.format("The export file contains protected %ss. Their values must be reset.", entityNameForLog); } } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java similarity index 92% rename from step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java index ae14a78fdf..28972c2ec2 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java @@ -16,9 +16,10 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.projectsettings; +package step.plugins.projectsettings; import step.core.accessors.Accessor; +import step.projectsettings.ProjectSetting; public interface ProjectSettingAccessor extends Accessor { } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java similarity index 93% rename from step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java rename to step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java index c7c55a8bc8..1de780014f 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with STEP. If not, see . ******************************************************************************/ -package step.projectsettings; +package step.plugins.projectsettings; import step.core.collections.Collection; import step.core.settings.AbstractSettingAccessorWithHook; +import step.projectsettings.ProjectSetting; -// TODO: is step-core a good place for these classes? public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { public ProjectSettingAccessorImpl(Collection collectionDriver) { diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 46849eb974..8a86c98057 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -37,8 +37,6 @@ import step.plugins.encryption.EncryptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; import step.projectsettings.ProjectSetting; -import step.projectsettings.ProjectSettingAccessor; -import step.projectsettings.ProjectSettingAccessorImpl; import step.projectsettings.ProjectSettingManager; import java.util.Set; diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index b93cf7d493..01d0ab564b 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -24,11 +24,10 @@ import step.framework.server.security.SecuredContext; import step.projectsettings.ProjectSetting; -// TODO: think about permissions @Path("/project-settings") @Tag(name = "ProjectSettings") @Tag(name = "Entity=ProjectSetting") -@SecuredContext(key = "entity", value = "projectsetting") +@SecuredContext(key = "entity", value = "project-setting") public class ProjectSettingServices extends AbstractEntityServices { public ProjectSettingServices() { diff --git a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java b/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java deleted file mode 100644 index 8fb08e51fc..0000000000 --- a/step-controller/step-controller-remote-client/src/main/java/step/client/accessors/RemoteProjectSettingAccessor.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, exense GmbH - * - * This file is part of STEP - * - * STEP is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * STEP is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with STEP. If not, see . - ******************************************************************************/ -package step.client.accessors; - -import step.client.collections.remote.RemoteCollectionFactory; -import step.core.accessors.AbstractAccessor; -import step.projectsettings.ProjectSetting; -import step.projectsettings.ProjectSettingAccessor; - -// TODO: maybe not required -public class RemoteProjectSettingAccessor extends AbstractAccessor implements ProjectSettingAccessor { - - public RemoteProjectSettingAccessor(RemoteCollectionFactory remoteCollectionFactory) { - super(remoteCollectionFactory.getCollection( ProjectSetting.ENTITY_NAME, ProjectSetting.class)); - } - - -} - diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java index 4172dd7dd5..ff2bc7d57b 100644 --- a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -77,7 +77,5 @@ public void setKey(String key) { this.key = key; } - public abstract ParameterScope getScope(); - public abstract String getScopeEntity(); } diff --git a/step-core-model/src/main/java/step/parameter/Parameter.java b/step-core-model/src/main/java/step/parameter/Parameter.java index 6ede0be520..dc790263ad 100644 --- a/step-core-model/src/main/java/step/parameter/Parameter.java +++ b/step-core-model/src/main/java/step/parameter/Parameter.java @@ -76,7 +76,6 @@ public void setDescription(String description) { /** * @return the {@link ParameterScope} of this parameter */ - @Override public ParameterScope getScope() { return scope; } diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index c4c68580d1..c4d90adee0 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -79,12 +79,6 @@ public void setDescription(String description) { this.description = description; } - - @Override - public ParameterScope getScope() { - return ParameterScope.GLOBAL; - } - @Override public String getScopeEntity() { return null; diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index d8f3fe14f7..72e06a3e07 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -29,7 +29,6 @@ import step.core.encryption.EncryptionManagerException; import step.core.objectenricher.ObjectPredicate; import step.core.plugins.exceptions.PluginCriticalException; -import step.parameter.ParameterScope; import javax.script.Bindings; import javax.script.ScriptException; @@ -62,14 +61,7 @@ public static T maskProtectedValue(T obj) { } public T save(T newObj, T sourceObj, String modificationUser) { - if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { - throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); - } - - ParameterScope scope = newObj.getScope(); - if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { - throw new EncryptedValueManagerException("Scope entity cannot be set for " + getEntityNameForLogging() + "s with GLOBAL scope."); - } + validateBeforeSave(newObj); if(sourceObj != null && isProtected(sourceObj)) { // protected value should not be changed @@ -98,6 +90,12 @@ public T save(T newObj, T sourceObj, String modificationUser) { return getAccessor().save(newObj); } + protected void validateBeforeSave(T newObj) { + if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { + throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); + } + } + protected abstract Accessor getAccessor(); public static boolean isProtected(T p) { diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index 2dfb373e56..5c754944fe 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -24,6 +24,7 @@ import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; import step.encryption.AbstractEncryptedValuesManager; +import step.encryption.EncryptedValueManagerException; public class ParameterManager extends AbstractEncryptedValuesManager { @@ -56,4 +57,13 @@ public Accessor getParameterAccessor() { return getAccessor(); } + @Override + protected void validateBeforeSave(Parameter newObj) { + super.validateBeforeSave(newObj); + + ParameterScope scope = newObj.getScope(); + if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { + throw new EncryptedValueManagerException("Scope entity cannot be set for " + getEntityNameForLogging() + "s with GLOBAL scope."); + } + } } From e59fafcb425d6ed4b8b0f16f6c4dc7ece0357366 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Fri, 2 May 2025 15:19:22 +0300 Subject: [PATCH 10/26] SED-3921: object overlapper for project settings (draft) --- pom.xml | 2 +- .../ProjectSettingServices.java | 52 +++++++++++++++++++ .../core/deployment/AbstractStepServices.java | 4 ++ .../ProjectSettingManager.java | 17 ++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5670c88695..a49acc7dab 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 2025.3.14 2.3.0 - 2025.4.24-6809e18112b6ec36ea977bfe + 0.0.0-SNAPSHOT 3.0.23 diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 01d0ab564b..59dacdbcb5 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -19,10 +19,22 @@ package step.plugins.projectsettings; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import step.controller.services.async.AsyncTaskStatus; import step.controller.services.entities.AbstractEntityServices; +import step.core.deployment.ControllerServiceException; +import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; +import step.framework.server.tables.service.bulk.TableBulkOperationReport; +import step.framework.server.tables.service.bulk.TableBulkOperationRequest; import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingManager; + +import java.util.List; @Path("/project-settings") @Tag(name = "ProjectSettings") @@ -30,8 +42,48 @@ @SecuredContext(key = "entity", value = "project-setting") public class ProjectSettingServices extends AbstractEntityServices { + private ProjectSettingManager manager; + public ProjectSettingServices() { super(ProjectSetting.ENTITY_NAME); } + @PostConstruct + public void init() throws Exception { + super.init(); + manager = getContext().require(ProjectSettingManager.class); + } + + @Override + protected ProjectSetting beforeSave(ProjectSetting entity) { + getObjectOverlapper().onBeforeSave(entity); + return super.beforeSave(entity); + } + + @GET + @Path("/unique/all") + @Produces(MediaType.APPLICATION_JSON) + @Secured(right = "{entity}-read") + public List getUniqueSettings() { + try { + return manager.getAllSettingsWithUniqueKeys(getObjectOverlapper()); + } catch (Exception e) { + throw new ControllerServiceException(e.getMessage()); + } + } + + @Override + public AsyncTaskStatus cloneEntities(TableBulkOperationRequest request) { + throw new UnsupportedOperationException("Clone is not supported for project settings"); + } + + @Override + public ProjectSetting clone(String id) { + throw new UnsupportedOperationException("Clone is not supported for project settings"); + } + + @Override + protected ProjectSetting cloneEntity(ProjectSetting entity) { + throw new UnsupportedOperationException("Clone is not supported for project settings"); + } } diff --git a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java index 8becded49f..55259871d7 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java +++ b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java @@ -71,6 +71,10 @@ protected ObjectEnricher getObjectEnricher() { return objectHookRegistry.getObjectEnricher(getSession()); } + protected ObjectOverlapper getObjectOverlapper(){ + return objectHookRegistry.getObjectOverlapper(getSession()); + } + protected ObjectFilter getObjectFilter() { return objectHookRegistry.getObjectFilter(getSession()); } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 40ed5f54c7..224279c48e 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -23,8 +23,15 @@ import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; +import step.core.objectenricher.ObjectOverlapper; import step.encryption.AbstractEncryptedValuesManager; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + public class ProjectSettingManager extends AbstractEncryptedValuesManager { private final Accessor accessor; @@ -47,4 +54,14 @@ protected Accessor getAccessor() { public String getEntityNameForLogging() { return "project setting"; } + + public List getAllSettingsWithUniqueKeys(ObjectOverlapper objectOverlapper) { + // TODO: think if the ObjectFilter should also be applied here + List allSettings = StreamSupport.stream( + Spliterators.spliteratorUnknownSize(accessor.getAll(), Spliterator.ORDERED), + false).collect(Collectors.toList()); + + // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project + return objectOverlapper == null ? allSettings : objectOverlapper.overlapObjects(allSettings); + } } From 227f90da4497e6e1f23f2c431f4b2ebd81443cb9 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Mon, 5 May 2025 13:17:37 +0300 Subject: [PATCH 11/26] SED-3921: object overlapper for project settings --- .../step/multitenancy/TenantOverlapping.java | 27 +++++++++++++++++++ .../step/projectsettings/ProjectSetting.java | 16 +++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java diff --git a/step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java b/step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java new file mode 100644 index 0000000000..d6a147794b --- /dev/null +++ b/step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java @@ -0,0 +1,27 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.multitenancy; + +// TODO: add javadoc +public interface TenantOverlapping { + KEY getKey(); + String getEntityName(); +} diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index c4d90adee0..1a33ee6352 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -21,9 +21,9 @@ import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -import step.parameter.ParameterScope; +import step.multitenancy.TenantOverlapping; -public class ProjectSetting extends EncryptedTrackedObject { +public class ProjectSetting extends EncryptedTrackedObject implements TenantOverlapping { public static final String ENTITY_NAME = "projectsettings"; @@ -53,6 +53,16 @@ public ProjectSetting(Expression activationExpression, String key, String value, this.activationExpression = activationExpression; } + @Override + public String getKey() { + return key; + } + + @Override + public String getEntityName() { + return ENTITY_NAME; + } + @Override public Expression getActivationExpression() { return activationExpression; @@ -88,4 +98,6 @@ public String getScopeEntity() { public String toString() { return "ProjectSetting [key=" + key + "]"; } + + } From 7129af6a9ebf7ce806babe94ce1aec68f1dbcdc0 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Wed, 21 May 2025 08:51:21 +0300 Subject: [PATCH 12/26] SED-3921: support multitenancy for project settings --- .../packages/AutomationPackageServices.java | 9 +-- .../AutomationPackageManagerOSTest.java | 12 ++-- .../packages/junit/JUnitPlansProvider.java | 2 +- .../packages/AutomationPackageManager.java | 55 +++++++------- ...tomationPackagePlansAttributesApplier.java | 12 ++-- .../execution/AutomationPackageExecutor.java | 3 +- ...epositoryWithAutomationPackageSupport.java | 4 +- .../cli/ApLocalExecuteCommandHandler.java | 2 +- .../parametermanager/ParameterServices.java | 2 +- .../ProjectSettingServices.java | 71 +++++++++++-------- .../core/deployment/AbstractStepServices.java | 4 +- ...erlapping.java => TenantUniqueEntity.java} | 10 ++- .../step/projectsettings/ProjectSetting.java | 4 +- .../packages/AutomationPackageContext.java | 11 ++- .../AbstractEncryptedValuesManager.java | 11 ++- .../java/step/parameter/ParameterManager.java | 5 +- .../ProjectSettingManager.java | 48 ++++++++++--- .../AutomationPackageParameterHook.java | 2 +- 18 files changed, 175 insertions(+), 92 deletions(-) rename step-core-model/src/main/java/step/multitenancy/{TenantOverlapping.java => TenantUniqueEntity.java} (84%) diff --git a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java index 3edd7b8fab..6b04cbe689 100644 --- a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java +++ b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java @@ -105,7 +105,7 @@ public String createAutomationPackage(@QueryParam("version") String apVersion, ObjectId id = automationPackageManager.createAutomationPackage( automationPackageInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate() + getObjectEnricher(), getObjectPredicate(), getObjectValidator() ); return id == null ? null : id.toString(); } catch (AutomationPackageManagerException e) { @@ -146,7 +146,8 @@ public List executeAutomationPackage(@FormDataParam("file") InputStream fileDetail == null ? null : fileDetail.getFileName(), executionParameters, getObjectEnricher(), - getObjectPredicate() + getObjectPredicate(), + getObjectValidator() ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -213,7 +214,7 @@ public AutomationPackageUpdateResult updateAutomationPackageMetadata(@PathParam( return automationPackageManager.createOrUpdateAutomationPackage( true, false, new ObjectId(id), uploadedInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate(), async != null && async + getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async != null && async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -244,7 +245,7 @@ public Response createOrUpdateAutomationPackage(@QueryParam("async") Boolean asy try { AutomationPackageUpdateResult result = automationPackageManager.createOrUpdateAutomationPackage( true, true, null, uploadedInputStream, fileDetail.getFileName(), apVersion, activationExpression, - getObjectEnricher(), getObjectPredicate(), async != null && async + getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async != null && async ); Response.ResponseBuilder responseBuilder; if (result.getStatus() == AutomationPackageUpdateStatus.CREATED) { diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java index b52abc3675..b14efc0c35 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java @@ -176,7 +176,7 @@ public void testCrud() throws IOException { String fileName = "step-automation-packages-sample1-extended.jar"; File automationPackageJar = new File("src/test/resources/samples/" + fileName); try (InputStream is = new FileInputStream(automationPackageJar)) { - AutomationPackageUpdateResult result = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, false); + AutomationPackageUpdateResult result = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, null, false); Assert.assertEquals(AutomationPackageUpdateStatus.UPDATED, result.getStatus()); ObjectId resultId = result.getId(); @@ -318,7 +318,7 @@ public void testResourcesInKeywordsAndPlans() throws IOException { try (InputStream is = new FileInputStream(automationPackageJar)) { ObjectId result; - result = manager.createAutomationPackage(is, fileName, null, null, null, null); + result = manager.createAutomationPackage(is, fileName, null, null, null, null, null); AutomationPackage storedPackage = automationPackageAccessor.get(result); List storedPlans = planAccessor.findManyByCriteria(getAutomationPackageIdCriteria(result)).collect(Collectors.toList()); @@ -340,7 +340,7 @@ public void testResourcesInKeywordsAndPlans() throws IOException { @Test public void testInvalidFile() throws IOException { try (InputStream is = new FileInputStream("src/test/resources/step/automation/packages/picture.png")) { - manager.createAutomationPackage(is, "picture.png", null, null, null, null); + manager.createAutomationPackage(is, "picture.png", null, null, null, null, null); Assert.fail("The exception should be thrown in case of invalid automation package file"); } catch (AutomationPackageManagerException ex) { // ok - invalid file should cause the exception @@ -351,7 +351,7 @@ public void testInvalidFile() throws IOException { public void testZipArchive() throws IOException { try (InputStream is = new FileInputStream("src/test/resources/step/automation/packages/step-automation-packages.zip")) { ObjectId result; - result = manager.createAutomationPackage(is, "step-automation-packages.zip", null, null, null, null); + result = manager.createAutomationPackage(is, "step-automation-packages.zip", null, null, null, null, null); AutomationPackage storedPackage = automationPackageAccessor.get(result); List storedPlans = planAccessor.findManyByCriteria(getAutomationPackageIdCriteria(result)).collect(Collectors.toList()); @@ -425,9 +425,9 @@ private SampleUploadingResult uploadSample1WithAsserts(boolean createNew, boolea try (InputStream is = new FileInputStream(automationPackageJar)) { ObjectId result; if (createNew) { - result = manager.createAutomationPackage(is, fileName, null, null, null, null); + result = manager.createAutomationPackage(is, fileName, null, null, null, null,null); } else { - AutomationPackageUpdateResult updateResult = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, async); + AutomationPackageUpdateResult updateResult = manager.createOrUpdateAutomationPackage(true, true, null, is, fileName, null, null, null, null, null, async); if (async && expectedDelay) { Assert.assertEquals(AutomationPackageUpdateStatus.UPDATE_DELAYED, updateResult.getStatus()); } else { diff --git a/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java b/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java index 490e80e99f..ffa81d9574 100644 --- a/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java +++ b/step-automation-packages/step-automation-packages-junit-core/src/main/java/step/automation/packages/junit/JUnitPlansProvider.java @@ -47,7 +47,7 @@ public List getTestPlans(ExecutionEngine executionEngine) AutomationPackageFromClassLoaderProvider automationPackageProvider = new AutomationPackageFromClassLoaderProvider(testClass.getClassLoader()); ObjectId automationPackageId = automationPackageManager.createOrUpdateAutomationPackage( false, true, null, automationPackageProvider, null, null, - true, null, null, false + true, null, null, null, false ).getId(); List planFilterList = new ArrayList<>(); diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java index 928ecfad07..a43adb7a42 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java @@ -29,10 +29,7 @@ import step.core.accessors.AbstractOrganizableObject; import step.core.collections.IndexField; import step.core.entities.Entity; -import step.core.objectenricher.EnricheableObject; -import step.core.objectenricher.ObjectEnricher; -import step.core.objectenricher.ObjectEnricherComposer; -import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.*; import step.core.plans.InMemoryPlanAccessor; import step.core.plans.Plan; import step.core.plans.PlanAccessor; @@ -283,7 +280,8 @@ protected void deleteAutomationPackageEntities(AutomationPackage automationPacka deletePlans(automationPackage); // schedules will be deleted in deleteAdditionalData via hooks deleteResources(automationPackage); - deleteAdditionalData(automationPackage, new AutomationPackageContext(operationMode, resourceManager, null, null,null, extensions)); + // TODO: archive, packageContent, enricher and validator are only used in AutomationPackageContext for save/update - maybe it is better to create separate class AutomationPackageContextForSave rather then use nulls here + deleteAdditionalData(automationPackage, new AutomationPackageContext(operationMode, resourceManager, null, null,null, null, extensions)); } /** @@ -296,8 +294,9 @@ protected void deleteAutomationPackageEntities(AutomationPackage automationPacka * @return the id of created package * @throws AutomationPackageManagerException */ - public ObjectId createAutomationPackage(InputStream packageStream, String fileName, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate) throws AutomationPackageManagerException { - return createOrUpdateAutomationPackage(false, true, null, packageStream, fileName, apVersion, activationExpr, enricher, objectPredicate, false).getId(); + public ObjectId createAutomationPackage(InputStream packageStream, String fileName, String apVersion, String activationExpr, + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator validator) throws AutomationPackageManagerException { + return createOrUpdateAutomationPackage(false, true, null, packageStream, fileName, apVersion, activationExpr, enricher, objectPredicate, validator, false).getId(); } /** @@ -313,10 +312,11 @@ public ObjectId createAutomationPackage(InputStream packageStream, String fileNa */ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, InputStream inputStream, String fileName, String apVersion, String activationExpr, - ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) throws AutomationPackageManagerException { + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator validator, + boolean async) throws AutomationPackageManagerException { try { try (AutomationPackageArchiveProvider provider = new AutomationPackageFromInputStreamProvider(inputStream, fileName)) { - return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, async); + return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, validator, async); } } catch (IOException | AutomationPackageReadingException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); @@ -372,7 +372,8 @@ public void updateAutomationPackageMetadata(ObjectId id, String apVersion, Strin */ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, AutomationPackageArchiveProvider automationPackageProvider, String apVersion, String activationExpr, - boolean isLocalPackage, ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) { + boolean isLocalPackage, ObjectEnricher enricher, + ObjectPredicate objectPredicate, ObjectValidator validator, boolean async) { AutomationPackageArchive automationPackageArchive; AutomationPackageContent packageContent; @@ -423,7 +424,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all enrichers.add(new AutomationPackageLinkEnricher(newPackage.getId().toString())); ObjectEnricher enricherForIncludedEntities = ObjectEnricherComposer.compose(enrichers); - fillStaging(staging, packageContent, oldPackage, enricherForIncludedEntities, automationPackageArchive, activationExpr); + fillStaging(staging, packageContent, oldPackage, enricherForIncludedEntities, validator, automationPackageArchive, activationExpr); // persist and activate automation package log.debug("Updating automation package, old package is " + ((oldPackage == null) ? "null" : "not null" + ", async: " + async)); @@ -432,7 +433,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all if (oldPackage == null || !async || immediateWriteLock) { //If not async or if it's a new package, we synchronously wait on a write lock and update log.info("Updating the automation package " + newPackage.getId().toString() + " synchronously, any running executions on this package will delay the update."); - ObjectId result = updateAutomationPackage(oldPackage, newPackage, packageContent, staging, enricherForIncludedEntities, immediateWriteLock, automationPackageArchive); + ObjectId result = updateAutomationPackage(oldPackage, newPackage, packageContent, staging, enricherForIncludedEntities, validator, immediateWriteLock, automationPackageArchive); return new AutomationPackageUpdateResult(oldPackage == null ? AutomationPackageUpdateStatus.CREATED : AutomationPackageUpdateStatus.UPDATED, result); } else { // async update @@ -442,7 +443,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all AutomationPackage finalNewPackage = newPackage; delayedUpdateExecutor.submit(() -> { try { - updateAutomationPackage(oldPackage, finalNewPackage, packageContent, staging, enricherForIncludedEntities, false, automationPackageArchive); + updateAutomationPackage(oldPackage, finalNewPackage, packageContent, staging, enricherForIncludedEntities, validator, false, automationPackageArchive); } catch (Exception e) { log.error("Exception on delayed AP update", e); } @@ -473,7 +474,7 @@ public Map> getAllEntities(Obj AutomationPackageHook hook = automationPackageHookRegistry.getHook(hookName); result.putAll(hook.getEntitiesForAutomationPackage( automationPackageId, - new AutomationPackageContext(operationMode, resourceManager, null, null, null, extensions) + new AutomationPackageContext(operationMode, resourceManager, null, null, null, null, extensions) ) ); } @@ -481,7 +482,8 @@ public Map> getAllEntities(Obj } private ObjectId updateAutomationPackage(AutomationPackage oldPackage, AutomationPackage newPackage, - AutomationPackageContent packageContent, AutomationPackageStaging staging, ObjectEnricher enricherForIncludedEntities, + AutomationPackageContent packageContent, AutomationPackageStaging staging, + ObjectEnricher enricherForIncludedEntities, ObjectValidator validator, boolean alreadyLocked, AutomationPackageArchive automationPackageArchive) { try { //If not already locked (i.e. was not able to acquire an immediate write lock) @@ -495,7 +497,7 @@ private ObjectId updateAutomationPackage(AutomationPackage oldPackage, Automatio deleteAutomationPackageEntities(oldPackage); } // persist all staged entities - persistStagedEntities(staging, enricherForIncludedEntities, automationPackageArchive, packageContent); + persistStagedEntities(staging, enricherForIncludedEntities, validator, automationPackageArchive, packageContent); ObjectId result = automationPackageAccessor.save(newPackage).getId(); logAfterSave(staging, oldPackage, newPackage); return result; @@ -543,10 +545,11 @@ protected AutomationPackageStaging createStaging(){ return new AutomationPackageStaging(); } - protected void fillStaging(AutomationPackageStaging staging, AutomationPackageContent packageContent, AutomationPackage oldPackage, ObjectEnricher enricherForIncludedEntities, + protected void fillStaging(AutomationPackageStaging staging, AutomationPackageContent packageContent, AutomationPackage oldPackage, + ObjectEnricher enricherForIncludedEntities, ObjectValidator validatorForIncludedEntities, AutomationPackageArchive automationPackageArchive, String evaluationExpression) { - staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, staging.getResourceManager(), evaluationExpression)); - staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); + staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, validatorForIncludedEntities, staging.getResourceManager(), evaluationExpression)); + staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, validatorForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); List hookEntries = packageContent.getAdditionalData().entrySet().stream().map(e -> new HookEntry(e.getKey(), e.getValue())).collect(Collectors.toList()); List orderedEntryNames = automationPackageHookRegistry.getOrderedHookFieldNames(); @@ -556,7 +559,7 @@ protected void fillStaging(AutomationPackageStaging staging, AutomationPackageCo for (HookEntry hookEntry : hookEntries) { boolean hooked = automationPackageHookRegistry.onPrepareStaging( hookEntry.fieldName, - new AutomationPackageContext(operationMode, staging.getResourceManager(), automationPackageArchive, packageContent, enricherForIncludedEntities, extensions), + new AutomationPackageContext(operationMode, staging.getResourceManager(), automationPackageArchive, packageContent, enricherForIncludedEntities, validatorForIncludedEntities, extensions), packageContent, hookEntry.values, oldPackage, staging); @@ -569,6 +572,7 @@ protected void fillStaging(AutomationPackageStaging staging, AutomationPackageCo protected void persistStagedEntities(AutomationPackageStaging staging, ObjectEnricher objectEnricher, + ObjectValidator objectValidator, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent) { List stagingResources = staging.getResourceManager().findManyByCriteria(null); @@ -599,7 +603,7 @@ protected void persistStagedEntities(AutomationPackageStaging staging, for (HookEntry hookEntry : hookEntries) { boolean hooked = automationPackageHookRegistry.onCreate( hookEntry.fieldName, hookEntry.values, - new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, objectEnricher, extensions) + new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, objectEnricher, objectValidator, extensions) ); if (!hooked) { log.warn("Additional field in automation package has been ignored and skipped: " + hookEntry.fieldName); @@ -617,11 +621,11 @@ public void runExtensionsBeforeIsolatedExecution(AutomationPackage automationPac } protected List preparePlansStaging(AutomationPackageContent packageContent, AutomationPackageArchive automationPackageArchive, - AutomationPackage oldPackage, ObjectEnricher enricher, ResourceManager stagingResourceManager, + AutomationPackage oldPackage, ObjectEnricher enricher, ObjectValidator validator, ResourceManager stagingResourceManager, String evaluationExpression) { List plans = packageContent.getPlans(); AutomationPackagePlansAttributesApplier specialAttributesApplier = new AutomationPackagePlansAttributesApplier(stagingResourceManager); - specialAttributesApplier.applySpecialAttributesToPlans(plans, automationPackageArchive, packageContent, enricher, extensions, operationMode); + specialAttributesApplier.applySpecialAttributesToPlans(plans, automationPackageArchive, packageContent, enricher, validator, extensions, operationMode); fillEntities(plans, oldPackage != null ? getPackagePlans(oldPackage.getId()) : new ArrayList<>(), enricher); if (evaluationExpression != null && !evaluationExpression.isEmpty()){ @@ -632,9 +636,10 @@ protected List preparePlansStaging(AutomationPackageContent packageContent return plans; } - protected List prepareFunctionsStaging(AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, ObjectEnricher enricher, + protected List prepareFunctionsStaging(AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, + ObjectEnricher enricher, ObjectValidator validator, AutomationPackage oldPackage, ResourceManager stagingResourceManager, String evaluationExpression) { - AutomationPackageContext apContext = new AutomationPackageContext(operationMode, stagingResourceManager, automationPackageArchive, packageContent, enricher, extensions); + AutomationPackageContext apContext = new AutomationPackageContext(operationMode, stagingResourceManager, automationPackageArchive, packageContent, enricher, validator, extensions); List completeFunctions = packageContent.getKeywords().stream().map(keyword -> keyword.prepareKeyword(apContext)).collect(Collectors.toList()); // get old functions with same name and reuse their ids diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java index fb0c841069..ff70879ac1 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackagePlansAttributesApplier.java @@ -24,6 +24,7 @@ import step.core.entities.EntityManager; import step.core.entities.EntityReference; import step.core.objectenricher.ObjectEnricher; +import step.core.objectenricher.ObjectValidator; import step.core.plans.Plan; import step.resources.Resource; import step.resources.ResourceManager; @@ -55,15 +56,18 @@ public AutomationPackagePlansAttributesApplier(ResourceManager resourceManager) public void applySpecialAttributesToPlans(List plans, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, - ObjectEnricher objectEnricher, Map extensions, AutomationPackageOperationMode operationMode) { - AutomationPackageContext apContext = prepareContext(operationMode, automationPackageArchive, packageContent, objectEnricher, extensions); + ObjectEnricher objectEnricher, ObjectValidator validator, + Map extensions, + AutomationPackageOperationMode operationMode) { + AutomationPackageContext apContext = prepareContext(operationMode, automationPackageArchive, packageContent, objectEnricher, validator, extensions); for (Plan plan : plans) { applySpecialValuesForArtifact(plan.getRoot(), apContext); } } - protected AutomationPackageContext prepareContext(AutomationPackageOperationMode operationMode, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, ObjectEnricher enricher, Map extensions) { - return new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, enricher, extensions); + protected AutomationPackageContext prepareContext(AutomationPackageOperationMode operationMode, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, + ObjectEnricher enricher, ObjectValidator validator, Map extensions) { + return new AutomationPackageContext(operationMode, resourceManager, automationPackageArchive, packageContent, enricher, validator, extensions); } private void applySpecialValuesForArtifact(AbstractArtefact artifact, AutomationPackageContext apContext) { diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java index dbccb1426f..3588026f0a 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/AutomationPackageExecutor.java @@ -29,6 +29,7 @@ import step.core.execution.model.*; import step.core.objectenricher.ObjectEnricher; import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.ObjectValidator; import step.core.plans.Plan; import step.core.plans.PlanFilter; import step.core.repositories.RepositoryObjectManager; @@ -87,7 +88,7 @@ public List runDeployedAutomationPackage(ObjectId automationPackageId, } public List runInIsolation(InputStream apInputStream, String inputStreamFileName, IsolatedAutomationPackageExecutionParameters parameters, - ObjectEnricher objectEnricher, ObjectPredicate objectPredicate) { + ObjectEnricher objectEnricher, ObjectPredicate objectPredicate, ObjectValidator validator) { ObjectId contextId = new ObjectId(); List executions = new ArrayList<>(); diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java index bb2bb6c938..a9bb5f6b48 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/execution/RepositoryWithAutomationPackageSupport.java @@ -90,6 +90,8 @@ public TestSetStatusOverview getTestSetStatusOverview(Map reposi PackageExecutionContext ctx = null; try { File artifact = getArtifact(repositoryParameters); + + // we don't pass the ObjectValidator here, because it is only required to validate the entity before persist into DB ctx = createIsolatedPackageExecutionContext(null, objectPredicate, new ObjectId().toString(), new AutomationPackageFile(artifact, null), false); TestSetStatusOverview overview = new TestSetStatusOverview(); List runs = getFilteredPackagePlans(ctx.getAutomationPackage(), repositoryParameters, ctx.getAutomationPackageManager()) @@ -285,7 +287,7 @@ public PackageExecutionContext createIsolatedPackageExecutionContext(ObjectEnric // create single automation package in isolated manager try (FileInputStream fis = new FileInputStream(apFile.getFile())) { // the apVersion is null (we always use the actual version), because we only create the isolated in-memory AP here - inMemoryPackageManager.createAutomationPackage(fis, apFile.getFile().getName(), null, null, enricher, predicate); + inMemoryPackageManager.createAutomationPackage(fis, apFile.getFile().getName(), null, null, enricher, predicate, null); } catch (IOException e) { throw new AutomationPackageManagerException("Cannot read the AP file: " + apFile.getFile().getName()); } diff --git a/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java b/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java index 37bee80614..6ca7c12519 100644 --- a/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java +++ b/step-cli/step-cli-launcher/src/main/java/step/cli/ApLocalExecuteCommandHandler.java @@ -62,7 +62,7 @@ public void afterExecutionEnd(ExecutionContext context) { AutomationPackageFromInputStreamProvider automationPackageProvider = new AutomationPackageFromInputStreamProvider(is, apFile.getName()); ObjectId automationPackageId = automationPackageManager.createOrUpdateAutomationPackage( false, true, null, automationPackageProvider, null, null, - true, null, null, false + true, null, null, null, false ).getId(); PlanFilter planFilters = getPlanFilters(includePlans, excludePlans, includeCategories, excludeCategories); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index f6262bd65f..7f3140f596 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -100,7 +100,7 @@ public Parameter save(Parameter newParameter) { private Parameter save(Parameter newParameter, Parameter sourceParameter) { assertRights(newParameter); try { - return AbstractEncryptedValuesManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername())); + return AbstractEncryptedValuesManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername(), getObjectValidator())); } catch (EncryptedValueManagerException e) { throw new ControllerServiceException(e.getMessage()); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 59dacdbcb5..2f0bcf55ed 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -20,70 +20,85 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.PostConstruct; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; -import step.controller.services.async.AsyncTaskStatus; -import step.controller.services.entities.AbstractEntityServices; +import org.bson.types.ObjectId; +import step.core.deployment.AbstractStepServices; import step.core.deployment.ControllerServiceException; +import step.encryption.AbstractEncryptedValuesManager; +import step.encryption.EncryptedValueManagerException; import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; -import step.framework.server.tables.service.bulk.TableBulkOperationReport; -import step.framework.server.tables.service.bulk.TableBulkOperationRequest; import step.projectsettings.ProjectSetting; import step.projectsettings.ProjectSettingManager; import java.util.List; +// TODO: check if secured context works ok for AbstractStepServices @Path("/project-settings") @Tag(name = "ProjectSettings") @Tag(name = "Entity=ProjectSetting") @SecuredContext(key = "entity", value = "project-setting") -public class ProjectSettingServices extends AbstractEntityServices { +public class ProjectSettingServices extends AbstractStepServices { private ProjectSettingManager manager; + private ProjectSettingAccessor accessor; public ProjectSettingServices() { - super(ProjectSetting.ENTITY_NAME); } @PostConstruct public void init() throws Exception { super.init(); manager = getContext().require(ProjectSettingManager.class); + accessor = getContext().require(ProjectSettingAccessor.class); } - @Override - protected ProjectSetting beforeSave(ProjectSetting entity) { - getObjectOverlapper().onBeforeSave(entity); - return super.beforeSave(entity); + @POST + @Secured(right = "{entity}-read") + public ProjectSetting save(ProjectSetting newSetting) { + if (newSetting.getKey() == null || newSetting.getKey().isBlank()) { + throw new ControllerServiceException("The parameter's key is mandatory."); + } + + ProjectSetting oldSetting; + if (newSetting.getId() != null) { + oldSetting = accessor.get(newSetting.getId()); + } else { + oldSetting = null; + } + + return save(newSetting, oldSetting); + } + + @DELETE + @Path("/{id}") + @Secured(right = "{entity}-delete") + public void delete(@PathParam("id") String id) { + assertEntityIsAcceptableInContext(accessor.get(id)); + accessor.remove(new ObjectId(id)); + } + + private ProjectSetting save(ProjectSetting newSetting, ProjectSetting sourceSetting) { + try { + ProjectSetting result = manager.save(newSetting, sourceSetting, getSession().getUser().getUsername(), getObjectValidator()); + return AbstractEncryptedValuesManager.maskProtectedValue(result); + } catch (EncryptedValueManagerException e) { + throw new ControllerServiceException(e.getMessage()); + } } + @GET @Path("/unique/all") @Produces(MediaType.APPLICATION_JSON) @Secured(right = "{entity}-read") public List getUniqueSettings() { try { - return manager.getAllSettingsWithUniqueKeys(getObjectOverlapper()); + return manager.getAllSettingsWithUniqueKeys(); } catch (Exception e) { throw new ControllerServiceException(e.getMessage()); } } - @Override - public AsyncTaskStatus cloneEntities(TableBulkOperationRequest request) { - throw new UnsupportedOperationException("Clone is not supported for project settings"); - } - - @Override - public ProjectSetting clone(String id) { - throw new UnsupportedOperationException("Clone is not supported for project settings"); - } - - @Override - protected ProjectSetting cloneEntity(ProjectSetting entity) { - throw new UnsupportedOperationException("Clone is not supported for project settings"); - } } diff --git a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java index 55259871d7..8c1bb3dbb5 100644 --- a/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java +++ b/step-controller/step-controller-server/src/main/java/step/core/deployment/AbstractStepServices.java @@ -71,8 +71,8 @@ protected ObjectEnricher getObjectEnricher() { return objectHookRegistry.getObjectEnricher(getSession()); } - protected ObjectOverlapper getObjectOverlapper(){ - return objectHookRegistry.getObjectOverlapper(getSession()); + protected ObjectValidator getObjectValidator(){ + return objectHookRegistry.getObjectValidator(getSession()); } protected ObjectFilter getObjectFilter() { diff --git a/step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java b/step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java similarity index 84% rename from step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java rename to step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java index d6a147794b..25c7aa4540 100644 --- a/step-core-model/src/main/java/step/multitenancy/TenantOverlapping.java +++ b/step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java @@ -21,7 +21,15 @@ package step.multitenancy; // TODO: add javadoc -public interface TenantOverlapping { +public interface TenantUniqueEntity { + + /** + * Defines the priority in accordance to the tenant + */ + String ATTRIBUTE_PRIORITY = "priority"; + KEY getKey(); + + // TODO: maybe not required String getEntityName(); } diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index 1a33ee6352..4f82bb56ad 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -21,9 +21,9 @@ import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -import step.multitenancy.TenantOverlapping; +import step.multitenancy.TenantUniqueEntity; -public class ProjectSetting extends EncryptedTrackedObject implements TenantOverlapping { +public class ProjectSetting extends EncryptedTrackedObject implements TenantUniqueEntity { public static final String ENTITY_NAME = "projectsettings"; diff --git a/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java b/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java index 5d77abd9e4..7503b50ee5 100644 --- a/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java +++ b/step-core/src/main/java/step/automation/packages/AutomationPackageContext.java @@ -19,6 +19,7 @@ package step.automation.packages; import step.core.objectenricher.ObjectEnricher; +import step.core.objectenricher.ObjectValidator; import step.resources.ResourceManager; import java.util.Map; @@ -33,6 +34,7 @@ public class AutomationPackageContext { private AutomationPackageArchive automationPackageArchive; private AutomationPackageContent packageContent; + private ObjectValidator validator; private ObjectEnricher enricher; private String uploadedPackageFileResource; @@ -42,12 +44,15 @@ public class AutomationPackageContext { public AutomationPackageContext(AutomationPackageOperationMode operationMode, ResourceManager resourceManager, AutomationPackageArchive automationPackageArchive, AutomationPackageContent packageContent, - ObjectEnricher enricher, Map extensions) { + ObjectEnricher enricher, + ObjectValidator validator, + Map extensions) { this.operationMode = Objects.requireNonNull(operationMode); this.resourceManager = resourceManager; this.automationPackageArchive = automationPackageArchive; this.packageContent = packageContent; this.enricher = enricher; + this.validator = validator; this.extensions = extensions; } @@ -75,6 +80,10 @@ public ObjectEnricher getEnricher() { return enricher; } + public ObjectValidator getValidator(){ + return validator; + } + public void setAutomationPackageArchive(AutomationPackageArchive automationPackageArchive) { this.automationPackageArchive = automationPackageArchive; } diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index 72e06a3e07..0857dd63e2 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -28,6 +28,7 @@ import step.core.encryption.EncryptionManager; import step.core.encryption.EncryptionManagerException; import step.core.objectenricher.ObjectPredicate; +import step.core.objectenricher.ObjectValidator; import step.core.plugins.exceptions.PluginCriticalException; import javax.script.Bindings; @@ -60,8 +61,8 @@ public static T maskProtectedValue(T obj) { return obj; } - public T save(T newObj, T sourceObj, String modificationUser) { - validateBeforeSave(newObj); + public T save(T newObj, T sourceObj, String modificationUser, ObjectValidator objectValidator) { + validateBeforeSave(newObj, objectValidator); if(sourceObj != null && isProtected(sourceObj)) { // protected value should not be changed @@ -90,7 +91,11 @@ public T save(T newObj, T sourceObj, String modificationUser) { return getAccessor().save(newObj); } - protected void validateBeforeSave(T newObj) { + protected void validateBeforeSave(T newObj, ObjectValidator objectValidator) { + if (objectValidator != null) { + objectValidator.validateOnSave(newObj); + } + if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); } diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index 5c754944fe..f6252b9c20 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -23,6 +23,7 @@ import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; +import step.core.objectenricher.ObjectValidator; import step.encryption.AbstractEncryptedValuesManager; import step.encryption.EncryptedValueManagerException; @@ -58,8 +59,8 @@ public Accessor getParameterAccessor() { } @Override - protected void validateBeforeSave(Parameter newObj) { - super.validateBeforeSave(newObj); + protected void validateBeforeSave(Parameter newObj, ObjectValidator objectValidator) { + super.validateBeforeSave(newObj, objectValidator); ParameterScope scope = newObj.getScope(); if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 224279c48e..3d100589ce 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -19,16 +19,17 @@ package step.projectsettings; import ch.exense.commons.app.Configuration; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import step.commons.activation.Activator; +import step.core.accessors.AbstractOrganizableObject; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; -import step.core.objectenricher.ObjectOverlapper; import step.encryption.AbstractEncryptedValuesManager; +import step.multitenancy.TenantUniqueEntity; -import java.util.List; -import java.util.Spliterator; -import java.util.Spliterators; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -55,13 +56,44 @@ public String getEntityNameForLogging() { return "project setting"; } - public List getAllSettingsWithUniqueKeys(ObjectOverlapper objectOverlapper) { + public List getAllSettingsWithUniqueKeys() { + return getEntitiesWithHighestPriority().stream().map(e -> (ProjectSetting) e).collect(Collectors.toList()); + } + + private List getEntitiesWithHighestPriority() { // TODO: think if the ObjectFilter should also be applied here - List allSettings = StreamSupport.stream( + + // TODO: maybe extract this logic to some common class (plugin) + List allSettings = StreamSupport.stream( Spliterators.spliteratorUnknownSize(accessor.getAll(), Spliterator.ORDERED), - false).collect(Collectors.toList()); + false).map(e -> (TenantUniqueEntity) e).collect(Collectors.toList()); // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project - return objectOverlapper == null ? allSettings : objectOverlapper.overlapObjects(allSettings); + List highestPriorityProjectSettings = new ArrayList<>(); + ListMultimap groupedByKey = ArrayListMultimap.create(); + for (TenantUniqueEntity setting : allSettings) { + groupedByKey.put(setting.getKey(), setting); + } + + for (Object key : groupedByKey.keys()) { + List settingsWithTheSameKey = groupedByKey.get(key); + TenantUniqueEntity settingWithHighestPriority = null; + for (TenantUniqueEntity projectSetting : settingsWithTheSameKey) { + String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(TenantUniqueEntity.ATTRIBUTE_PRIORITY); + if (settingWithHighestPriority == null) { + settingWithHighestPriority = projectSetting; + } else { + String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(TenantUniqueEntity.ATTRIBUTE_PRIORITY); + if (Objects.equals(currentPriority, otherPriority)) { + throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); + } else if (currentPriority == null && otherPriority != null) { + settingWithHighestPriority = projectSetting; + } else if (otherPriority != null && Integer.parseInt(currentPriority) < Integer.parseInt(otherPriority)) { + settingWithHighestPriority = projectSetting; + } + } + } + } + return highestPriorityProjectSettings; } } diff --git a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java index 435578b260..e27411e977 100644 --- a/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java +++ b/step-plans/step-plans-core/src/main/java/step/parameter/automation/AutomationPackageParameterHook.java @@ -73,7 +73,7 @@ public void onCreate(List entities, AutomationPackageContex // enrich with automation package id context.getEnricher().accept(entity); try { - getParameterManager(context.getExtensions()).save(entity, null, null); + getParameterManager(context.getExtensions()).save(entity, null, null, context.getValidator()); } catch (EncryptedValueManagerException e) { log.error("The automation package parameter {} cannot be saved for automation package {}.", entity.getKey(), context.getAutomationPackageArchive().getOriginalFileName(), e); throw new EncryptedValueManagerException("The automation package parameter " + entity.getKey() + " cannot be saved: " + e.getMessage()); From e6cb62a64a36bd59fdfb972c0d9b0dcc75628921 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Thu, 22 May 2025 08:47:01 +0300 Subject: [PATCH 13/26] SED-3921: support multitenancy for project settings (step framework version) --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dfe9da1c3b..5806d79f83 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,8 @@ 2025.3.14 2025.5.12-6821d8e4cbc831232c03e85a - 0.0.0-SNAPSHOT + + 2025.5.2-6814b7eef0c7a36de808d7d9 3.0.23 From 64491327f6aff6747078c4fccf144e43082d5c33 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Thu, 22 May 2025 09:05:31 +0300 Subject: [PATCH 14/26] SED-3921: support multitenancy for project settings (step framework version) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5806d79f83..8ac06290a2 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 2025.3.14 2025.5.12-6821d8e4cbc831232c03e85a - 2025.5.2-6814b7eef0c7a36de808d7d9 + 2025.5.21-682d628b2c2d5a21a7c04ffa 3.0.23 From 4f40c9d4f2843fdc1635b859749ac4a5e581ec76 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 27 May 2025 09:15:49 +0300 Subject: [PATCH 15/26] SED-3921: support multitenancy for project settings (validator for unique entities) --- .../ProjectSettingControllerPlugin.java | 24 +++++++ .../unique/UniqueEntityControllerPlugin.java | 71 +++++++++++++++++++ .../step/projectsettings/ProjectSetting.java | 4 +- .../EntityWithUniqueAttributes.java} | 6 +- .../ProjectSettingManager.java | 24 +++---- .../java/step/unique/UniqueEntityManager.java | 58 +++++++++++++++ 6 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java rename step-core-model/src/main/java/step/{multitenancy/TenantUniqueEntity.java => unique/EntityWithUniqueAttributes.java} (88%) create mode 100644 step-core/src/main/java/step/unique/UniqueEntityManager.java diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 8a86c98057..40202dd830 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import step.core.AbstractContext; import step.core.GlobalContext; import step.core.collections.Collection; import step.core.collections.Filters; @@ -27,6 +28,7 @@ import step.core.deployment.ObjectHookControllerPlugin; import step.core.encryption.EncryptionManager; import step.core.entities.Entity; +import step.core.objectenricher.*; import step.core.plugins.AbstractControllerPlugin; import step.core.plugins.Plugin; import step.framework.server.tables.Table; @@ -81,6 +83,28 @@ public void serverStart(GlobalContext context) { context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); + context.require(ObjectHookRegistry.class).add(new ObjectHook() { + @Override + public ObjectFilter getObjectFilter(AbstractContext abstractContext) { + return null; + } + + @Override + public ObjectEnricher getObjectEnricher(AbstractContext abstractContext) { + return null; + } + + @Override + public void rebuildContext(AbstractContext abstractContext, EnricheableObject enricheableObject) throws Exception { + + } + + @Override + public boolean isObjectAcceptableInContext(AbstractContext abstractContext, EnricheableObject enricheableObject) { + return false; + } + }); + context.getServiceRegistrationCallback().registerService(ProjectSettingServices.class); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java new file mode 100644 index 0000000000..dfe9f2dde2 --- /dev/null +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (C) 2020, exense GmbH + * + * This file is part of STEP + * + * STEP is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * STEP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with STEP. If not, see . + ******************************************************************************/ +package step.plugins.unique; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.AbstractContext; +import step.core.GlobalContext; +import step.core.collections.CollectionFactory; +import step.core.deployment.ObjectHookControllerPlugin; +import step.core.objectenricher.*; +import step.core.plugins.AbstractControllerPlugin; +import step.core.plugins.Plugin; +import step.unique.UniqueEntityManager; + +@Plugin(dependencies= {ObjectHookControllerPlugin.class}) +public class UniqueEntityControllerPlugin extends AbstractControllerPlugin { + + public static Logger logger = LoggerFactory.getLogger(UniqueEntityControllerPlugin.class); + + @Override + public void serverStart(GlobalContext context) { + + UniqueEntityManager uniqueEntityManager = new UniqueEntityManager(); + context.put(UniqueEntityManager.class, uniqueEntityManager); + + context.require(ObjectHookRegistry.class).add(new ObjectHook() { + @Override + public ObjectFilter getObjectFilter(AbstractContext abstractContext) { + return null; + } + + @Override + public ObjectEnricher getObjectEnricher(AbstractContext abstractContext) { + return null; + } + + @Override + public void rebuildContext(AbstractContext abstractContext, EnricheableObject enricheableObject) throws Exception { + + } + + @Override + public boolean isObjectAcceptableInContext(AbstractContext abstractContext, EnricheableObject enricheableObject) { + return false; + } + + @Override + public ObjectValidator getObjectValidator(AbstractContext context) { + return uniqueEntityManager.createObjectValidator(context.require(CollectionFactory.class)); + } + }); + } + +} diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index 4f82bb56ad..b77d836a2b 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -21,9 +21,9 @@ import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -import step.multitenancy.TenantUniqueEntity; +import step.unique.EntityWithUniqueAttributes; -public class ProjectSetting extends EncryptedTrackedObject implements TenantUniqueEntity { +public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { public static final String ENTITY_NAME = "projectsettings"; diff --git a/step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java similarity index 88% rename from step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java rename to step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java index 25c7aa4540..95cdcd1482 100644 --- a/step-core-model/src/main/java/step/multitenancy/TenantUniqueEntity.java +++ b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java @@ -18,13 +18,13 @@ * * along with STEP. If not, see . * ***************************************************************************** */ -package step.multitenancy; +package step.unique; // TODO: add javadoc -public interface TenantUniqueEntity { +public interface EntityWithUniqueAttributes { /** - * Defines the priority in accordance to the tenant + * Defines the priority (for instance, in accordance to the tenant) */ String ATTRIBUTE_PRIORITY = "priority"; diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 3d100589ce..99fd7c19f0 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -27,7 +27,7 @@ import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; import step.encryption.AbstractEncryptedValuesManager; -import step.multitenancy.TenantUniqueEntity; +import step.unique.EntityWithUniqueAttributes; import java.util.*; import java.util.stream.Collectors; @@ -60,30 +60,30 @@ public List getAllSettingsWithUniqueKeys() { return getEntitiesWithHighestPriority().stream().map(e -> (ProjectSetting) e).collect(Collectors.toList()); } - private List getEntitiesWithHighestPriority() { + private List getEntitiesWithHighestPriority() { // TODO: think if the ObjectFilter should also be applied here // TODO: maybe extract this logic to some common class (plugin) - List allSettings = StreamSupport.stream( + List allSettings = StreamSupport.stream( Spliterators.spliteratorUnknownSize(accessor.getAll(), Spliterator.ORDERED), - false).map(e -> (TenantUniqueEntity) e).collect(Collectors.toList()); + false).map(e -> (EntityWithUniqueAttributes) e).collect(Collectors.toList()); // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project - List highestPriorityProjectSettings = new ArrayList<>(); - ListMultimap groupedByKey = ArrayListMultimap.create(); - for (TenantUniqueEntity setting : allSettings) { + List highestPriorityProjectSettings = new ArrayList<>(); + ListMultimap groupedByKey = ArrayListMultimap.create(); + for (EntityWithUniqueAttributes setting : allSettings) { groupedByKey.put(setting.getKey(), setting); } for (Object key : groupedByKey.keys()) { - List settingsWithTheSameKey = groupedByKey.get(key); - TenantUniqueEntity settingWithHighestPriority = null; - for (TenantUniqueEntity projectSetting : settingsWithTheSameKey) { - String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(TenantUniqueEntity.ATTRIBUTE_PRIORITY); + List settingsWithTheSameKey = groupedByKey.get(key); + EntityWithUniqueAttributes settingWithHighestPriority = null; + for (EntityWithUniqueAttributes projectSetting : settingsWithTheSameKey) { + String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); if (settingWithHighestPriority == null) { settingWithHighestPriority = projectSetting; } else { - String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(TenantUniqueEntity.ATTRIBUTE_PRIORITY); + String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); if (Objects.equals(currentPriority, otherPriority)) { throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); } else if (currentPriority == null && otherPriority != null) { diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java new file mode 100644 index 0000000000..a28a67ef5b --- /dev/null +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.unique; + +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.CollectionFactory; +import step.core.collections.Filter; +import step.core.collections.Filters; +import step.core.objectenricher.ObjectValidator; + +import java.util.*; + +public class UniqueEntityManager { + + public ObjectValidator createObjectValidator(CollectionFactory collectionFactory) { + return enricheableObject -> { + if (enricheableObject instanceof EntityWithUniqueAttributes) { + EntityWithUniqueAttributes e = (EntityWithUniqueAttributes) enricheableObject; + Collection collection = collectionFactory.getCollection(e.getEntityName(), e.getClass()); + + if (!enricheableObject.getAttributes().isEmpty()) { + ArrayList attrFilter = new ArrayList<>(); + for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } + Filter filterByAttribute = Filters.and(attrFilter); + Optional collision = collection.find(filterByAttribute, null, null, null, 0) + .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), ((AbstractIdentifiableObject) tmp).getId()) && Objects.equals(e.getKey(), tmp.getKey())) + .findFirst(); + if (collision.isPresent()) { + throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", + e.getEntityName(), ((AbstractIdentifiableObject) e).getId(), ((AbstractIdentifiableObject) collision.get()).getId() + )); + } + } + } + }; + } +} From 58f0b8625cfa0850f58ad7552241ae154323333b Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 3 Jun 2025 20:26:04 +0300 Subject: [PATCH 16/26] SED-3921: support multitenancy for project settings (fix remarks) --- .../StepRunnerWithPlansAnnotationTest.java | 4 + .../step/core/export/ExportManagerTest.java | 25 +++- .../EncryptedEntityExportBiConsumer.java | 12 +- .../EncryptedEntityImportBiConsumer.java | 16 ++- .../ParameterManagerControllerPlugin.java | 28 +++- .../parametermanager/ParameterServices.java | 8 +- .../ProjectSettingControllerPlugin.java | 28 +++- .../ProjectSettingServices.java | 18 ++- .../step/core/EncryptedTrackedObject.java | 15 +- .../main/java/step/parameter/Parameter.java | 12 +- .../step/projectsettings/ProjectSetting.java | 44 ++---- .../unique/EntityWithUniqueAttributes.java | 6 +- .../AbstractEncryptedValuesManager.java | 117 ++-------------- .../java/step/parameter/ParameterManager.java | 128 +++++++++++++++++- .../ProjectSettingManager.java | 48 ++++++- 15 files changed, 322 insertions(+), 187 deletions(-) diff --git a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java index a318baa90f..6ec5123c29 100644 --- a/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java +++ b/step-automation-packages/step-automation-packages-junit5/src/test/java/step/junit5/runner/StepRunnerWithPlansAnnotationTest.java @@ -22,6 +22,10 @@ import step.junit.runners.annotations.ExecutionParameters; import step.junit.runners.annotations.Plans; +/** + * This test covers some plans from annotation package. Plans with JMeter keywords are excluded to support local + * launch without installed JMeter. All plans including JMeter are covered by {@link StepAutomationPackageRunAllTest} + */ @ExcludePlans({"JMeter Plan", "testAutomation.plan"}) @Plans({"plans/plan2.plan", "plans/plan3.plan", "plans/assertsTest.plan"}) @ExecutionParameters({"PARAM_EXEC","Value","PARAM_EXEC2","Value","PARAM_EXEC3","Value"}) diff --git a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java index c06ee3e3a9..2faeac9cf1 100644 --- a/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java +++ b/step-controller/step-controller-backend/src/test/java/step/core/export/ExportManagerTest.java @@ -56,6 +56,7 @@ import step.core.plans.PlanEntity; import step.core.plans.builder.PlanBuilder; import step.datapool.excel.ExcelDataPool; +import step.encryption.AbstractEncryptedValuesManager; import step.expressions.ExpressionHandler; import step.functions.Function; import step.functions.accessor.FunctionAccessor; @@ -157,8 +158,28 @@ private void newContext(EncryptionManager encryptionManager) { .register(new ResourceEntity(resourceAccessor, resourceManager, fileResolver, entityManager)) .register(new Entity<>(EntityManager.resourceRevisions, resourceRevisionAccessor, ResourceRevision.class)); - entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, "parameter")); - entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, "parameter")); + entityManager.registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, "parameter") { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); + entityManager.registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, "parameter") { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); entityManager.registerImportHook(new ResourceImporter(resourceManager)); migrationManager = new MigrationManager(); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java index 6e4f815ea7..a8dd871169 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityExportBiConsumer.java @@ -25,7 +25,7 @@ import java.util.function.BiConsumer; -public class EncryptedEntityExportBiConsumer implements BiConsumer { +public abstract class EncryptedEntityExportBiConsumer implements BiConsumer { private final Class clazz; private final String entityNameForLog; @@ -38,11 +38,11 @@ public EncryptedEntityExportBiConsumer(Class c @Override public void accept(Object object_, ExportContext exportContext) { if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { - EncryptedTrackedObject param = (EncryptedTrackedObject) object_; + T param = (T) object_; //if protected and not encrypted, mask value by changing it to reset value if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() != null) { - param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + if (getValue(param) != null) { + setResetValue(param); exportContext.addMessage(getExportProtectParamWarn()); } else { exportContext.addMessage(getExportEncryptParamWarn()); @@ -51,6 +51,10 @@ public void accept(Object object_, ExportContext exportContext) { } } + protected abstract Object getValue(T obj); + + protected abstract void setResetValue(T obj); + private String getExportProtectParamWarn(){ return String.format("The %s list contains protected %s. The values of these %ss won't be exported and will have to be reset at import.", entityNameForLog, entityNameForLog, entityNameForLog); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java index 8f6d741b0c..14cc12bfa1 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/encryption/EncryptedEntityImportBiConsumer.java @@ -27,7 +27,7 @@ import java.util.function.BiConsumer; -public class EncryptedEntityImportBiConsumer implements BiConsumer { +public abstract class EncryptedEntityImportBiConsumer implements BiConsumer { private final EncryptionManager encryptionManager; private final Class clazz; @@ -42,32 +42,36 @@ public EncryptedEntityImportBiConsumer(EncryptionManager encryptionManager, Clas @Override public void accept(Object object_, ImportContext importContext) { if (object_ != null && clazz.isAssignableFrom(object_.getClass())) { - EncryptedTrackedObject param = (EncryptedTrackedObject) object_; + T param = (T) object_; //if importing protected and encrypted value if (param.getProtectedValue() != null && param.getProtectedValue()) { - if (param.getValue() == null) { + if (getValue(param) == null) { //if we have a valid encryption manager and can still decrypt keep encrypted value, else reset if (encryptionManager != null && param.getEncryptedValue() != null) { try { encryptionManager.decrypt(param.getEncryptedValue()); } catch (EncryptionManagerException e) { - param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + setResetValue(param); param.setEncryptedValue(null); importContext.addMessage(getImportDecryptFailWarn()); } } else { - param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + setResetValue(param); param.setEncryptedValue(null); importContext.addMessage(getImportDecryptNoEmWarn()); } } else { - param.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + setResetValue(param); importContext.addMessage(getImportResetWarn()); } } } } + protected abstract Object getValue(T obj); + + protected abstract void setResetValue(T obj); + private String getImportDecryptFailWarn() { return String.format("The export file contains encrypted %s which could not be decrypted. The values of these %ss will be reset.", entityNameForLog, entityNameForLog); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java index 0f2b53b66a..ac8a2e66c1 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterManagerControllerPlugin.java @@ -27,6 +27,7 @@ import step.core.collections.Filters; import step.core.collections.filters.Equals; import step.core.deployment.ObjectHookControllerPlugin; +import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; import step.core.entities.Entity; import step.core.plugins.AbstractControllerPlugin; @@ -68,7 +69,7 @@ public void serverStart(GlobalContext context) { context.put("ParameterAccessor", parameterAccessor); context.get(TableRegistry.class).register(ENTITY_PARAMETERS, new Table<>(collection, "param-read", true) - .withResultItemTransformer((p,s) -> AbstractEncryptedValuesManager.maskProtectedValue(p)) + .withResultItemTransformer((p,s) -> ParameterManager.maskProtectedValue(p)) .withDerivedTableFiltersFactory(lf -> { Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); @@ -82,8 +83,28 @@ public void serverStart(GlobalContext context) { Parameter.ENTITY_NAME, parameterAccessor, Parameter.class)); - context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, parameterManager.getEntityNameForLogging())); - context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, parameterManager.getEntityNameForLogging())); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(Parameter.class, parameterManager.getEntityNameForLogging()) { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, Parameter.class, parameterManager.getEntityNameForLogging()) { + @Override + protected Object getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(Parameter obj) { + obj.setValue(new DynamicValue<>(AbstractEncryptedValuesManager.RESET_VALUE)); + } + }); context.getServiceRegistrationCallback().registerService(ParameterServices.class); } @@ -108,5 +129,4 @@ public ExecutionEnginePlugin getExecutionEnginePlugin() { return new ParameterManagerPlugin(parameterManager); } - } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java index 7f3140f596..3921898d16 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/parametermanager/ParameterServices.java @@ -100,7 +100,7 @@ public Parameter save(Parameter newParameter) { private Parameter save(Parameter newParameter, Parameter sourceParameter) { assertRights(newParameter); try { - return AbstractEncryptedValuesManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername(), getObjectValidator())); + return ParameterManager.maskProtectedValue(parameterManager.save(newParameter, sourceParameter, getSession().getUser().getUsername(), getObjectValidator())); } catch (EncryptedValueManagerException e) { throw new ControllerServiceException(e.getMessage()); } @@ -141,11 +141,11 @@ public void delete(String id) { @Override public Parameter get(String id) { Parameter parameter = parameterAccessor.get(new ObjectId(id)); - return AbstractEncryptedValuesManager.maskProtectedValue(parameter); + return ParameterManager.maskProtectedValue(parameter); } protected List maskProtectedValues(Stream stream) { - return stream.map(AbstractEncryptedValuesManager::maskProtectedValue).collect(Collectors.toList()); + return stream.map(ParameterManager::maskProtectedValue).collect(Collectors.toList()); } @POST @@ -154,7 +154,7 @@ protected List maskProtectedValues(Stream stream) { @Produces(MediaType.APPLICATION_JSON) @Secured(right="{entity}-read") public Parameter getParameterByAttributes(Map attributes) { - return AbstractEncryptedValuesManager.maskProtectedValue(parameterAccessor.findByAttributes(attributes)); + return ParameterManager.maskProtectedValue(parameterAccessor.findByAttributes(attributes)); } @Override diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 40202dd830..5743a6bf59 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -66,13 +66,13 @@ public void serverStart(GlobalContext context) { context.put("ProjectSettingAccessor", projectSettingAccessor); context.get(TableRegistry.class).register(ProjectSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) - .withResultItemTransformer((p,s) -> AbstractEncryptedValuesManager.maskProtectedValue(p)) + .withResultItemTransformer((p,s) -> ProjectSettingManager.maskProtectedValue(p)) .withDerivedTableFiltersFactory(lf -> { Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); })); - ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration(), context.getDynamicBeanResolver()); + ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration()); context.put(ProjectSettingManager.class, projectSettingManager); this.projectSettingManager = projectSettingManager; @@ -80,8 +80,28 @@ public void serverStart(GlobalContext context) { ProjectSetting.ENTITY_NAME, projectSettingAccessor, ProjectSetting.class)); - context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); - context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class, projectSettingManager.getEntityNameForLogging())); + context.getEntityManager().registerExportHook(new EncryptedEntityExportBiConsumer(ProjectSetting.class, projectSettingManager.getEntityNameForLogging()) { + @Override + protected Object getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(ProjectSetting obj) { + obj.setValue(AbstractEncryptedValuesManager.RESET_VALUE); + } + }); + context.getEntityManager().registerImportHook(new EncryptedEntityImportBiConsumer(encryptionManager, ProjectSetting.class, projectSettingManager.getEntityNameForLogging()) { + @Override + protected Object getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setResetValue(ProjectSetting obj) { + obj.setValue(AbstractEncryptedValuesManager.RESET_VALUE); + } + }); context.require(ObjectHookRegistry.class).add(new ObjectHook() { @Override diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 2f0bcf55ed..44d228a8a4 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -25,7 +25,6 @@ import org.bson.types.ObjectId; import step.core.deployment.AbstractStepServices; import step.core.deployment.ControllerServiceException; -import step.encryption.AbstractEncryptedValuesManager; import step.encryption.EncryptedValueManagerException; import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; @@ -38,7 +37,7 @@ @Path("/project-settings") @Tag(name = "ProjectSettings") @Tag(name = "Entity=ProjectSetting") -@SecuredContext(key = "entity", value = "project-setting") +@SecuredContext(key = "entity", value = "project-settings") public class ProjectSettingServices extends AbstractStepServices { private ProjectSettingManager manager; @@ -55,7 +54,7 @@ public void init() throws Exception { } @POST - @Secured(right = "{entity}-read") + @Secured(right = "{entity}-write") public ProjectSetting save(ProjectSetting newSetting) { if (newSetting.getKey() == null || newSetting.getKey().isBlank()) { throw new ControllerServiceException("The parameter's key is mandatory."); @@ -82,7 +81,7 @@ public void delete(@PathParam("id") String id) { private ProjectSetting save(ProjectSetting newSetting, ProjectSetting sourceSetting) { try { ProjectSetting result = manager.save(newSetting, sourceSetting, getSession().getUser().getUsername(), getObjectValidator()); - return AbstractEncryptedValuesManager.maskProtectedValue(result); + return ProjectSettingManager.maskProtectedValue(result); } catch (EncryptedValueManagerException e) { throw new ControllerServiceException(e.getMessage()); } @@ -101,4 +100,15 @@ public List getUniqueSettings() { } } + @GET + @Path("/unique/single") + @Produces(MediaType.APPLICATION_JSON) + @Secured(right = "{entity}-read") + public ProjectSetting getUniqueSettingByKey(@QueryParam("key") String key) { + try { + return manager.getUniqueSettingByKey(key); + } catch (Exception e) { + throw new ControllerServiceException(e.getMessage()); + } + } } diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java index ff2bc7d57b..7d72a2fd87 100644 --- a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -18,40 +18,29 @@ ******************************************************************************/ package step.core; -import step.commons.activation.ActivableObject; import step.core.accessors.AbstractTrackedObject; -import step.core.dynamicbeans.DynamicValue; import step.core.objectenricher.EnricheableObject; import step.parameter.Parameter; -import step.parameter.ParameterScope; -public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements ActivableObject, EnricheableObject, ValueWithKey { +public abstract class EncryptedTrackedObject extends AbstractTrackedObject implements EnricheableObject, ValueWithKey { public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; public static final String PARAMETER_VALUE_FIELD = "value"; protected Boolean protectedValue = false; + /** * When running with an encryption manager, the value of protected * {@link Parameter}s is encrypted and the encrypted value is stored into this * field */ protected String encryptedValue; - protected DynamicValue value; protected String key; public EncryptedTrackedObject() { super(); } - public DynamicValue getValue() { - return value; - } - - public void setValue(DynamicValue value) { - this.value = value; - } - public Boolean getProtectedValue() { return protectedValue; } diff --git a/step-core-model/src/main/java/step/parameter/Parameter.java b/step-core-model/src/main/java/step/parameter/Parameter.java index dc790263ad..d04e05121e 100644 --- a/step-core-model/src/main/java/step/parameter/Parameter.java +++ b/step-core-model/src/main/java/step/parameter/Parameter.java @@ -18,11 +18,12 @@ ******************************************************************************/ package step.parameter; +import step.commons.activation.ActivableObject; import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; import step.core.dynamicbeans.DynamicValue; -public class Parameter extends EncryptedTrackedObject { +public class Parameter extends EncryptedTrackedObject implements ActivableObject { public static final String ENTITY_NAME = "parameters"; @@ -31,6 +32,7 @@ public class Parameter extends EncryptedTrackedObject { protected Expression activationExpression; protected Integer priority; + protected DynamicValue value; protected ParameterScope scope; protected String scopeEntity; @@ -97,6 +99,14 @@ public void setScopeEntity(String scopeEntity) { this.scopeEntity = scopeEntity; } + public DynamicValue getValue() { + return value; + } + + public void setValue(DynamicValue value) { + this.value = value; + } + @Override public String toString() { return "Parameter [key=" + key + "]"; diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index b77d836a2b..cc37b249e9 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -18,39 +18,26 @@ ******************************************************************************/ package step.projectsettings; -import step.commons.activation.Expression; import step.core.EncryptedTrackedObject; -import step.core.dynamicbeans.DynamicValue; import step.unique.EntityWithUniqueAttributes; public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { public static final String ENTITY_NAME = "projectsettings"; + protected String value; protected String description; - protected Expression activationExpression; - protected Integer priority; - - - /** - * When running with an encryption manager, the value of protected - * {@link ProjectSetting}s is encrypted and the encrypted value is stored into this - * field - */ - protected String encryptedValue; public ProjectSetting() { super(); } - public ProjectSetting(Expression activationExpression, String key, String value, String description) { + public ProjectSetting(String key, String value, String description) { super(); this.key = key; - this.value = new DynamicValue<>(value); this.description = description; - this.activationExpression = activationExpression; } @Override @@ -63,24 +50,6 @@ public String getEntityName() { return ENTITY_NAME; } - @Override - public Expression getActivationExpression() { - return activationExpression; - } - - @Override - public Integer getPriority() { - return priority; - } - - public void setActivationExpression(Expression activationExpression) { - this.activationExpression = activationExpression; - } - - public void setPriority(Integer priority) { - this.priority = priority; - } - public String getDescription() { return description; } @@ -94,10 +63,17 @@ public String getScopeEntity() { return null; } + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + @Override public String toString() { return "ProjectSetting [key=" + key + "]"; } - } diff --git a/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java index 95cdcd1482..bd33d274c0 100644 --- a/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java +++ b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java @@ -20,7 +20,10 @@ */ package step.unique; -// TODO: add javadoc +/** + * The interface for entities with unique combination of attributes in the application + * @param + */ public interface EntityWithUniqueAttributes { /** @@ -30,6 +33,5 @@ public interface EntityWithUniqueAttributes { KEY getKey(); - // TODO: maybe not required String getEntityName(); } diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index 0857dd63e2..d6c28793dc 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -20,24 +20,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import step.commons.activation.Activator; import step.core.EncryptedTrackedObject; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; -import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; import step.core.encryption.EncryptionManagerException; -import step.core.objectenricher.ObjectPredicate; import step.core.objectenricher.ObjectValidator; -import step.core.plugins.exceptions.PluginCriticalException; -import javax.script.Bindings; -import javax.script.ScriptException; -import javax.script.SimpleBindings; import java.util.*; -import java.util.stream.Collectors; -public abstract class AbstractEncryptedValuesManager { +public abstract class AbstractEncryptedValuesManager { protected static Logger logger = LoggerFactory.getLogger(AbstractEncryptedValuesManager.class); @@ -54,23 +46,15 @@ public AbstractEncryptedValuesManager(EncryptionManager encryptionManager, Strin this.dynamicBeanResolver = dynamicBeanResolver; } - public static T maskProtectedValue(T obj) { - if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue().getValue())) { - obj.setValue(new DynamicValue<>(PROTECTED_VALUE)); - } - return obj; - } - public T save(T newObj, T sourceObj, String modificationUser, ObjectValidator objectValidator) { validateBeforeSave(newObj, objectValidator); - if(sourceObj != null && isProtected(sourceObj)) { + if (sourceObj != null && isProtected(sourceObj)) { // protected value should not be changed newObj.setProtectedValue(true); // if the protected mask is set as value, reuse source value (i.e. value hasn't been changed) - DynamicValue newValue = newObj.getValue(); - if(newValue != null && !newValue.isDynamic() && newValue.get().equals(PROTECTED_VALUE)) { - newObj.setValue(sourceObj.getValue()); + if (getValue(newObj) != null && !isDynamicValue(newObj) && getStringValue(newObj).equals(PROTECTED_VALUE)) { + setValue(newObj, getValue(sourceObj)); } } @@ -95,10 +79,6 @@ protected void validateBeforeSave(T newObj, ObjectValidator objectValidator) { if (objectValidator != null) { objectValidator.validateOnSave(newObj); } - - if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { - throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); - } } protected abstract Accessor getAccessor(); @@ -110,10 +90,10 @@ public static boolean isProtected(T p) { public T encryptValueIfEncryptionManagerAvailable(T obj) throws EncryptionManagerException { if(encryptionManager != null) { if(isProtected(obj)) { - DynamicValue value = obj.getValue(); - if(value != null && value.get() != null) { - obj.setValue(null); - String encryptedValue = encryptionManager.encrypt(value.get()); + String value = getStringValue(obj); + if(value != null) { + setValue(obj, null); + String encryptedValue = encryptionManager.encrypt(value); obj.setEncryptedValue(encryptedValue); } } @@ -121,82 +101,13 @@ public T encryptValueIfEncryptionManagerAvailable(T obj) throws EncryptionManage return obj; } - public Map getAllValues(Map contextBindings, ObjectPredicate objectPredicate) { - return getAllObjects(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); - } - - public Map getAllObjects(Map contextBindings, ObjectPredicate objectPredicate) { - Map result = new HashMap<>(); - Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; - - Map> objectsMap = new HashMap<>(); - getAccessor().getAll().forEachRemaining(p->{ - if(objectPredicate==null || objectPredicate.test(p)) { - List objects = objectsMap.get(p.getKey()); - if(objects==null) { - objects = new ArrayList<>(); - objectsMap.put(p.getKey(), objects); - } - objects.add(p); - try { - Activator.compileActivationExpression(p, defaultScriptEngine); - } catch (ScriptException e) { - logger.error("Error while compiling activation expression of " + getEntityNameForLogging() + " " + p, e); - } - } - }); + protected abstract boolean isDynamicValue(T obj); + protected abstract String getStringValue(T obj); - for(String key:objectsMap.keySet()) { - List objects = objectsMap.get(key); - T bestMatch = Activator.findBestMatch(bindings, objects, defaultScriptEngine); - if(bestMatch!=null) { - result.put(key, bestMatch); - } - } - resolveAllValues(result, contextBindings); - return result; - } + protected abstract void setValue(T obj, V value); - private void resolveAllValues(Map allObjects, Map contextBindings) { - List unresolvedKeys = new ArrayList<>(allObjects.keySet()); - List resolvedKeys = new ArrayList<>(); - HashMap bindings = new HashMap<>(contextBindings); - int unresolvedCountBeforeIteration; - do { - unresolvedCountBeforeIteration = unresolvedKeys.size(); - unresolvedKeys.forEach(k -> { - T obj = allObjects.get(k); - Boolean protectedValue = obj.getProtectedValue(); - boolean isProtected = isProtected(obj); - DynamicValue value = obj.getValue(); - if (!isProtected && value != null) { - try { - if (value.isDynamic()) { - dynamicBeanResolver.evaluate(obj, bindings); - } - String resolvedValue = obj.getValue().get(); //throw an error if evaluation failed - bindings.put(k, resolvedValue); - resolvedKeys.add(k); - } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("Could not (yet) resolve " + getEntityNameForLogging() + " dynamic value " + obj); - } - } - } else { - //value is not set or is protected, resolution is skipped - resolvedKeys.add(k); - if (logger.isDebugEnabled()) { - logger.debug("Following won't be resolved (null or protected value) " + obj); - } - } - }); - unresolvedKeys.removeAll(resolvedKeys); - } while (!unresolvedKeys.isEmpty() && unresolvedKeys.size() < unresolvedCountBeforeIteration); - if (!unresolvedKeys.isEmpty()) { - throw new PluginCriticalException("Error while resolving " + getEntityNameForLogging() + "s, following " + getEntityNameForLogging() + " s could not be resolved: " + unresolvedKeys); - } - } + protected abstract V getValue(T obj); public void encryptAll() { getAccessor().getAll().forEachRemaining(p->{ @@ -216,13 +127,15 @@ public void resetAllProtectedValues() { getAccessor().getAll().forEachRemaining(p->{ if(isProtected(p)) { logger.info("Resetting " + getEntityNameForLogging() + " " + p); - p.setValue(new DynamicValue<>(RESET_VALUE)); + setValue(p, getResetValue()); p.setEncryptedValue(null); getAccessor().save(p); } }); } + public abstract V getResetValue(); + public EncryptionManager getEncryptionManager() { return encryptionManager; } diff --git a/step-core/src/main/java/step/parameter/ParameterManager.java b/step-core/src/main/java/step/parameter/ParameterManager.java index f6252b9c20..829d44fc44 100644 --- a/step-core/src/main/java/step/parameter/ParameterManager.java +++ b/step-core/src/main/java/step/parameter/ParameterManager.java @@ -22,12 +22,24 @@ import step.commons.activation.Activator; import step.core.accessors.Accessor; import step.core.dynamicbeans.DynamicBeanResolver; +import step.core.dynamicbeans.DynamicValue; import step.core.encryption.EncryptionManager; +import step.core.objectenricher.ObjectPredicate; import step.core.objectenricher.ObjectValidator; +import step.core.plugins.exceptions.PluginCriticalException; import step.encryption.AbstractEncryptedValuesManager; import step.encryption.EncryptedValueManagerException; -public class ParameterManager extends AbstractEncryptedValuesManager { +import javax.script.Bindings; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ParameterManager extends AbstractEncryptedValuesManager> { private final Accessor parameterAccessor; @@ -49,6 +61,31 @@ protected Accessor getAccessor() { return parameterAccessor; } + @Override + protected boolean isDynamicValue(Parameter obj) { + return obj.getValue() != null && obj.getValue().isDynamic(); + } + + @Override + protected String getStringValue(Parameter obj) { + return obj.getValue() == null ? null : obj.getValue().get(); + } + + @Override + protected void setValue(Parameter obj, DynamicValue value) { + obj.setValue(value); + } + + @Override + protected DynamicValue getValue(Parameter obj) { + return obj.getValue(); + } + + @Override + public DynamicValue getResetValue() { + return new DynamicValue<>(RESET_VALUE); + } + @Override public String getEntityNameForLogging() { return "parameter"; @@ -66,5 +103,94 @@ protected void validateBeforeSave(Parameter newObj, ObjectValidator objectValida if(scope != null && scope.equals(ParameterScope.GLOBAL) && newObj.getScopeEntity() != null) { throw new EncryptedValueManagerException("Scope entity cannot be set for " + getEntityNameForLogging() + "s with GLOBAL scope."); } + + if (isProtected(newObj) && newObj.getValue() != null && newObj.getValue().isDynamic()) { + throw new EncryptedValueManagerException("Protected entity (" + getEntityNameForLogging() + ") do not support values with dynamic expression."); + } + + } + + public Map getAllValues(Map contextBindings, ObjectPredicate objectPredicate) { + return getAllObjects(contextBindings, objectPredicate).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().get())); + } + + public Map getAllObjects(Map contextBindings, ObjectPredicate objectPredicate) { + Map result = new HashMap<>(); + Bindings bindings = contextBindings!=null?new SimpleBindings(contextBindings):null; + + Map> objectsMap = new HashMap<>(); + getAccessor().getAll().forEachRemaining(p->{ + if(objectPredicate==null || objectPredicate.test(p)) { + List objects = objectsMap.get(p.getKey()); + if(objects==null) { + objects = new ArrayList<>(); + objectsMap.put(p.getKey(), objects); + } + objects.add(p); + try { + Activator.compileActivationExpression(p, defaultScriptEngine); + } catch (ScriptException e) { + logger.error("Error while compiling activation expression of " + getEntityNameForLogging() + " " + p, e); + } + } + }); + + + for(String key:objectsMap.keySet()) { + List objects = objectsMap.get(key); + Parameter bestMatch = Activator.findBestMatch(bindings, objects, defaultScriptEngine); + if(bestMatch!=null) { + result.put(key, bestMatch); + } + } + resolveAllValues(result, contextBindings); + return result; + } + + private void resolveAllValues(Map allObjects, Map contextBindings) { + List unresolvedKeys = new ArrayList<>(allObjects.keySet()); + List resolvedKeys = new ArrayList<>(); + HashMap bindings = new HashMap<>(contextBindings); + int unresolvedCountBeforeIteration; + do { + unresolvedCountBeforeIteration = unresolvedKeys.size(); + unresolvedKeys.forEach(k -> { + Parameter obj = allObjects.get(k); + Boolean protectedValue = obj.getProtectedValue(); + boolean isProtected = isProtected(obj); + if (!isProtected && getValue(obj) != null) { + try { + if (isDynamicValue(obj) && dynamicBeanResolver != null) { + dynamicBeanResolver.evaluate(obj, bindings); + } + String resolvedValue = getStringValue(obj); //throw an error if evaluation failed + bindings.put(k, resolvedValue); + resolvedKeys.add(k); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Could not (yet) resolve " + getEntityNameForLogging() + " dynamic value " + obj); + } + } + } else { + //value is not set or is protected, resolution is skipped + resolvedKeys.add(k); + if (logger.isDebugEnabled()) { + logger.debug("Following won't be resolved (null or protected value) " + obj); + } + } + }); + unresolvedKeys.removeAll(resolvedKeys); + } while (!unresolvedKeys.isEmpty() && unresolvedKeys.size() < unresolvedCountBeforeIteration); + if (!unresolvedKeys.isEmpty()) { + throw new PluginCriticalException("Error while resolving " + getEntityNameForLogging() + "s, following " + getEntityNameForLogging() + " s could not be resolved: " + unresolvedKeys); + } + } + + + public static Parameter maskProtectedValue(Parameter obj) { + if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue().getValue())) { + obj.setValue(new DynamicValue<>(PROTECTED_VALUE)); + } + return obj; } } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 99fd7c19f0..debd52ebd3 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -24,7 +24,6 @@ import step.commons.activation.Activator; import step.core.accessors.AbstractOrganizableObject; import step.core.accessors.Accessor; -import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; import step.encryption.AbstractEncryptedValuesManager; import step.unique.EntityWithUniqueAttributes; @@ -33,24 +32,57 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -public class ProjectSettingManager extends AbstractEncryptedValuesManager { +public class ProjectSettingManager extends AbstractEncryptedValuesManager { private final Accessor accessor; - public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { - this(accessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE), dynamicBeanResolver); + public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, Configuration configuration) { + this(accessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE)); } - public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { - super(encryptionManager, defaultScriptEngine, dynamicBeanResolver); + public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine) { + // project settings can't be dynamic, so the dynamic bean resolver is null + super(encryptionManager, defaultScriptEngine, null); this.accessor = accessor; } + public static ProjectSetting maskProtectedValue(ProjectSetting obj) { + if(obj != null && isProtected(obj) & !RESET_VALUE.equals(obj.getValue())) { + obj.setValue(PROTECTED_VALUE); + } + return obj; + } + @Override protected Accessor getAccessor() { return accessor; } + @Override + protected boolean isDynamicValue(ProjectSetting obj) { + return false; + } + + @Override + protected String getStringValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + protected void setValue(ProjectSetting obj, String value) { + obj.setValue(value); + } + + @Override + protected String getValue(ProjectSetting obj) { + return obj.getValue(); + } + + @Override + public String getResetValue() { + return RESET_VALUE; + } + @Override public String getEntityNameForLogging() { return "project setting"; @@ -96,4 +128,8 @@ private List getEntitiesWithHighestPriorit } return highestPriorityProjectSettings; } + + public ProjectSetting getUniqueSettingByKey(String key) { + return getAllSettingsWithUniqueKeys().stream().filter(s -> Objects.equals(key, s.getKey())).findFirst().orElse(null); + } } From 1981efe851a0bca29b4c0f5e07718f1a8e01967d Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sat, 14 Jun 2025 15:37:19 +0300 Subject: [PATCH 17/26] SED-3921: unit test on validator --- .../step/core/EncryptedTrackedObject.java | 2 + .../step/projectsettings/ProjectSetting.java | 10 ++- .../unique/EntityWithUniqueAttributes.java | 7 +- .../ProjectSettingManager.java | 2 +- .../java/step/unique/UniqueEntityManager.java | 7 +- .../step/unique/UniqueEntityManagerTest.java | 87 +++++++++++++++++++ 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 step-core/src/test/java/step/unique/UniqueEntityManagerTest.java diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java index 7d72a2fd87..57fe64c1a3 100644 --- a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -27,6 +27,8 @@ public abstract class EncryptedTrackedObject extends AbstractTrackedObject imple public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; public static final String PARAMETER_VALUE_FIELD = "value"; + protected static final String KEY_FIELD_NAME = "key"; + protected Boolean protectedValue = false; /** diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index cc37b249e9..7190a10063 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -21,21 +21,20 @@ import step.core.EncryptedTrackedObject; import step.unique.EntityWithUniqueAttributes; -public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { +public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { public static final String ENTITY_NAME = "projectsettings"; protected String value; protected String description; - - public ProjectSetting() { super(); } public ProjectSetting(String key, String value, String description) { super(); + this.value = value; this.key = key; this.description = description; } @@ -45,6 +44,11 @@ public String getKey() { return key; } + @Override + public String getKeyFieldName() { + return KEY_FIELD_NAME; + } + @Override public String getEntityName() { return ENTITY_NAME; diff --git a/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java index bd33d274c0..06e3e6de04 100644 --- a/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java +++ b/step-core-model/src/main/java/step/unique/EntityWithUniqueAttributes.java @@ -22,16 +22,17 @@ /** * The interface for entities with unique combination of attributes in the application - * @param */ -public interface EntityWithUniqueAttributes { +public interface EntityWithUniqueAttributes { /** * Defines the priority (for instance, in accordance to the tenant) */ String ATTRIBUTE_PRIORITY = "priority"; - KEY getKey(); + String getKey(); + + String getKeyFieldName(); String getEntityName(); } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index debd52ebd3..9be79be1ce 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -92,7 +92,7 @@ public List getAllSettingsWithUniqueKeys() { return getEntitiesWithHighestPriority().stream().map(e -> (ProjectSetting) e).collect(Collectors.toList()); } - private List getEntitiesWithHighestPriority() { + private List getEntitiesWithHighestPriority() { // TODO: think if the ObjectFilter should also be applied here // TODO: maybe extract this logic to some common class (plugin) diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java index a28a67ef5b..8c57b83b9e 100644 --- a/step-core/src/main/java/step/unique/UniqueEntityManager.java +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -37,12 +37,17 @@ public ObjectValidator createObjectValidator(CollectionFactory collectionFactory EntityWithUniqueAttributes e = (EntityWithUniqueAttributes) enricheableObject; Collection collection = collectionFactory.getCollection(e.getEntityName(), e.getClass()); - if (!enricheableObject.getAttributes().isEmpty()) { + if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { ArrayList attrFilter = new ArrayList<>(); for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); } Filter filterByAttribute = Filters.and(attrFilter); + if(e.getKeyFieldName() != null){ + attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); + } + + // filter out the entity with the same ID and apply the filter by key again (if getKeyFieldName returns null and the key is resolved dynamically) Optional collision = collection.find(filterByAttribute, null, null, null, 0) .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), ((AbstractIdentifiableObject) tmp).getId()) && Objects.equals(e.getKey(), tmp.getKey())) .findFirst(); diff --git a/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java new file mode 100644 index 0000000000..d06964896e --- /dev/null +++ b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java @@ -0,0 +1,87 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ + +package step.unique; + +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.collections.Collection; +import step.core.collections.inmemory.InMemoryCollectionFactory; +import step.core.objectenricher.ObjectValidator; +import step.projectsettings.ProjectSetting; + +public class UniqueEntityManagerTest { + private static final Logger log = LoggerFactory.getLogger(UniqueEntityManagerTest.class); + + @Test + public void testObjectValidator(){ + UniqueEntityManager manager = new UniqueEntityManager(); + + InMemoryCollectionFactory inMemoryCollectionFactory = new InMemoryCollectionFactory(null); + Collection projectSettingCollection = inMemoryCollectionFactory.getCollection(ProjectSetting.ENTITY_NAME, ProjectSetting.class); + ProjectSetting ps1 = new ProjectSetting("setting1", "value1", "description1"); + ps1.addAttribute("name", "setting1"); + ps1.addAttribute("project", "project1"); + projectSettingCollection.save(ps1); + + ProjectSetting ps2 = new ProjectSetting("setting2", "value2", "description2"); + ps1.addAttribute("name", "setting2"); + ps1.addAttribute("project", "project2"); + projectSettingCollection.save(ps2); + + ObjectValidator validator = manager.createObjectValidator(inMemoryCollectionFactory); + + // new entity - ok + ProjectSetting newPs = new ProjectSetting("setting3", "value3", "description3"); + newPs.addAttribute("name", "setting3"); + newPs.addAttribute("project", "project_new"); + validator.validateOnSave(newPs); + + // update the same entity (with the same id) - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.setId(ps1.getId()); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project1"); + validator.validateOnSave(newPs); + + // entity with the same key and name, but for another project - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project_new"); + validator.validateOnSave(newPs); + + // collision - the same key, name and project + // entity with the same key and name, but for another project - ok + newPs = new ProjectSetting("setting1", "updated value", "updated description"); + newPs.addAttribute("name", "setting1"); + newPs.addAttribute("project", "project1"); + + try { + validator.validateOnSave(newPs); + Assert.fail("Exception should be thrown here"); + } catch (Exception e) { + log.info("Collision detected - OK: {}", e.getMessage()); + } + + } +} \ No newline at end of file From 8ef55a2a9170a78491e0ef60540013cf932cc38c Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sat, 14 Jun 2025 17:12:11 +0300 Subject: [PATCH 18/26] SED-3921: accessors --- .../ProjectSettingAccessor.java | 25 ----- .../ProjectSettingAccessorImpl.java | 30 ------ .../ProjectSettingControllerPlugin.java | 2 + .../ProjectSettingServices.java | 1 + .../step/core/EncryptedTrackedObject.java | 2 - .../step/projectsettings/ProjectSetting.java | 1 + .../AbstractSettingAccessorWithHook.java | 2 + .../ProjectSettingAccessor.java | 32 +++++++ .../ProjectSettingAccessorImpl.java | 94 +++++++++++++++++++ .../ProjectSettingManager.java | 53 +---------- .../EntityWithUniqueAttributesAccessor.java | 60 ++++++++++++ .../java/step/unique/UniqueEntityManager.java | 33 ++----- 12 files changed, 206 insertions(+), 129 deletions(-) delete mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java delete mode 100644 step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java create mode 100644 step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java create mode 100644 step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java create mode 100644 step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java deleted file mode 100644 index 28972c2ec2..0000000000 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessor.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, exense GmbH - * - * This file is part of STEP - * - * STEP is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * STEP is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with STEP. If not, see . - ******************************************************************************/ -package step.plugins.projectsettings; - -import step.core.accessors.Accessor; -import step.projectsettings.ProjectSetting; - -public interface ProjectSettingAccessor extends Accessor { -} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java deleted file mode 100644 index 1de780014f..0000000000 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingAccessorImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020, exense GmbH - * - * This file is part of STEP - * - * STEP is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * STEP is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with STEP. If not, see . - ******************************************************************************/ -package step.plugins.projectsettings; - -import step.core.collections.Collection; -import step.core.settings.AbstractSettingAccessorWithHook; -import step.projectsettings.ProjectSetting; - -public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { - - public ProjectSettingAccessorImpl(Collection collectionDriver) { - super(collectionDriver); - } -} diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index 5743a6bf59..d5b25740f8 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -39,6 +39,8 @@ import step.plugins.encryption.EncryptedEntityExportBiConsumer; import step.plugins.screentemplating.ScreenTemplatePlugin; import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; +import step.projectsettings.ProjectSettingAccessorImpl; import step.projectsettings.ProjectSettingManager; import java.util.Set; diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 44d228a8a4..5fc38fcbc2 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -29,6 +29,7 @@ import step.framework.server.security.Secured; import step.framework.server.security.SecuredContext; import step.projectsettings.ProjectSetting; +import step.projectsettings.ProjectSettingAccessor; import step.projectsettings.ProjectSettingManager; import java.util.List; diff --git a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java index 57fe64c1a3..7d72a2fd87 100644 --- a/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java +++ b/step-core-model/src/main/java/step/core/EncryptedTrackedObject.java @@ -27,8 +27,6 @@ public abstract class EncryptedTrackedObject extends AbstractTrackedObject imple public static final String PARAMETER_PROTECTED_VALUE_FIELD = "protectedValue"; public static final String PARAMETER_VALUE_FIELD = "value"; - protected static final String KEY_FIELD_NAME = "key"; - protected Boolean protectedValue = false; /** diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index 7190a10063..3179564fc2 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -24,6 +24,7 @@ public class ProjectSetting extends EncryptedTrackedObject implements EntityWithUniqueAttributes { public static final String ENTITY_NAME = "projectsettings"; + public static final String KEY_FIELD_NAME = "key"; protected String value; protected String description; diff --git a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java index 6f30db5d24..a95d773d43 100644 --- a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java +++ b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java @@ -26,6 +26,8 @@ import step.core.accessors.AbstractIdentifiableObject; import step.core.collections.Collection; import step.core.collections.Filters; +import step.core.objectenricher.EnricheableObject; +import step.unique.EntityWithUniqueAttributesAccessor; import java.util.ArrayList; import java.util.List; diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java new file mode 100644 index 0000000000..210a2b1777 --- /dev/null +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java @@ -0,0 +1,32 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.projectsettings; + +import step.core.accessors.Accessor; + +import java.util.List; + +public interface ProjectSettingAccessor extends Accessor { + + List getSettingsWithHighestPriority(); + + ProjectSetting getSettingWithHighestPriority(String key); +} diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java new file mode 100644 index 0000000000..303852e23e --- /dev/null +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java @@ -0,0 +1,94 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.projectsettings; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import step.core.accessors.AbstractOrganizableObject; +import step.core.collections.Collection; +import step.core.collections.Filters; +import step.core.settings.AbstractSettingAccessorWithHook; +import step.unique.EntityWithUniqueAttributes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { + + public ProjectSettingAccessorImpl(Collection collectionDriver) { + super(collectionDriver); + } + + @Override + public List getSettingsWithHighestPriority() { + Stream allEntities = getCollectionDriver().find(Filters.empty(), null, null, null, 0); + return filterSettingsByPriority(allEntities); + } + + @Override + public ProjectSetting getSettingWithHighestPriority(String key) { + Stream allEntities = getCollectionDriver().find(Filters.equals(ProjectSetting.KEY_FIELD_NAME, key), null, null, null, 0); + List filtered = filterSettingsByPriority(allEntities); + if (filtered.size() == 0) { + return null; + } else if (filtered.size() > 1) { + throw new RuntimeException("Non unique project setting is detected by key: " + key); + } else { + return filtered.get(0); + } + } + + private static List filterSettingsByPriority(Stream allEntities) { + // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project + List highestPriorityProjectSettings = new ArrayList<>(); + + ListMultimap groupedByKey = ArrayListMultimap.create(); + allEntities.forEach(e -> { + groupedByKey.put(e.getKey(), e); + }); + + for (Object key : groupedByKey.keys()) { + List settingsWithTheSameKey = groupedByKey.get(key); + ProjectSetting settingWithHighestPriority = null; + for (ProjectSetting projectSetting : settingsWithTheSameKey) { + String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + if (settingWithHighestPriority == null) { + settingWithHighestPriority = projectSetting; + } else { + String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + if (Objects.equals(currentPriority, otherPriority)) { + throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); + } else if (currentPriority == null && otherPriority != null) { + settingWithHighestPriority = projectSetting; + } else if (otherPriority != null && Integer.parseInt(currentPriority) < Integer.parseInt(otherPriority)) { + settingWithHighestPriority = projectSetting; + } + } + } + if (settingWithHighestPriority != null) { + highestPriorityProjectSettings.add(settingWithHighestPriority); + } + } + return highestPriorityProjectSettings; + } +} diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 9be79be1ce..a0dcb56824 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -19,28 +19,22 @@ package step.projectsettings; import ch.exense.commons.app.Configuration; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; import step.commons.activation.Activator; -import step.core.accessors.AbstractOrganizableObject; import step.core.accessors.Accessor; import step.core.encryption.EncryptionManager; import step.encryption.AbstractEncryptedValuesManager; -import step.unique.EntityWithUniqueAttributes; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; public class ProjectSettingManager extends AbstractEncryptedValuesManager { - private final Accessor accessor; + private final ProjectSettingAccessor accessor; - public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, Configuration configuration) { + public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager encryptionManager, Configuration configuration) { this(accessor, encryptionManager, configuration.getProperty("tec.activator.scriptEngine", Activator.DEFAULT_SCRIPT_ENGINE)); } - public ProjectSettingManager(Accessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine) { + public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine) { // project settings can't be dynamic, so the dynamic bean resolver is null super(encryptionManager, defaultScriptEngine, null); this.accessor = accessor; @@ -89,47 +83,10 @@ public String getEntityNameForLogging() { } public List getAllSettingsWithUniqueKeys() { - return getEntitiesWithHighestPriority().stream().map(e -> (ProjectSetting) e).collect(Collectors.toList()); - } - - private List getEntitiesWithHighestPriority() { - // TODO: think if the ObjectFilter should also be applied here - - // TODO: maybe extract this logic to some common class (plugin) - List allSettings = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(accessor.getAll(), Spliterator.ORDERED), - false).map(e -> (EntityWithUniqueAttributes) e).collect(Collectors.toList()); - - // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project - List highestPriorityProjectSettings = new ArrayList<>(); - ListMultimap groupedByKey = ArrayListMultimap.create(); - for (EntityWithUniqueAttributes setting : allSettings) { - groupedByKey.put(setting.getKey(), setting); - } - - for (Object key : groupedByKey.keys()) { - List settingsWithTheSameKey = groupedByKey.get(key); - EntityWithUniqueAttributes settingWithHighestPriority = null; - for (EntityWithUniqueAttributes projectSetting : settingsWithTheSameKey) { - String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); - if (settingWithHighestPriority == null) { - settingWithHighestPriority = projectSetting; - } else { - String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); - if (Objects.equals(currentPriority, otherPriority)) { - throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); - } else if (currentPriority == null && otherPriority != null) { - settingWithHighestPriority = projectSetting; - } else if (otherPriority != null && Integer.parseInt(currentPriority) < Integer.parseInt(otherPriority)) { - settingWithHighestPriority = projectSetting; - } - } - } - } - return highestPriorityProjectSettings; + return accessor.getSettingsWithHighestPriority(); } public ProjectSetting getUniqueSettingByKey(String key) { - return getAllSettingsWithUniqueKeys().stream().filter(s -> Objects.equals(key, s.getKey())).findFirst().orElse(null); + return accessor.getSettingWithHighestPriority(key); } } diff --git a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java new file mode 100644 index 0000000000..33e218cc78 --- /dev/null +++ b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java @@ -0,0 +1,60 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ +package step.unique; + +import step.core.accessors.AbstractAccessor; +import step.core.accessors.AbstractIdentifiableObject; +import step.core.collections.Collection; +import step.core.collections.Filter; +import step.core.collections.Filters; +import step.core.objectenricher.EnricheableObject; + +import java.util.*; + +public class EntityWithUniqueAttributesAccessor extends AbstractAccessor { + + public EntityWithUniqueAttributesAccessor(Collection collectionDriver) { + super((Collection) collectionDriver); + } + + public Optional findDuplicate(EntityWithUniqueAttributes e){ + EnricheableObject enricheableObject = (EnricheableObject) e; + + ArrayList attrFilter = new ArrayList<>(); + if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { + for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } + } + + Filter filterByAttribute = Filters.and(attrFilter); + if(e.getKeyFieldName() != null){ + attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); + } + + // filter out the entity with the same ID and apply the filter by key again (if getKeyFieldName returns null and the key is resolved dynamically) + return getCollectionDriver().find(filterByAttribute, null, null, null, 0) + .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), tmp.getId()) && Objects.equals(e.getKey(), ((EntityWithUniqueAttributes) tmp).getKey())) + .findFirst(); + } + + +} diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java index 8c57b83b9e..8ccfe0ad11 100644 --- a/step-core/src/main/java/step/unique/UniqueEntityManager.java +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -23,8 +23,7 @@ import step.core.accessors.AbstractIdentifiableObject; import step.core.collections.Collection; import step.core.collections.CollectionFactory; -import step.core.collections.Filter; -import step.core.collections.Filters; +import step.core.objectenricher.EnricheableObject; import step.core.objectenricher.ObjectValidator; import java.util.*; @@ -34,29 +33,15 @@ public class UniqueEntityManager { public ObjectValidator createObjectValidator(CollectionFactory collectionFactory) { return enricheableObject -> { if (enricheableObject instanceof EntityWithUniqueAttributes) { - EntityWithUniqueAttributes e = (EntityWithUniqueAttributes) enricheableObject; - Collection collection = collectionFactory.getCollection(e.getEntityName(), e.getClass()); - - if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { - ArrayList attrFilter = new ArrayList<>(); - for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { - attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); - } - Filter filterByAttribute = Filters.and(attrFilter); - if(e.getKeyFieldName() != null){ - attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); - } - - // filter out the entity with the same ID and apply the filter by key again (if getKeyFieldName returns null and the key is resolved dynamically) - Optional collision = collection.find(filterByAttribute, null, null, null, 0) - .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), ((AbstractIdentifiableObject) tmp).getId()) && Objects.equals(e.getKey(), tmp.getKey())) - .findFirst(); - if (collision.isPresent()) { - throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", - e.getEntityName(), ((AbstractIdentifiableObject) e).getId(), ((AbstractIdentifiableObject) collision.get()).getId() - )); - } + Collection collection = collectionFactory.getCollection(((EntityWithUniqueAttributes) enricheableObject).getEntityName(), enricheableObject.getClass()); + EntityWithUniqueAttributesAccessor accessor = new EntityWithUniqueAttributesAccessor<>(collection); + Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject); + if (duplicate.isPresent()) { + throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", + ((EntityWithUniqueAttributes) enricheableObject).getEntityName(), ((AbstractIdentifiableObject) enricheableObject).getId(), ((AbstractIdentifiableObject) duplicate.get()).getId() + )); } + } }; } From 885b9e8eff33422fd219b7fa6725d8aae30e220e Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sat, 14 Jun 2025 19:17:05 +0300 Subject: [PATCH 19/26] SED-3921: fix ProjectSettingAccessorImpl --- .../step/projectsettings/ProjectSetting.java | 6 +- .../ProjectSettingAccessorImpl.java | 18 ++--- .../ProjectSettingAccessorImplTest.java | 81 +++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java diff --git a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java index 3179564fc2..b18a9d718a 100644 --- a/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java +++ b/step-core-model/src/main/java/step/projectsettings/ProjectSetting.java @@ -78,7 +78,9 @@ public void setValue(String value) { @Override public String toString() { - return "ProjectSetting [key=" + key + "]"; + return "ProjectSetting{" + + "value='" + value + '\'' + + ", key='" + key + '\'' + + '}'; } - } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java index 303852e23e..9013a23e09 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java @@ -22,7 +22,6 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; -import step.core.accessors.AbstractOrganizableObject; import step.core.collections.Collection; import step.core.collections.Filters; import step.core.settings.AbstractSettingAccessorWithHook; @@ -47,8 +46,8 @@ public List getSettingsWithHighestPriority() { @Override public ProjectSetting getSettingWithHighestPriority(String key) { - Stream allEntities = getCollectionDriver().find(Filters.equals(ProjectSetting.KEY_FIELD_NAME, key), null, null, null, 0); - List filtered = filterSettingsByPriority(allEntities); + Stream filteredByKey = getCollectionDriver().find(Filters.equals(ProjectSetting.KEY_FIELD_NAME, key), null, null, null, 0); + List filtered = filterSettingsByPriority(filteredByKey); if (filtered.size() == 0) { return null; } else if (filtered.size() > 1) { @@ -67,20 +66,21 @@ private static List filterSettingsByPriority(Stream settingsWithTheSameKey = groupedByKey.get(key); ProjectSetting settingWithHighestPriority = null; + for (ProjectSetting projectSetting : settingsWithTheSameKey) { - String otherPriority = ((AbstractOrganizableObject) projectSetting).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); if (settingWithHighestPriority == null) { settingWithHighestPriority = projectSetting; } else { - String currentPriority = ((AbstractOrganizableObject) settingWithHighestPriority).getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); - if (Objects.equals(currentPriority, otherPriority)) { + String currentPriority = projectSetting.getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + String highestPriority = settingWithHighestPriority.getAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY); + if (Objects.equals(highestPriority, currentPriority)) { throw new RuntimeException("Validation failed. 2 setting with same keys " + key + " with various priorities have been detected"); - } else if (currentPriority == null && otherPriority != null) { + } else if (highestPriority == null && currentPriority != null) { settingWithHighestPriority = projectSetting; - } else if (otherPriority != null && Integer.parseInt(currentPriority) < Integer.parseInt(otherPriority)) { + } else if (currentPriority != null && Integer.parseInt(highestPriority) < Integer.parseInt(currentPriority)) { settingWithHighestPriority = projectSetting; } } diff --git a/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java new file mode 100644 index 0000000000..fad1695faa --- /dev/null +++ b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java @@ -0,0 +1,81 @@ +/* + * ****************************************************************************** + * * Copyright (C) 2020, exense GmbH + * * + * * This file is part of STEP + * * + * * STEP is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * STEP is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with STEP. If not, see . + * ***************************************************************************** + */ + +package step.projectsettings; + +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import step.core.collections.inmemory.InMemoryCollection; +import step.unique.EntityWithUniqueAttributes; + +import java.util.List; + +public class ProjectSettingAccessorImplTest extends TestCase { + + private static final Logger log = LoggerFactory.getLogger(ProjectSettingAccessorImplTest.class); + + @Test + public void testGetSettingsWithHighestPriority(){ + ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); + prepareTestData(accessor); + + List settingsWithHighestPriority = accessor.getSettingsWithHighestPriority(); + log.info("Found settings: {}", settingsWithHighestPriority); + + Assert.assertEquals(3, settingsWithHighestPriority.size()); + + ProjectSetting s = settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key1")).findFirst().orElseThrow(); + Assert.assertEquals("value with priority", s.getValue()); + + settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key2")).findFirst().orElseThrow(); + settingsWithHighestPriority.stream().filter(ss -> ss.getKey().equals("key3")).findFirst().orElseThrow(); + } + + @Test + public void testGetSettingWithHighestPriority(){ + ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); + prepareTestData(accessor); + ProjectSetting s = accessor.getSettingWithHighestPriority("key1"); + Assert.assertEquals("value with priority", s.getValue()); + } + + private static void prepareTestData(ProjectSettingAccessorImpl accessor) { + ProjectSetting s1 = new ProjectSetting("key1", "value1", "description"); + s1.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "10"); + + ProjectSetting s2 = new ProjectSetting("key2", "value2", "description"); + s2.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "10"); + + ProjectSetting s3 = new ProjectSetting("key1", "value with priority", "description"); + s3.addAttribute(EntityWithUniqueAttributes.ATTRIBUTE_PRIORITY, "100"); + + // without priority attribute + ProjectSetting s4 = new ProjectSetting("key3", "value3", "description"); + + accessor.save(s1); + accessor.save(s2); + accessor.save(s3); + accessor.save(s4); + } +} \ No newline at end of file From 2f7f7d149839c8ca0f6cec7e9dcaf3e1e8e431b5 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sun, 15 Jun 2025 16:42:01 +0300 Subject: [PATCH 20/26] SED-3921: object filter in ProjectSettingAccessor --- .../ProjectSettingServices.java | 4 +-- .../ProjectSettingAccessor.java | 7 +++-- .../ProjectSettingAccessorImpl.java | 28 ++++++++++++++++--- .../ProjectSettingManager.java | 9 +++--- .../ProjectSettingAccessorImplTest.java | 4 +-- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 5fc38fcbc2..25c2b88b49 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -95,7 +95,7 @@ private ProjectSetting save(ProjectSetting newSetting, ProjectSetting sourceSett @Secured(right = "{entity}-read") public List getUniqueSettings() { try { - return manager.getAllSettingsWithUniqueKeys(); + return manager.getAllSettingsWithUniqueKeys(getObjectFilter()); } catch (Exception e) { throw new ControllerServiceException(e.getMessage()); } @@ -107,7 +107,7 @@ public List getUniqueSettings() { @Secured(right = "{entity}-read") public ProjectSetting getUniqueSettingByKey(@QueryParam("key") String key) { try { - return manager.getUniqueSettingByKey(key); + return manager.getUniqueSettingByKey(key, getObjectFilter()); } catch (Exception e) { throw new ControllerServiceException(e.getMessage()); } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java index 210a2b1777..c49ca7473d 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessor.java @@ -21,12 +21,15 @@ package step.projectsettings; import step.core.accessors.Accessor; +import step.core.objectenricher.ObjectFilter; import java.util.List; public interface ProjectSettingAccessor extends Accessor { - List getSettingsWithHighestPriority(); + List getSettingsWithHighestPriority(ObjectFilter filter); - ProjectSetting getSettingWithHighestPriority(String key); + ProjectSetting getSettingWithHighestPriority(String key, ObjectFilter filter); + + List getSettingByKey(String key, ObjectFilter objectFilter); } diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java index 9013a23e09..36db005f79 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingAccessorImpl.java @@ -23,13 +23,17 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import step.core.collections.Collection; +import step.core.collections.Filter; import step.core.collections.Filters; +import step.core.objectenricher.ObjectFilter; +import step.core.ql.OQLFilterBuilder; import step.core.settings.AbstractSettingAccessorWithHook; import step.unique.EntityWithUniqueAttributes; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import java.util.stream.Stream; public class ProjectSettingAccessorImpl extends AbstractSettingAccessorWithHook implements ProjectSettingAccessor { @@ -39,14 +43,18 @@ public ProjectSettingAccessorImpl(Collection collectionDriver) { } @Override - public List getSettingsWithHighestPriority() { - Stream allEntities = getCollectionDriver().find(Filters.empty(), null, null, null, 0); + public List getSettingsWithHighestPriority(ObjectFilter filter) { + Stream allEntities = getCollectionDriver().find(getFilterNotNull(filter), null, null, null, 0); return filterSettingsByPriority(allEntities); } + private static Filter getFilterNotNull(ObjectFilter filter) { + return filter == null ? Filters.empty() : (filter.getOQLFilter() == null ? Filters.empty() : OQLFilterBuilder.getFilter(filter.getOQLFilter())); + } + @Override - public ProjectSetting getSettingWithHighestPriority(String key) { - Stream filteredByKey = getCollectionDriver().find(Filters.equals(ProjectSetting.KEY_FIELD_NAME, key), null, null, null, 0); + public ProjectSetting getSettingWithHighestPriority(String key, ObjectFilter filter) { + Stream filteredByKey = getSettingsStreamByKey(key, filter); List filtered = filterSettingsByPriority(filteredByKey); if (filtered.size() == 0) { return null; @@ -57,6 +65,18 @@ public ProjectSetting getSettingWithHighestPriority(String key) { } } + private Stream getSettingsStreamByKey(String key, ObjectFilter filter) { + Filter additionalFilterNotNull = getFilterNotNull(filter); + Filter filterByKey = Filters.equals(ProjectSetting.KEY_FIELD_NAME, key); + Stream filteredByKey = getCollectionDriver().find(Filters.and(List.of(additionalFilterNotNull, filterByKey)), null, null, null, 0); + return filteredByKey; + } + + @Override + public List getSettingByKey(String key, ObjectFilter objectFilter){ + return getSettingsStreamByKey(key, objectFilter).collect(Collectors.toList()); + } + private static List filterSettingsByPriority(Stream allEntities) { // to support multitenancy here we want to filter out settings (defined for global project) overridden in local project List highestPriorityProjectSettings = new ArrayList<>(); diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index a0dcb56824..1ea7a292f7 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -22,6 +22,7 @@ import step.commons.activation.Activator; import step.core.accessors.Accessor; import step.core.encryption.EncryptionManager; +import step.core.objectenricher.ObjectFilter; import step.encryption.AbstractEncryptedValuesManager; import java.util.*; @@ -82,11 +83,11 @@ public String getEntityNameForLogging() { return "project setting"; } - public List getAllSettingsWithUniqueKeys() { - return accessor.getSettingsWithHighestPriority(); + public List getAllSettingsWithUniqueKeys(ObjectFilter additionalFilter) { + return accessor.getSettingsWithHighestPriority(additionalFilter); } - public ProjectSetting getUniqueSettingByKey(String key) { - return accessor.getSettingWithHighestPriority(key); + public ProjectSetting getUniqueSettingByKey(String key, ObjectFilter additionalFilter) { + return accessor.getSettingWithHighestPriority(key, additionalFilter); } } diff --git a/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java index fad1695faa..970bcc5747 100644 --- a/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java +++ b/step-core/src/test/java/step/projectsettings/ProjectSettingAccessorImplTest.java @@ -40,7 +40,7 @@ public void testGetSettingsWithHighestPriority(){ ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); prepareTestData(accessor); - List settingsWithHighestPriority = accessor.getSettingsWithHighestPriority(); + List settingsWithHighestPriority = accessor.getSettingsWithHighestPriority(null); log.info("Found settings: {}", settingsWithHighestPriority); Assert.assertEquals(3, settingsWithHighestPriority.size()); @@ -56,7 +56,7 @@ public void testGetSettingsWithHighestPriority(){ public void testGetSettingWithHighestPriority(){ ProjectSettingAccessorImpl accessor = new ProjectSettingAccessorImpl(new InMemoryCollection<>()); prepareTestData(accessor); - ProjectSetting s = accessor.getSettingWithHighestPriority("key1"); + ProjectSetting s = accessor.getSettingWithHighestPriority("key1", null); Assert.assertEquals("value with priority", s.getValue()); } From 323c22cc36cc23f0c143b8c3691f29d1c18bb9b7 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Sun, 15 Jun 2025 17:06:50 +0300 Subject: [PATCH 21/26] SED-3921: new step-framework.version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ac06290a2..2a39702eee 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 2025.3.14 2025.5.12-6821d8e4cbc831232c03e85a - 2025.5.21-682d628b2c2d5a21a7c04ffa + 2025.6.15-684ed0f35008c944809c80e1 3.0.23 From 9de09912b50bb2d76ea988c0017f2615af73c02b Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Mon, 23 Jun 2025 18:55:48 +0300 Subject: [PATCH 22/26] SED-3921: fix remarks --- pom.xml | 4 +-- .../packages/AutomationPackageServices.java | 6 ++-- .../packages/AutomationPackageManager.java | 9 +++--- .../ProjectSettingControllerPlugin.java | 29 ------------------- .../AbstractSettingAccessorWithHook.java | 2 -- .../AbstractEncryptedValuesManager.java | 4 +-- .../java/step/parameter/ParameterManager.java | 4 ++- .../ProjectSettingManager.java | 2 +- 8 files changed, 15 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index 9ef4298b56..b168b58b3c 100644 --- a/pom.xml +++ b/pom.xml @@ -47,9 +47,9 @@ 2025.3.14 2025.6.2-683db53f335ed956c4147901 - 2025.6.15-684ed0f35008c944809c80e1 + 2025.6.15-684ed0f35008c944809c80e1 - + 3.0.23 5.4.3 5.2.3 diff --git a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java index 4ee9ee8d30..fd0e6f86f5 100644 --- a/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java +++ b/step-automation-packages/step-automation-packages-controller/src/main/java/step/automation/packages/AutomationPackageServices.java @@ -146,7 +146,7 @@ public String createAutomationPackageFromMaven(@QueryParam("version") String apV try { MavenArtifactIdentifier mavenArtifactIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createAutomationPackageFromMaven( - mavenArtifactIdentifier, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate() + mavenArtifactIdentifier, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator() ).toString(); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -327,7 +327,7 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackageFromMaven(@Q try { MavenArtifactIdentifier mvnIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createOrUpdateAutomationPackageFromMaven( - mvnIdentifier, true, true, null, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), async == null ? false : async + mvnIdentifier, true, true, null, apVersion, activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async == null ? false : async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); @@ -359,7 +359,7 @@ public AutomationPackageUpdateResult updateAutomationPackageFromMaven(@PathParam MavenArtifactIdentifier mvnIdentifier = getMavenArtifactIdentifierFromXml(mavenArtifactXml); return automationPackageManager.createOrUpdateAutomationPackageFromMaven( mvnIdentifier, true, false, new ObjectId(id), apVersion, - activationExpression, getObjectEnricher(), getObjectPredicate(), async == null ? false : async + activationExpression, getObjectEnricher(), getObjectPredicate(), getObjectValidator(), async == null ? false : async ); } catch (AutomationPackageManagerException e) { throw new ControllerServiceException(e.getMessage()); diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java index 2e0a41951e..61bcd9aad1 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java @@ -292,11 +292,11 @@ protected void deleteAutomationPackageEntities(AutomationPackage automationPacka deleteAdditionalData(automationPackage, new AutomationPackageContext(operationMode, resourceManager, null, null,null, null, extensions)); } - public ObjectId createAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate) { + public ObjectId createAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, String apVersion, String activationExpr, ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator objectValidator) { validateMavenConfigAndArtifactClassifier(mavenArtifactIdentifier); try { try (AutomationPackageFromMavenProvider provider = new AutomationPackageFromMavenProvider(mavenConfigProvider.getConfig(objectPredicate), mavenArtifactIdentifier)) { - return createOrUpdateAutomationPackage(false, true, null, provider, apVersion, activationExpr, false, enricher, objectPredicate, false).getId(); + return createOrUpdateAutomationPackage(false, true, null, provider, apVersion, activationExpr, false, enricher, objectPredicate, objectValidator, false).getId(); } } catch (IOException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); @@ -372,11 +372,12 @@ public AutomationPackageUpdateResult createOrUpdateAutomationPackage(boolean all public AutomationPackageUpdateResult createOrUpdateAutomationPackageFromMaven(MavenArtifactIdentifier mavenArtifactIdentifier, boolean allowUpdate, boolean allowCreate, ObjectId explicitOldId, String apVersion, String activationExpr, - ObjectEnricher enricher, ObjectPredicate objectPredicate, boolean async) throws AutomationPackageManagerException { + ObjectEnricher enricher, ObjectPredicate objectPredicate, ObjectValidator objectValidator, + boolean async) throws AutomationPackageManagerException { try { validateMavenConfigAndArtifactClassifier(mavenArtifactIdentifier); try (AutomationPackageFromMavenProvider provider = new AutomationPackageFromMavenProvider(mavenConfigProvider.getConfig(objectPredicate), mavenArtifactIdentifier)) { - return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, async); + return createOrUpdateAutomationPackage(allowUpdate, allowCreate, explicitOldId, provider, apVersion, activationExpr, false, enricher, objectPredicate, objectValidator, async); } } catch (IOException ex) { throw new AutomationPackageManagerException("Automation package cannot be created. Caused by: " + ex.getMessage(), ex); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index d5b25740f8..de8765289a 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -67,13 +67,6 @@ public void serverStart(GlobalContext context) { ProjectSettingAccessor projectSettingAccessor = new ProjectSettingAccessorImpl(collection); context.put("ProjectSettingAccessor", projectSettingAccessor); - context.get(TableRegistry.class).register(ProjectSetting.ENTITY_NAME, new Table<>(collection, "param-read", true) - .withResultItemTransformer((p,s) -> ProjectSettingManager.maskProtectedValue(p)) - .withDerivedTableFiltersFactory(lf -> { - Set allFilterAttributes = lf.stream().map(Filters::collectFilterAttributes).flatMap(Set::stream).collect(Collectors.toSet()); - return allFilterAttributes.contains(PARAMETER_VALUE_FIELD + ".value") ? new Equals(PARAMETER_PROTECTED_VALUE_FIELD, false) : Filters.empty(); - })); - ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration()); context.put(ProjectSettingManager.class, projectSettingManager); this.projectSettingManager = projectSettingManager; @@ -105,28 +98,6 @@ protected void setResetValue(ProjectSetting obj) { } }); - context.require(ObjectHookRegistry.class).add(new ObjectHook() { - @Override - public ObjectFilter getObjectFilter(AbstractContext abstractContext) { - return null; - } - - @Override - public ObjectEnricher getObjectEnricher(AbstractContext abstractContext) { - return null; - } - - @Override - public void rebuildContext(AbstractContext abstractContext, EnricheableObject enricheableObject) throws Exception { - - } - - @Override - public boolean isObjectAcceptableInContext(AbstractContext abstractContext, EnricheableObject enricheableObject) { - return false; - } - }); - context.getServiceRegistrationCallback().registerService(ProjectSettingServices.class); } diff --git a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java index a95d773d43..6f30db5d24 100644 --- a/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java +++ b/step-core/src/main/java/step/core/settings/AbstractSettingAccessorWithHook.java @@ -26,8 +26,6 @@ import step.core.accessors.AbstractIdentifiableObject; import step.core.collections.Collection; import step.core.collections.Filters; -import step.core.objectenricher.EnricheableObject; -import step.unique.EntityWithUniqueAttributesAccessor; import java.util.ArrayList; import java.util.List; diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index d6c28793dc..6aa5f4d42f 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -36,14 +36,12 @@ public abstract class AbstractEncryptedValuesManager> { + protected final DynamicBeanResolver dynamicBeanResolver; private final Accessor parameterAccessor; public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, Configuration configuration, DynamicBeanResolver dynamicBeanResolver) { @@ -48,8 +49,9 @@ public ParameterManager(Accessor parameterAccessor, EncryptionManager } public ParameterManager(Accessor parameterAccessor, EncryptionManager encryptionManager, String defaultScriptEngine, DynamicBeanResolver dynamicBeanResolver) { - super(encryptionManager, defaultScriptEngine, dynamicBeanResolver); + super(encryptionManager, defaultScriptEngine); this.parameterAccessor = parameterAccessor; + this.dynamicBeanResolver = dynamicBeanResolver; } public static ParameterManager copy(ParameterManager from, Accessor parameterAccessor){ diff --git a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java index 1ea7a292f7..39d6d05312 100644 --- a/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java +++ b/step-core/src/main/java/step/projectsettings/ProjectSettingManager.java @@ -37,7 +37,7 @@ public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager public ProjectSettingManager(ProjectSettingAccessor accessor, EncryptionManager encryptionManager, String defaultScriptEngine) { // project settings can't be dynamic, so the dynamic bean resolver is null - super(encryptionManager, defaultScriptEngine, null); + super(encryptionManager, defaultScriptEngine); this.accessor = accessor; } From a81592e1a8c402b03c09dc29c3a64003c9061278 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 24 Jun 2025 05:06:48 +0300 Subject: [PATCH 23/26] SED-3921: step-framework.version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c21bb64f4d..f168eaeae1 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 2025.3.14 2025.6.20-68552bad6249f636f082d5c8 - 2025.6.15-684ed0f35008c944809c80e1 + 2025.6.24-685a07096249f636f0bc1276 3.0.23 From 32e45b50f3351cb933761e6c35a3a22a4366ccf1 Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 24 Jun 2025 21:20:51 +0300 Subject: [PATCH 24/26] SED-3921: fixes --- .../ProjectSettingControllerPlugin.java | 2 +- .../projectsettings/ProjectSettingServices.java | 2 +- .../plugins/unique/UniqueEntityControllerPlugin.java | 12 +++++++++--- .../encryption/AbstractEncryptedValuesManager.java | 1 - .../unique/EntityWithUniqueAttributesAccessor.java | 6 ++++-- .../main/java/step/unique/UniqueEntityManager.java | 8 ++++++-- .../java/step/unique/UniqueEntityManagerTest.java | 4 +++- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java index de8765289a..dab5357c6b 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingControllerPlugin.java @@ -65,7 +65,7 @@ public void serverStart(GlobalContext context) { Collection collection = context.getCollectionFactory().getCollection(ProjectSetting.ENTITY_NAME, ProjectSetting.class); ProjectSettingAccessor projectSettingAccessor = new ProjectSettingAccessorImpl(collection); - context.put("ProjectSettingAccessor", projectSettingAccessor); + context.put(ProjectSettingAccessor.class, projectSettingAccessor); ProjectSettingManager projectSettingManager = new ProjectSettingManager(projectSettingAccessor, encryptionManager, context.getConfiguration()); context.put(ProjectSettingManager.class, projectSettingManager); diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java index 25c2b88b49..9191663125 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/projectsettings/ProjectSettingServices.java @@ -34,7 +34,6 @@ import java.util.List; -// TODO: check if secured context works ok for AbstractStepServices @Path("/project-settings") @Tag(name = "ProjectSettings") @Tag(name = "Entity=ProjectSetting") @@ -57,6 +56,7 @@ public void init() throws Exception { @POST @Secured(right = "{entity}-write") public ProjectSetting save(ProjectSetting newSetting) { + assertEntityIsAcceptableInContext(newSetting); if (newSetting.getKey() == null || newSetting.getKey().isBlank()) { throw new ControllerServiceException("The parameter's key is mandatory."); } diff --git a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java index dfe9f2dde2..b554279235 100644 --- a/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java +++ b/step-controller/step-controller-base-plugins/src/main/java/step/plugins/unique/UniqueEntityControllerPlugin.java @@ -29,14 +29,20 @@ import step.core.plugins.Plugin; import step.unique.UniqueEntityManager; +import java.util.Map; + @Plugin(dependencies= {ObjectHookControllerPlugin.class}) public class UniqueEntityControllerPlugin extends AbstractControllerPlugin { public static Logger logger = LoggerFactory.getLogger(UniqueEntityControllerPlugin.class); + private CollectionFactory collectionFactory; + @Override public void serverStart(GlobalContext context) { + this.collectionFactory = context.getCollectionFactory(); + UniqueEntityManager uniqueEntityManager = new UniqueEntityManager(); context.put(UniqueEntityManager.class, uniqueEntityManager); @@ -58,12 +64,12 @@ public void rebuildContext(AbstractContext abstractContext, EnricheableObject en @Override public boolean isObjectAcceptableInContext(AbstractContext abstractContext, EnricheableObject enricheableObject) { - return false; + return true; } @Override - public ObjectValidator getObjectValidator(AbstractContext context) { - return uniqueEntityManager.createObjectValidator(context.require(CollectionFactory.class)); + public ObjectValidator getObjectValidator(AbstractContext context, Map config) { + return uniqueEntityManager.createObjectValidator(collectionFactory, config); } }); } diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index 6aa5f4d42f..0e515fa9bf 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -22,7 +22,6 @@ import org.slf4j.LoggerFactory; import step.core.EncryptedTrackedObject; import step.core.accessors.Accessor; -import step.core.dynamicbeans.DynamicBeanResolver; import step.core.encryption.EncryptionManager; import step.core.encryption.EncryptionManagerException; import step.core.objectenricher.ObjectValidator; diff --git a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java index 33e218cc78..d6473d42bc 100644 --- a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java +++ b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java @@ -35,13 +35,15 @@ public EntityWithUniqueAttributesAccessor(Collection) collectionDriver); } - public Optional findDuplicate(EntityWithUniqueAttributes e){ + public Optional findDuplicate(EntityWithUniqueAttributes e, Set ignoredAttributes){ EnricheableObject enricheableObject = (EnricheableObject) e; ArrayList attrFilter = new ArrayList<>(); if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { - attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + if(ignoredAttributes == null || !ignoredAttributes.contains(entry.getKey())) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } } } diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java index 8ccfe0ad11..5c2a42fb87 100644 --- a/step-core/src/main/java/step/unique/UniqueEntityManager.java +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -30,12 +30,16 @@ public class UniqueEntityManager { - public ObjectValidator createObjectValidator(CollectionFactory collectionFactory) { + public static final String NON_UNIQUE_ATTRIBUTES_CONFIG = "nonUniqueAttributes"; + + public ObjectValidator createObjectValidator(CollectionFactory collectionFactory, Map config) { return enricheableObject -> { if (enricheableObject instanceof EntityWithUniqueAttributes) { Collection collection = collectionFactory.getCollection(((EntityWithUniqueAttributes) enricheableObject).getEntityName(), enricheableObject.getClass()); EntityWithUniqueAttributesAccessor accessor = new EntityWithUniqueAttributesAccessor<>(collection); - Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject); + Object configValue = config.get(NON_UNIQUE_ATTRIBUTES_CONFIG); + Set nonUniqueAttributes = configValue == null ? null : new HashSet<>((java.util.Collection) configValue); + Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject, nonUniqueAttributes); if (duplicate.isPresent()) { throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", ((EntityWithUniqueAttributes) enricheableObject).getEntityName(), ((AbstractIdentifiableObject) enricheableObject).getId(), ((AbstractIdentifiableObject) duplicate.get()).getId() diff --git a/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java index d06964896e..334b3d4a40 100644 --- a/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java +++ b/step-core/src/test/java/step/unique/UniqueEntityManagerTest.java @@ -30,6 +30,8 @@ import step.core.objectenricher.ObjectValidator; import step.projectsettings.ProjectSetting; +import java.util.HashMap; + public class UniqueEntityManagerTest { private static final Logger log = LoggerFactory.getLogger(UniqueEntityManagerTest.class); @@ -49,7 +51,7 @@ public void testObjectValidator(){ ps1.addAttribute("project", "project2"); projectSettingCollection.save(ps2); - ObjectValidator validator = manager.createObjectValidator(inMemoryCollectionFactory); + ObjectValidator validator = manager.createObjectValidator(inMemoryCollectionFactory, new HashMap<>()); // new entity - ok ProjectSetting newPs = new ProjectSetting("setting3", "value3", "description3"); From 293e1789c87c415a65989197a67ab7cd062d559e Mon Sep 17 00:00:00 2001 From: Igor Egorov Date: Tue, 24 Jun 2025 22:06:51 +0300 Subject: [PATCH 25/26] SED-3921: configurable validator --- pom.xml | 2 +- .../AbstractEncryptedValuesManager.java | 6 +- .../EntityWithUniqueAttributesAccessor.java | 71 +++++++++++++++++-- .../java/step/unique/UniqueEntityManager.java | 4 +- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index f168eaeae1..ebbcac67b8 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 2025.3.14 2025.6.20-68552bad6249f636f082d5c8 - 2025.6.24-685a07096249f636f0bc1276 + 2025.6.24-685aec5f64523b01b039cecd 3.0.23 diff --git a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java index 0e515fa9bf..ffc9e5a7a3 100644 --- a/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java +++ b/step-core/src/main/java/step/encryption/AbstractEncryptedValuesManager.java @@ -74,7 +74,11 @@ public T save(T newObj, T sourceObj, String modificationUser, ObjectValidator ob protected void validateBeforeSave(T newObj, ObjectValidator objectValidator) { if (objectValidator != null) { - objectValidator.validateOnSave(newObj); + try { + objectValidator.validateOnSave(newObj); + } catch (Exception e) { + throw new EncryptedValueManagerException("Value cannot be saved: " + e.getMessage(), e); + } } } diff --git a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java index d6473d42bc..9687ebaa87 100644 --- a/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java +++ b/step-core/src/main/java/step/unique/EntityWithUniqueAttributesAccessor.java @@ -35,28 +35,85 @@ public EntityWithUniqueAttributesAccessor(Collection) collectionDriver); } - public Optional findDuplicate(EntityWithUniqueAttributes e, Set ignoredAttributes){ + public Optional findDuplicate(EntityWithUniqueAttributes e, List additionalRestrictions) { EnricheableObject enricheableObject = (EnricheableObject) e; ArrayList attrFilter = new ArrayList<>(); if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { - if(ignoredAttributes == null || !ignoredAttributes.contains(entry.getKey())) { - attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); - } + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); } } + Optional duplicate = findDuplicate(e, attrFilter); + if (duplicate.isPresent()) { + return duplicate; + } - Filter filterByAttribute = Filters.and(attrFilter); - if(e.getKeyFieldName() != null){ - attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); + // perform additional checks with additional conditions + if (additionalRestrictions != null) { + for (AdditionalUniquenessRestriction additionalRestriction : additionalRestrictions) { + attrFilter = new ArrayList<>(); + + if (enricheableObject.getAttributes() != null && !enricheableObject.getAttributes().isEmpty()) { + for (Map.Entry entry : enricheableObject.getAttributes().entrySet()) { + if (additionalRestriction.getIgnoredAttributes() == null || !additionalRestriction.getIgnoredAttributes().contains(entry.getKey())) { + attrFilter.add(Filters.equals("attributes." + entry.getKey(), entry.getValue())); + } + } + + if (additionalRestriction.getAttributeGroupField() != null) { + attrFilter.add(Filters.equals("attributes." + additionalRestriction.getAttributeGroupField(), additionalRestriction.getAttributeGroupValue())); + } + + duplicate = findDuplicate(e, attrFilter); + if (duplicate.isPresent()) { + return duplicate; + } + } + } } + return Optional.empty(); + } + + private Optional findDuplicate(EntityWithUniqueAttributes e, ArrayList attrFilter) { + Filter filterByAttribute = addFilterByKey(e, attrFilter); + // filter out the entity with the same ID and apply the filter by key again (if getKeyFieldName returns null and the key is resolved dynamically) return getCollectionDriver().find(filterByAttribute, null, null, null, 0) .filter(tmp -> !Objects.equals(((AbstractIdentifiableObject) e).getId(), tmp.getId()) && Objects.equals(e.getKey(), ((EntityWithUniqueAttributes) tmp).getKey())) .findFirst(); } + private static Filter addFilterByKey(EntityWithUniqueAttributes e, ArrayList attrFilter) { + Filter filterByAttribute = Filters.and(attrFilter); + if(e.getKeyFieldName() != null){ + attrFilter.add(Filters.equals(e.getKeyFieldName(), e.getKey())); + } + return filterByAttribute; + } + + public static class AdditionalUniquenessRestriction { + private Set ignoredAttributes; + private String attributeGroupField; + private String attributeGroupValue; + + public AdditionalUniquenessRestriction(Set ignoredAttributes, String attributeGroupField, String attributeGroupValue) { + this.ignoredAttributes = ignoredAttributes; + this.attributeGroupField = attributeGroupField; + this.attributeGroupValue = attributeGroupValue; + } + public Set getIgnoredAttributes() { + return ignoredAttributes; + } + + public String getAttributeGroupField() { + return attributeGroupField; + } + + public String getAttributeGroupValue() { + return attributeGroupValue; + } + } } diff --git a/step-core/src/main/java/step/unique/UniqueEntityManager.java b/step-core/src/main/java/step/unique/UniqueEntityManager.java index 5c2a42fb87..635d5ed529 100644 --- a/step-core/src/main/java/step/unique/UniqueEntityManager.java +++ b/step-core/src/main/java/step/unique/UniqueEntityManager.java @@ -38,8 +38,8 @@ public ObjectValidator createObjectValidator(CollectionFactory collectionFactory Collection collection = collectionFactory.getCollection(((EntityWithUniqueAttributes) enricheableObject).getEntityName(), enricheableObject.getClass()); EntityWithUniqueAttributesAccessor accessor = new EntityWithUniqueAttributesAccessor<>(collection); Object configValue = config.get(NON_UNIQUE_ATTRIBUTES_CONFIG); - Set nonUniqueAttributes = configValue == null ? null : new HashSet<>((java.util.Collection) configValue); - Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject, nonUniqueAttributes); + List additionalRestrictions = configValue == null ? null : new ArrayList<>((List) configValue); + Optional duplicate = (Optional) accessor.findDuplicate((EntityWithUniqueAttributes) enricheableObject, additionalRestrictions); if (duplicate.isPresent()) { throw new RuntimeException(String.format("%s (%s) cannot be saved. Another entity (%s) with the same attributes has been detected", ((EntityWithUniqueAttributes) enricheableObject).getEntityName(), ((AbstractIdentifiableObject) enricheableObject).getId(), ((AbstractIdentifiableObject) duplicate.get()).getId() From 9f13da311e2f4f6db098ad72b4c2479a37a7d70d Mon Sep 17 00:00:00 2001 From: David Stephan Date: Wed, 30 Jul 2025 08:59:01 +0200 Subject: [PATCH 26/26] SED-3921 resolving merge issues --- .../step/automation/packages/AutomationPackageManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java index 16c856b989..92f3dcaa07 100644 --- a/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java +++ b/step-automation-packages/step-automation-packages-manager/src/main/java/step/automation/packages/AutomationPackageManager.java @@ -609,8 +609,8 @@ protected AutomationPackageStaging createStaging(){ protected void fillStaging(AutomationPackageStaging staging, AutomationPackageContent packageContent, AutomationPackage oldPackage, ObjectEnricher enricherForIncludedEntities, ObjectValidator validatorForIncludedEntities, AutomationPackageArchive automationPackageArchive, String evaluationExpression, ObjectPredicate objectPredicate) { - staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, staging.getResourceManager(), evaluationExpression)); - staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); + staging.getPlans().addAll(preparePlansStaging(packageContent, automationPackageArchive, oldPackage, enricherForIncludedEntities, validatorForIncludedEntities, staging.getResourceManager(), evaluationExpression)); + staging.getFunctions().addAll(prepareFunctionsStaging(automationPackageArchive, packageContent, enricherForIncludedEntities, validatorForIncludedEntities, oldPackage, staging.getResourceManager(), evaluationExpression)); List hookEntries = new ArrayList<>(); for (String additionalField : packageContent.getAdditionalFields()) {