From e67e44e04f34239dfaa952defb0426248ace3331 Mon Sep 17 00:00:00 2001 From: Albert Tregnaghi Date: Mon, 3 Feb 2025 17:09:26 +0100 Subject: [PATCH 1/3] Enhance security by using sealed objects in domain communication #3842 --- .../sharedkernel/messaging/DomainMessagePart.java | 15 +++++++++------ .../IntegrationTestEventInspectorService.java | 13 ++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessagePart.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessagePart.java index ce5ad3ccb2..27c051d90c 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessagePart.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessagePart.java @@ -4,10 +4,14 @@ import java.util.HashMap; import java.util.Map; +import javax.crypto.SealedObject; + +import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; + public abstract class DomainMessagePart { private MessageID id; - protected Map parameters; + protected Map parameters; DomainMessagePart(MessageID id) { this.id = id; @@ -33,8 +37,8 @@ public MessageID getMessageId() { */ public T get(MessageDataKey key) { assertKeyNotNull(key); - String data = parameters.get(key.getId()); - return key.getProvider().get(data); + SealedObject sealedObject = parameters.get(key.getId()); + return key.getProvider().get(CryptoAccess.CRYPTO_STRING.unseal(sealedObject)); } @Override @@ -46,12 +50,11 @@ public void set(MessageDataKey key, T content) { if (key == null) { throw new IllegalArgumentException("key may not be null!"); } - String contentAsString = key.getProvider().getString(content); - parameters.put(key.getId(), contentAsString); + parameters.put(key.getId(), CryptoAccess.CRYPTO_STRING.seal(key.getProvider().getString(content))); } String getRaw(String key) { - return parameters.get(key); + return CryptoAccess.CRYPTO_STRING.unseal(parameters.get(key)); } private void assertKeyNotNull(MessageDataKey key) { diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/IntegrationTestEventInspectorService.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/IntegrationTestEventInspectorService.java index 91a742c994..d8947a1e76 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/IntegrationTestEventInspectorService.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/IntegrationTestEventInspectorService.java @@ -3,11 +3,14 @@ import java.util.Map; +import javax.crypto.SealedObject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; import com.mercedesbenz.sechub.sharedkernel.Profiles; /** @@ -101,7 +104,15 @@ private void appendAdditionalDebugData(DomainMessage request, IntegrationTestEve Map messageData = debug.getMessageData(); if (messageData != null && request.parameters != null) { - messageData.putAll(request.parameters); + /* + * for integration testing we break the sealed object data, because we need this + * for event trace tests + */ + for (Map.Entry entry : request.parameters.entrySet()) { + String key = entry.getKey(); + SealedObject val = entry.getValue(); + messageData.put(key, CryptoAccess.CRYPTO_STRING.unseal(val)); + } } } From 94440f79a068bf31029a10997e60656720d19534 Mon Sep 17 00:00:00 2001 From: Albert Tregnaghi Date: Wed, 12 Feb 2025 14:46:23 +0100 Subject: [PATCH 2/3] Introduce template usage validator #3610 --- .../commons/core/CachingPatternProvider.java | 112 +++++ .../sechub/commons/core/PatternCompiler.java | 20 + .../core/CachingPatternProviderTest.java | 147 +++++++ .../commons/core/PatternCompilerTest.java | 43 ++ .../SecHubConfigurationModelSupport.java | 6 +- .../SecHubConfigurationModelValidator.java | 2 +- .../commons/model/template/TemplateData.java | 2 +- .../model/template/TemplateDataResolver.java | 7 + .../template/TemplateUsageValidator.java | 159 +++++++ .../SecHubConfigurationModelSupportTest.java | 6 +- ...SecHubConfigurationModelValidatorTest.java | 2 +- .../template/TemplateUsageValidatorTest.java | 414 ++++++++++++++++++ .../internal/IntegrationTestTemplateFile.java | 33 +- .../scenario1/TemplateScenario1IntTest.java | 61 ++- ...sechub-integrationtest-webscanconfig1.json | 12 +- ...sechub-integrationtest-webscanconfig2.json | 10 +- .../domain/scan/ScanMessageHandler.java | 21 + ...anSecHubConfigurationRuntimeInspector.java | 140 ++++++ ...cHubConfigurationRuntimeInspectorTest.java | 190 ++++++++ .../schedule/ScheduleAssertService.java | 21 + ...eSecHubConfigurationRuntimeValidation.java | 78 ++++ .../schedule/SchedulerCreateJobService.java | 2 + .../domain/schedule/job/SecHubJobFactory.java | 2 +- ...HubConfigurationRuntimeValidationTest.java | 101 +++++ .../job/SecHubJobFactorySpringBootTest.java | 2 +- .../server/SecHubServerPojoFactory.java | 11 + .../messaging/MessageDataKeys.java | 2 + .../sharedkernel/messaging/MessageID.java | 13 + 28 files changed, 1584 insertions(+), 35 deletions(-) create mode 100644 sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/CachingPatternProvider.java create mode 100644 sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/PatternCompiler.java create mode 100644 sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/CachingPatternProviderTest.java create mode 100644 sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/PatternCompilerTest.java create mode 100644 sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidator.java create mode 100644 sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspector.java create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspectorTest.java create mode 100644 sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidation.java create mode 100644 sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidationTest.java diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/CachingPatternProvider.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/CachingPatternProvider.java new file mode 100644 index 0000000000..9103c0fdfd --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/CachingPatternProvider.java @@ -0,0 +1,112 @@ +package com.mercedesbenz.sechub.commons.core; + +import static java.util.Objects.*; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This provider gives a cached way to provide patterns. Because {@link Pattern} + * is very expensive to create. + * + */ +public class CachingPatternProvider { + + private static final Logger LOG = LoggerFactory.getLogger(CachingPatternProvider.class); + + private Map map = new TreeMap<>(); + private SortedSet sortedEntries = new TreeSet<>(); + + private int maximumSize; + private PatternCompiler compiler; + + private static final Pattern ACCEPT_EVERYTHING_PATTERN = Pattern.compile(".*"); + + public CachingPatternProvider(int maximumCacheSize, PatternCompiler compiler) { + if (maximumCacheSize < 2) { + throw new IllegalArgumentException("Maximum cache size must be greater than 1!"); + } + this.maximumSize = maximumCacheSize; + this.compiler = requireNonNull(compiler); + } + + /** + * Resolve compiled pattern for given pattern string. + * + * @param regularExpression regular expression as string + * @return compiled pattern or null if it was not possible to + * create a compiled pattern (because of syntax problems) + */ + public Pattern get(String regularExpression) { + requireNonNull(regularExpression, "regular expression text may not be null!"); + + PatternCacheEntry entry = map.get(regularExpression); + if (entry == null) { + entry = createNewEntryAndShrinkCacheIfNecesary(regularExpression); + + } + return entry.compiledPattern; + } + + private PatternCacheEntry createNewEntryAndShrinkCacheIfNecesary(String regularExpression) { + PatternCacheEntry entry = new PatternCacheEntry(); + entry.pattern = regularExpression; + try { + Pattern compiledPattern = compiler.compile(regularExpression); + entry.compiledPattern = compiledPattern; + + } catch (PatternSyntaxException e) { + /* + * this is a configuration problem at administrator side! We log the problem as + * an error and use an alternative which accepts everything. + */ + entry.compiledPattern = ACCEPT_EVERYTHING_PATTERN; + LOG.error("The pattern is not valid: '{}'. Use instead compiled pattern: '{}'", regularExpression, entry.compiledPattern, e); + } + + map.put(regularExpression, entry); + sortedEntries.add(entry); // we add it here for cleanup + + /* check max size */ + while (sortedEntries.size() > maximumSize) { + PatternCacheEntry removeMe = sortedEntries.iterator().next(); + + /* remove from both collections: */ + sortedEntries.remove(removeMe); + map.remove(removeMe.pattern); + } + return entry; + } + + private class PatternCacheEntry implements Comparable { + private Pattern compiledPattern; + private String pattern; + private LocalDateTime created; + + private PatternCacheEntry() { + this.created = LocalDateTime.now(); + } + + @Override + public int compareTo(PatternCacheEntry o) { + if (o == null) { + return 1; + } + return created.compareTo(o.created); + } + + @Override + public String toString() { + return "PatternCacheEntry [pattern=" + pattern + ",created=" + created + ", compiledPattern=" + compiledPattern + "]"; + } + + } +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/PatternCompiler.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/PatternCompiler.java new file mode 100644 index 0000000000..2a8e11caac --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/PatternCompiler.java @@ -0,0 +1,20 @@ +package com.mercedesbenz.sechub.commons.core; + +import static java.util.Objects.*; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class PatternCompiler { + + /** + * Compiles regular expression to a pattern + * + * @param regExp regular expression + * @return pattern, never {@link NullPointerException} + * @throws PatternSyntaxException + */ + public Pattern compile(String regExp) { + return Pattern.compile(requireNonNull(regExp, "Regular expression may not be null!")); + } +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/CachingPatternProviderTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/CachingPatternProviderTest.java new file mode 100644 index 0000000000..223a4c0268 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/CachingPatternProviderTest.java @@ -0,0 +1,147 @@ +package com.mercedesbenz.sechub.commons.core; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.junit.jupiter.api.Test; + +class CachingPatternProviderTest { + + @Test + void cache_maximum_2_wrong_regular_expression_results_in_alternative_accepting_everything() { + + /* prepare */ + PatternCompiler compiler = mock(); + when(compiler.compile(any())).thenThrow(new PatternSyntaxException("desc", "regex", 0)); + CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); + + /* execute */ + Pattern result = providerToTest.get("i-am-simulated-wrong"); + + /* test */ + assertThat(result.toString()).isEqualTo(".*"); + } + + @Test + void cache_maximum_2_correct_regular_expression_results_in_correct_compiled_pattern() { + + /* prepare */ + PatternCompiler compiler = mock(); + Pattern pattern = mock(); + when(compiler.compile(eq("i-am-correct"))).thenReturn(pattern); + CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); + + /* execute */ + Pattern result = providerToTest.get("i-am-correct"); + + /* test */ + verify(compiler).compile("i-am-correct"); + assertThat(result).isSameAs(pattern); + } + + @Test + void cache_maximum_2_correct_regular_expression_fetched_3_times_created_only_one_time() { + + /* prepare */ + PatternCompiler compiler = mock(); + Pattern pattern = mock(); + when(compiler.compile(eq("i-am-correct"))).thenReturn(pattern); + CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); + + /* execute */ + Pattern result = providerToTest.get("i-am-correct"); + result = providerToTest.get("i-am-correct"); + result = providerToTest.get("i-am-correct"); + + /* test */ + verify(compiler, times(1)).compile("i-am-correct"); + assertThat(result).isSameAs(pattern); + } + + @Test + void cache_maximum_2_handling_3_regular_expressions_does_remove_older_entries_automatically_when_cache_size_limit_reached() { + + /* prepare */ + PatternCompiler compiler = mock(); + Pattern pattern1 = mock(); + Pattern pattern2 = mock(); + Pattern pattern3 = mock(); + + when(compiler.compile(eq("p1"))).thenReturn(pattern1); + when(compiler.compile(eq("p2"))).thenReturn(pattern2); + when(compiler.compile(eq("p3"))).thenReturn(pattern3); + CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); + + /* execute 1 - empty cache */ + Pattern result1 = providerToTest.get("p1"); // p1 added (one left) + Pattern result2 = providerToTest.get("p2"); // p2 added (zero left) + + /* test 1 - empty cache, both patterns are compiled */ + var expectedCompileP1amount = 1; + var expectedCompileP2amount = 1; + + verify(compiler, times(expectedCompileP1amount)).compile("p1"); + verify(compiler, times(expectedCompileP2amount)).compile("p2"); + + /* execute 2 - use cache only */ + for (int i = 0; i < 10; i++) { + /* execute 0 */ + result1 = providerToTest.get("p1"); // must be from cache + result2 = providerToTest.get("p2"); // must be from cache + } + + /* test 2 - test fetching existing ones does not compile again */ + verify(compiler, times(expectedCompileP1amount)).compile("p1"); + verify(compiler, times(expectedCompileP2amount)).compile("p2"); + + /* execute 3 - recreate removed pattern */ + result1 = providerToTest.get("p1"); // p1 added (one left) + result2 = providerToTest.get("p2"); // p2 added (zero left) + Pattern result3 = providerToTest.get("p3"); // p3 added, must remove p1 fetch here because oldest + Pattern result1x = providerToTest.get("p1"); // fetch p1 again... will recreated + + /* test 3 - data is still correct and re-creation works */ + expectedCompileP1amount++;// p1 must be recreated (p1,p2,p3->p1 removed, p1 fetch--> recreate) + var expectedCompileP3amount = 1; + + verify(compiler, times(expectedCompileP2amount)).compile("p2"); + verify(compiler, times(expectedCompileP3amount)).compile("p3"); + verify(compiler, times(expectedCompileP1amount)).compile("p1"); + + assertThat(result1).isSameAs(pattern1); + assertThat(result1x).isSameAs(pattern1); + assertThat(result2).isSameAs(pattern2); + assertThat(result3).isSameAs(pattern3); + + /* execute 4 - use cached values */ + result1 = providerToTest.get("p1"); + result3 = providerToTest.get("p3"); + + /* test 4 */ + verify(compiler, times(expectedCompileP1amount)).compile("p1"); // p1 must not be created again + verify(compiler, times(expectedCompileP3amount)).compile("p3"); // p3 must not be created again + + assertThat(result1).isSameAs(pattern1); + assertThat(result3).isSameAs(pattern3); + + /* execute 5 - use next entry, not in cache, and max reached */ + result2 = providerToTest.get("p2"); + + /* test 5 */ + expectedCompileP2amount++; // p2 must be recreated + verify(compiler, times(expectedCompileP2amount)).compile("p2"); + assertThat(result2).isSameAs(pattern2); + + /* execute 6 - next entry not */ + result2 = providerToTest.get("p2"); + + /* test 6 */ + verify(compiler, times(expectedCompileP2amount)).compile("p2"); // same expectedCompileP2amount as before - means comes from cache + assertThat(result2).isSameAs(pattern2); + + } + +} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/PatternCompilerTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/PatternCompilerTest.java new file mode 100644 index 0000000000..f659c022d5 --- /dev/null +++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/PatternCompilerTest.java @@ -0,0 +1,43 @@ +package com.mercedesbenz.sechub.commons.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PatternCompilerTest { + + private PatternCompiler compilerToTest; + + @BeforeEach + void beforeEach() { + compilerToTest = new PatternCompiler(); + } + + @Test + void wrong_regular_expresion_throws_pattern_syntax_exception() { + assertThatThrownBy(() -> compilerToTest.compile("i-am-wrong(")).isInstanceOf(PatternSyntaxException.class); + } + + @Test + void null_regular_expresion_throws_npe() { + assertThatThrownBy(() -> compilerToTest.compile(null)).isInstanceOf(NullPointerException.class).hasMessageContaining("may not be null"); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", ".*" }) + void correct_regular_expresion_results_in_pattern(String regExp) { + /* execute */ + Pattern result = compilerToTest.compile(regExp); + + /* test */ + assertThat(result).isNotNull(); + assertThat(result.toString()).isEqualTo(regExp); + } + +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupport.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupport.java index 039e0e103e..0e5d2eaca2 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupport.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupport.java @@ -173,12 +173,12 @@ private boolean atLeastOneNameReferencesOneElementInGivenDataConfiguration(Setnull */ - public Set collectPublicScanTypes(SecHubConfigurationModel model) { + public Set collectScanTypes(SecHubConfigurationModel model) { Set result = new LinkedHashSet<>(); if (model.getCodeScan().isPresent()) { result.add(ScanType.CODE_SCAN); diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidator.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidator.java index 226defc605..b99744e05b 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidator.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidator.java @@ -171,7 +171,7 @@ private void handleMetaData(InternalValidationContext context) { } private void handleScanTypesAndModuleGroups(InternalValidationContext context) { - Set scanTypes = modelSupport.collectPublicScanTypes(context.model); + Set scanTypes = modelSupport.collectScanTypes(context.model); handleScanTypes(context, scanTypes); handleModuleGroup(context, scanTypes); diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java index a0f2216ef4..e2d9241233 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java @@ -13,7 +13,7 @@ */ public class TemplateData { - private Map variables = new LinkedHashMap<>(); + private Map variables = new LinkedHashMap<>(); // entries are ordered in model like ordered in json public Map getVariables() { return variables; diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java index 7da6386f33..040d46df9d 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java @@ -9,6 +9,13 @@ public class TemplateDataResolver { + /** + * Resolves template data for given type + * + * @param type template type + * @param configuration SecHub configuration + * @return data or null + */ public TemplateData resolveTemplateData(TemplateType type, SecHubConfigurationModel configuration) { if (type == null) { return null; diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidator.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidator.java new file mode 100644 index 0000000000..0b3073fd86 --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidator.java @@ -0,0 +1,159 @@ +package com.mercedesbenz.sechub.commons.model.template; + +import static java.util.Objects.*; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import com.mercedesbenz.sechub.commons.core.CachingPatternProvider; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariableValidation; + +public class TemplateUsageValidator { + + private CachingPatternProvider cachingPatternProvider; + + public TemplateUsageValidator(CachingPatternProvider cachingPatternProvider) { + this.cachingPatternProvider = requireNonNull(cachingPatternProvider, "caching pattern provider may not be null!"); + } + + /** + * Validates that given template data contains variables for given template + * definition in expected way. + * + * @param definition template definition + * @param data the template data from a SecHub configuration file + * @return result, never null + */ + public TemplateUsageValidatorResult validate(TemplateDefinition definition, TemplateData data) { + requireNonNull(definition, "Template definition may not be null!"); + requireNonNull(data, "Template data may not be null!"); + + try { + startAssertions(definition, data); + } catch (TemplateUsageAssertionException exception) { + return validationFailure(exception.getMessage()); + } + return validationOk(); + } + + private void startAssertions(TemplateDefinition definition, TemplateData data) throws TemplateUsageAssertionException { + List variables = definition.getVariables(); + Map variablesData = data.getVariables(); + + assertMandatoryVariablesAvailable(variables, variablesData); + + /* validation check */ + for (TemplateVariable variable : variables) { + assertVariableValidation(variable, data); + } + } + + private void assertVariableValidation(TemplateVariable variable, TemplateData data) throws TemplateUsageAssertionException { + TemplateVariableValidation validation = variable.getValidation(); + if (validation == null) { + return; + } + String variableName = variable.getName(); + String variableValue = data.getVariables().get(variableName); + if (variableValue == null) { + if (variable.isOptional()) { + return; + } + throw new TemplateUsageAssertionException("Variable " + variableName + " is mandatory but null"); + } + + assertMinLength(validation, variableName, variableValue); + assertMaxLength(validation, variableName, variableValue); + assertRegularExpression(validation, variableName, variableValue); + } + + private void assertMinLength(TemplateVariableValidation validation, String variableName, String variableValue) throws TemplateUsageAssertionException { + Integer minLength = validation.getMinLength(); + if (minLength == null) { + return; + } + if (variableValue.length() < minLength) { + throw new TemplateUsageAssertionException("Variable " + variableName + " length less than " + minLength); + } + } + + private void assertMaxLength(TemplateVariableValidation validation, String variableName, String variableValue) throws TemplateUsageAssertionException { + Integer maxLength = validation.getMaxLength(); + if (maxLength == null) { + return; + } + if (variableValue.length() > maxLength) { + throw new TemplateUsageAssertionException("Variable " + variableName + " length greater than " + maxLength); + } + } + + private void assertRegularExpression(TemplateVariableValidation validation, String variableName, String variableValue) + throws TemplateUsageAssertionException { + String regularExpression = validation.getRegularExpression(); + if (regularExpression == null || regularExpression.isBlank()) { + return; + } + + Pattern pattern = cachingPatternProvider.get(regularExpression); + if (pattern.matcher(variableValue).matches()) { + return; + } + throw new TemplateUsageAssertionException( + "Variable " + variableName + " must have a value which matches regular expression '" + regularExpression + "'"); + } + + private void assertMandatoryVariablesAvailable(List variablesDefined, Map variablesInData) + throws TemplateUsageAssertionException { + /* mandatory check */ + for (TemplateVariable variableDefined : variablesDefined) { + if (variableDefined.isOptional()) { + continue; + } + /* is mandatory */ + String nameOfMandatoryVariable = variableDefined.getName(); + if (variablesInData.containsKey(nameOfMandatoryVariable)) { + continue; + } + /* not found */ + throw new TemplateUsageAssertionException("The mandatory variable '" + nameOfMandatoryVariable + "' is not defined in template data!"); + } + } + + private TemplateUsageValidatorResult validationOk() { + TemplateUsageValidatorResult result = new TemplateUsageValidatorResult(); + result.valid = true; + return result; + } + + private TemplateUsageValidatorResult validationFailure(String message) { + TemplateUsageValidatorResult result = new TemplateUsageValidatorResult(); + result.valid = false; + result.message = message; + return result; + } + + private class TemplateUsageAssertionException extends Exception { + + private static final long serialVersionUID = 1L; + + private TemplateUsageAssertionException(String message) { + super(message); + } + + } + + public class TemplateUsageValidatorResult { + private boolean valid; + private String message; + + public boolean isValid() { + return valid; + } + + public String getMessage() { + return message; + } + } +} diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java index 93d9106e7a..e51eb2eaf2 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java @@ -82,7 +82,7 @@ void collectScanTypes_empty_model_returns_empty_set() { SecHubConfigurationModel model = new SecHubConfigurationModel(); /* execute */ - Set result = supportToTest.collectPublicScanTypes(model); + Set result = supportToTest.collectScanTypes(model); /* test */ assertEquals(0, result.size()); @@ -95,7 +95,7 @@ void collectScanTypes_sechub_license_and_code_scan_example3__has_scan_types_code SecHubConfigurationModel model = sechub_license_and_code_scan_example3; /* execute */ - Set result = supportToTest.collectPublicScanTypes(model); + Set result = supportToTest.collectScanTypes(model); /* test */ assertEquals(2, result.size()); @@ -121,7 +121,7 @@ void collectScanTypes_all_even_impossible_combinations_are_found() { when(model.getSecretScan()).thenReturn(Optional.of(secretScan)); /* execute */ - Set result = supportToTest.collectPublicScanTypes(model); + Set result = supportToTest.collectScanTypes(model); /* test */ diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java index ac3daaa08d..a20c49ce02 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java @@ -49,7 +49,7 @@ private void beforeEach() { validatorToTest = new SecHubConfigurationModelValidator(); validatorToTest.modelSupport = modelSupport; - when(modelSupport.collectPublicScanTypes(any(SecHubConfigurationModel.class))).thenReturn(modelSupportCollectedScanTypes); + when(modelSupport.collectScanTypes(any(SecHubConfigurationModel.class))).thenReturn(modelSupportCollectedScanTypes); } @Test diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java new file mode 100644 index 0000000000..ebb196adfd --- /dev/null +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java @@ -0,0 +1,414 @@ +package com.mercedesbenz.sechub.commons.model.template; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.commons.core.CachingPatternProvider; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariableValidation; +import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator.TemplateUsageValidatorResult; + +class TemplateUsageValidatorTest { + + private TemplateUsageValidator validatorToTest; + + private CachingPatternProvider cachingPatternProvider; + + @BeforeEach + void beforeEach() { + cachingPatternProvider = mock(); + validatorToTest = new TemplateUsageValidator(cachingPatternProvider); + } + + @Test + void null_template_data_throws_exception() { + /* prepare */ + TemplateDefinition definition = new TemplateDefinition(); + TemplateData data = null; + + /* execute + test */ + assertThatThrownBy(() -> validatorToTest.validate(definition, data)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("data may not be null"); + + } + + @Test + void null_template_definition_throws_exception() { + /* prepare */ + TemplateDefinition definition = null; + TemplateData data = new TemplateData(); + + /* execute + test */ + assertThatThrownBy(() -> validatorToTest.validate(definition, data)).isInstanceOf(NullPointerException.class) + .hasMessageContaining("definition may not be null"); + + } + + @Test + void template_definition_has_no_variables_but_data_contains_variables() { + /* prepare */ + TemplateDefinition definition = new TemplateDefinition(); + TemplateData data = new TemplateData(); + data.getVariables().put("i-am-here-but-not-necessary", "value1"); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); // unused variable is just ignored... + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "mandatory-var2", "mandatory-va", "mandatory_var" }) + void mandatory_variable_not_defined_in_template_data(String nameOfOtherVariable) { + /* prepare */ + String variableName = "mandatory-var"; + TemplateDefinition definition = createTestTemplateWithMandatoryVariable(variableName); + + TemplateData data = new TemplateData(); + if (nameOfOtherVariable != null) { + data.getVariables().put(nameOfOtherVariable, "some value"); + } + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isFalse(); + assertThat(result.getMessage()).contains(variableName, "not defined"); + } + + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "mandatory-var", "other" }) + void mandatory_variable_is_defined_in_template_data(String variableName) { + /* prepare */ + TemplateDefinition definition = createTestTemplateWithMandatoryVariable(variableName); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, "some value"); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "optional-var2", "optional-va", "optional_var" }) + void optional_variable_not_defined_in_template_data(String nameOfOtherVariable) { + /* prepare */ + String variableName = "optional-var"; + TemplateDefinition definition = createTestTemplateWithOptionalVariable(variableName); + + TemplateData data = new TemplateData(); + if (nameOfOtherVariable != null) { + data.getVariables().put(nameOfOtherVariable, "some value"); + } + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "optional-var", "other" }) + void optional_variable_is_defined_in_template_data(String variableName) { + /* prepare */ + TemplateDefinition definition = createTestTemplateWithOptionalVariable(variableName); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, "some value"); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @ArgumentsSource(MinLengthFailingProvider.class) + void validation_min_length_failure(boolean optional, String value, int minLength) { + /* prepare */ + String variableName = "variable-x"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setMinLength(minLength); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isFalse(); + assertThat(result.getMessage()).contains("Variable", "variable-x", "less than " + minLength); + } + + @ParameterizedTest + @ArgumentsSource(MinLengthValidProvider.class) + void validation_min_length_valid(boolean optional, String value, int minLength) { + /* prepare */ + String variableName = "variable-x"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setMinLength(minLength); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @ArgumentsSource(MaxLengthFailingProvider.class) + void validation_max_length_failure(boolean optional, String value, int maxLength) { + /* prepare */ + String variableName = "variable-x"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setMaxLength(maxLength); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isFalse(); + assertThat(result.getMessage()).contains("Variable", "variable-x", "greater than " + maxLength); + } + + @ParameterizedTest + @ArgumentsSource(MaxLengthValidProvider.class) + void validation_max_length_valid(boolean optional, String value, int maxLength) { + /* prepare */ + String variableName = "variable-x"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setMaxLength(maxLength); + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + @ParameterizedTest + @ArgumentsSource(RegExpressionFailingProvider.class) + void validation_regexp_failure(boolean optional, String value, String regularExpression) { + /* prepare */ + String variableName = "variable-regex-failure"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setRegularExpression(regularExpression); + + when(cachingPatternProvider.get(regularExpression)).thenReturn(Pattern.compile(regularExpression)); // we just do not cache inside the tests + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isFalse(); + assertThat(result.getMessage()).contains("Variable", "variable-regex-failure", "must have a value which matches regular expression", regularExpression); + } + + @ParameterizedTest + @ArgumentsSource(RegExpressionValidProvider.class) + void validation_regexp_valid(boolean optional, String value, String regularExpression) { + /* prepare */ + String variableName = "variable-regex-valid"; + TemplateDefinition definition = createTestTemplateWithVariable(variableName, optional); + TemplateVariableValidation validation = firstVariableValidation(definition); + validation.setRegularExpression(regularExpression); + + when(cachingPatternProvider.get(regularExpression)).thenReturn(Pattern.compile(regularExpression)); // we just do not cache inside the tests + + TemplateData data = new TemplateData(); + data.getVariables().put(variableName, value); + + /* execute */ + TemplateUsageValidatorResult result = validatorToTest.validate(definition, data); + + /* test */ + assertThat(result.isValid()).isTrue(); + assertThat(result.getMessage()).isNull(); + } + + /* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + /* + ................Helper.......................... + */ + /* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + + private TemplateVariableValidation firstVariableValidation(TemplateDefinition definition) { + TemplateVariable firstVariable = definition.getVariables().iterator().next(); + if (firstVariable == null) { + throw new IllegalStateException("test case corrupt!"); + } + TemplateVariableValidation validation = firstVariable.getValidation(); + if (validation == null) { + validation = new TemplateVariableValidation(); + firstVariable.setValidation(validation); + } + return validation; + } + + private TemplateDefinition createTestTemplateWithOptionalVariable(String name) { + return createTestTemplateWithVariable(name, true); + } + + private TemplateDefinition createTestTemplateWithMandatoryVariable(String name) { + return createTestTemplateWithVariable(name, false); + } + + private TemplateDefinition createTestTemplateWithVariable(String name, boolean optional) { + TemplateDefinition definition = new TemplateDefinition(); + TemplateVariable variable = new TemplateVariable(); + variable.setName(name); + variable.setOptional(optional); + definition.getVariables().add(variable); + return definition; + } + + private static class MinLengthFailingProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, "1234", 5), + Arguments.of(false, "1234", 10), + Arguments.of(false, "ab", 3), + + Arguments.of(true, "1234", 5), + Arguments.of(true, "1234", 10), + Arguments.of(true, "ab", 3)); + } + /* @formatter:on*/ + } + + private static class MinLengthValidProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, "1234", 4), + Arguments.of(false, "1234", 2), + Arguments.of(false, "ab", 2), + + Arguments.of(true, "1234", 4), + Arguments.of(true, "1234", 2), + Arguments.of(true, "ab", 2)); + } + /* @formatter:on*/ + } + + private static class MaxLengthFailingProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, "1234", 3), + Arguments.of(false, "123456789", 8), + Arguments.of(false, "ab", 1), + + Arguments.of(true, "1234", 3), + Arguments.of(true, "123456789", 8), + Arguments.of(true, "ab", 1)); + } + /* @formatter:on*/ + } + + private static class MaxLengthValidProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, "1234", 4), + Arguments.of(false, "123456789", 9), + Arguments.of(false, "123456789", 10), + Arguments.of(false, "ab", 2), + + Arguments.of(true, "1234", 4), + Arguments.of(true, "123456789", 9), + Arguments.of(true, "123456789", 10), + Arguments.of(true, "ab", 2)); + } + /* @formatter:on*/ + } + + private static class RegExpressionFailingProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, "", ".+"), + Arguments.of(false, "12x4", "1234"), + Arguments.of(false, "text0", "[a-z]*"), + + Arguments.of(true, "", ".+"), + Arguments.of(true, "12x4", "1234"), + Arguments.of(true, "text0", "[a-z]*")); + } + /* @formatter:on*/ + } + + private static class RegExpressionValidProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + return Stream.of( + Arguments.of(false, " ", ".+"), + Arguments.of(false, "1234", "1234"), + Arguments.of(false, "text", "[a-z]*"), + + Arguments.of(true, " ", ".+"), + Arguments.of(true, "1234", "1234"), + Arguments.of(true, "text", "[a-z]*")); + } + /* @formatter:on*/ + } + +} diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/IntegrationTestTemplateFile.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/IntegrationTestTemplateFile.java index 14c719aebf..6513f59743 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/IntegrationTestTemplateFile.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/IntegrationTestTemplateFile.java @@ -8,14 +8,41 @@ public enum IntegrationTestTemplateFile { UPDATE_WHITELIST("sechub-integrationtest-updatewhitelist1.json"), /** - * Simple webscan setup + * Simple webscan setup with (empty) login setup + * + *

Login setup

+ * + *
+     * 
+     * {
+     *   "login" : {
+     *   }
+     * }
+     * 
+     * 
*/ WEBSCAN_1("sechub-integrationtest-webscanconfig1.json"), /** - * Same as {@link #WEBSCAN_1} but with meta data inside : + * Similar to as {@link #WEBSCAN_1} but with login setup + meta data inside: + *
+ *

Login setup

* - *

Labels

+ *
+     * 
+     * "login" : {
+     *      "templateData" : {
+     *          "variables" : {
+     *              "mandatory-variable-1" : "value-1"
+     *          }
+     *      }
+     *  }
+     * 
+     * 
+ * + *

Meta data:

+ * + *

Labels

* *
      * stage:testing
diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java
index ea11c92c3e..4da2eb29d4 100644
--- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java
+++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java
@@ -10,6 +10,7 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.web.client.HttpClientErrorException.UnprocessableEntity;
 
 import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition;
 import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable;
@@ -23,6 +24,7 @@
 import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestExtension;
 import com.mercedesbenz.sechub.integrationtest.api.TestAPI;
 import com.mercedesbenz.sechub.integrationtest.api.WithTestScenario;
+import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestTemplateFile;
 
 @ExtendWith(IntegrationTestExtension.class)
 @WithTestScenario(Scenario1.class)
@@ -44,31 +46,46 @@ public class TemplateScenario1IntTest {
     @BeforeEach
     void beforeEach() {
 
-        templateId = "template-1_" + System.nanoTime();
+        templateId = "test-template-1";
 
         /* @formatter:off */
-        TemplateDefinition fullTemplateDefinition = TemplateDefinition.builder().
-                templateId(templateId).
-                templateType(TemplateType.WEBSCAN_LOGIN).
-                assetId("asset1").
-                build();
-        /* @formatter:on */
-        TemplateVariable usernameVariable = new TemplateVariable();
-        usernameVariable.setName("username");
+        TemplateDefinition fullTemplateDefinition = TemplateDefinition.from("""
+                {
+                  "id" : "%s",
+                  "type" : "WEBSCAN_LOGIN",
+                  "variables" : [
 
-        TemplateVariable passwordVariable = new TemplateVariable();
-        passwordVariable.setName("password");
+                      {
+                          "name" : "mandatory-variable-1",
+                          "optional" : false
+                      }
+
+                  ],
+                  "assetId" : "asset1"
+                }
+                """.formatted(templateId));
 
-        fullTemplateDefinition.getVariables().add(usernameVariable);
-        fullTemplateDefinition.getVariables().add(passwordVariable);
+        System.out.println(fullTemplateDefinition.toFormattedJSON());
+
+        /* @formatter:on */
 
         String fullTemplateDefinitionJson = fullTemplateDefinition.toFormattedJSON();
         createDefinition = TemplateDefinition.from(fullTemplateDefinitionJson.replace(templateId, "does-not-matter-will-be-overriden"));
 
         definitionWithId = TemplateDefinition.from(fullTemplateDefinitionJson);
 
+        // update will try to change template id and add some new variables
         updateDefinition = TemplateDefinition.from(fullTemplateDefinitionJson.replace(templateId, "will-not-be-changed-by-update"));
 
+        TemplateVariable usernameVariable = new TemplateVariable();
+        usernameVariable.setName("username");
+
+        TemplateVariable passwordVariable = new TemplateVariable();
+        passwordVariable.setName("password");
+
+        updateDefinition.getVariables().add(usernameVariable); // important: user name is added before, keep this, otherwise test fails!
+        updateDefinition.getVariables().add(passwordVariable);
+
         /*
          * we need to clear old template data , to be able to restart the test for
          * development
@@ -80,8 +97,8 @@ void beforeEach() {
     @Test
     void template_crud_and_healthcheck_test() {
         /* prepare */
-        as(SUPER_ADMIN).createProject(Scenario1.PROJECT_1, SUPER_ADMIN); // not done in this scenario automatically
-
+        as(SUPER_ADMIN).createProject(Scenario1.PROJECT_1, SUPER_ADMIN).assignUserToProject(SUPER_ADMIN, Scenario1.PROJECT_1); // not done in this scenario
+                                                                                                                               // automatically
         /* check preconditions */
         assertTemplateNotInsideTemplateList();
 
@@ -91,12 +108,22 @@ void template_crud_and_healthcheck_test() {
 
         assertTemplateCanBeCreated();
 
-        assertTemplateCanBeUpdated();
-
         assertTemplateHealthCheckSaysOkButInfoThatTemplateIsNotAssigned();
 
         assertTemplateCanBeAssignedToProject();
 
+        // will not work, because test template file has not mandatory variable defined
+        assertThatThrownBy(() -> as(SUPER_ADMIN).createWebScan(Scenario1.PROJECT_1, IntegrationTestTemplateFile.WEBSCAN_1))
+                .isInstanceOf(UnprocessableEntity.class).hasMessageContaining("mandatory-variable-1");
+        ;
+
+        as(SUPER_ADMIN).createWebScan(Scenario1.PROJECT_1, IntegrationTestTemplateFile.WEBSCAN_2); // will work, because here variable is defined
+
+        assertTemplateCanBeUpdated();
+
+        assertThatThrownBy(() -> as(SUPER_ADMIN).createWebScan(Scenario1.PROJECT_1, IntegrationTestTemplateFile.WEBSCAN_2))
+                .isInstanceOf(UnprocessableEntity.class).hasMessageContaining("username");
+
         assertTemplateCanBeUnassignedFromProject();
 
         assertTemplateCanBeAssignedToProject();
diff --git a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig1.json b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig1.json
index 8452a2dcfc..66a46e5afb 100644
--- a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig1.json
+++ b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig1.json
@@ -5,6 +5,12 @@
     "project"   : "__projectname__",
 
     "webScan"   : {
-        "url": "__acceptedUri1__"
-    }
-}
\ No newline at end of file
+        "url": "__acceptedUri1__",
+		
+		"login" : {
+			"templateData" : {
+				
+			}
+		}
+	}
+}
diff --git a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig2.json b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig2.json
index 59db50d00e..1944e4debf 100644
--- a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig2.json
+++ b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig2.json
@@ -5,7 +5,15 @@
     "project"   : "__projectname__",
 
     "webScan"   : {
-        "url": "__acceptedUri1__"
+        "url": "__acceptedUri1__",
+		
+		"login" : {
+			"templateData" : {
+			    "variables" : {
+					"mandatory-variable-1" : "value-1"
+			    }
+			}
+		}
     },
     
     "metaData": {
diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java
index 0f7143a77c..eb91674a23 100644
--- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java
+++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java
@@ -9,6 +9,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import com.mercedesbenz.sechub.commons.model.SecHubMessagesList;
 import com.mercedesbenz.sechub.domain.scan.access.ScanDeleteAnyAccessToProjectAtAllService;
 import com.mercedesbenz.sechub.domain.scan.access.ScanGrantUserAccessToProjectService;
 import com.mercedesbenz.sechub.domain.scan.access.ScanRevokeUserAccessAtAllService;
@@ -19,6 +20,7 @@
 import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigAccessLevelService;
 import com.mercedesbenz.sechub.domain.scan.template.TemplateService;
 import com.mercedesbenz.sechub.sharedkernel.Step;
+import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration;
 import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier;
 import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier.MappingType;
 import com.mercedesbenz.sechub.sharedkernel.messaging.AdministrationConfigMessage;
@@ -75,6 +77,9 @@ public class ScanMessageHandler implements AsynchronMessageHandler, SynchronMess
     @Autowired
     TemplateService templateService;
 
+    @Autowired
+    ScanSecHubConfigurationRuntimeInspector configurationRuntimeInspector;
+
     @Override
     public void receiveAsyncMessage(DomainMessage request) {
         MessageID messageId = request.getMessageId();
@@ -125,6 +130,8 @@ public DomainMessageSynchronousResult receiveSynchronMessage(DomainMessage reque
             return handleAssignTemplateToProjectRequest(request);
         case REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT:
             return handleUnassignTemplateFromProjectRequest(request);
+        case REQUEST_FULL_CONFIGURATION_VALIDATION:
+            return handleFullConfigurationValidation(request);
         default:
             throw new IllegalStateException("unhandled message id:" + messageId);
         }
@@ -175,6 +182,20 @@ private DomainMessageSynchronousResult handleJobRestartHardRequested(DomainMessa
         }
     }
 
+    @IsRecevingSyncMessage(MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION)
+    @IsSendingSyncMessageAnswer(value = MessageID.RESULT_FULL_CONFIGURATION_VALIDATION, answeringTo = MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION, branchName = "success")
+    private DomainMessageSynchronousResult handleFullConfigurationValidation(DomainMessage request) {
+        SecHubConfiguration config = request.get(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG);
+
+        /* start inspection */
+        SecHubMessagesList inspectionList = configurationRuntimeInspector.inspect(config);
+
+        /* return inspection messages inside result */
+        DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_FULL_CONFIGURATION_VALIDATION);
+        result.set(MessageDataKeys.ERROR_MESSAGES, inspectionList);
+        return result;
+    }
+
     @IsSendingSyncMessageAnswer(value = MessageID.JOB_RESULT_PURGE_FAILED, answeringTo = MessageID.REQUEST_PURGE_JOB_RESULTS, branchName = "failed")
     private DomainMessageSynchronousResult purgeFailed(UUID jobUUID, Exception e) {
         DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.JOB_RESULT_PURGE_FAILED, e);
diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspector.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspector.java
new file mode 100644
index 0000000000..7d227ea9e9
--- /dev/null
+++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspector.java
@@ -0,0 +1,140 @@
+package com.mercedesbenz.sechub.domain.scan;
+
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.mercedesbenz.sechub.commons.model.ScanType;
+import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport;
+import com.mercedesbenz.sechub.commons.model.SecHubMessage;
+import com.mercedesbenz.sechub.commons.model.SecHubMessageType;
+import com.mercedesbenz.sechub.commons.model.SecHubMessagesList;
+import com.mercedesbenz.sechub.commons.model.template.TemplateData;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable;
+import com.mercedesbenz.sechub.commons.model.template.TemplateType;
+import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator;
+import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator.TemplateUsageValidatorResult;
+import com.mercedesbenz.sechub.domain.scan.template.RelevantScanTemplateDefinitionFilter;
+import com.mercedesbenz.sechub.domain.scan.template.TemplateService;
+import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration;
+
+@Component
+public class ScanSecHubConfigurationRuntimeInspector {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ScanSecHubConfigurationRuntimeInspector.class);
+
+    private TemplateService templateService;
+    private TemplateDataResolver templateDataResolver;
+    private RelevantScanTemplateDefinitionFilter templateDefinitionFilter;
+    private SecHubConfigurationModelSupport configurationModelSupport;
+    private TemplateUsageValidator templateUsageValidator;
+
+    ScanSecHubConfigurationRuntimeInspector(TemplateService templateService, RelevantScanTemplateDefinitionFilter templateDefinitionFilter,
+            TemplateDataResolver templateDataResolver, SecHubConfigurationModelSupport configurationModelSupport,
+            TemplateUsageValidator templateUsageValidator) {
+
+        this.templateService = templateService;
+        this.templateDefinitionFilter = templateDefinitionFilter;
+        this.templateDataResolver = templateDataResolver;
+        this.configurationModelSupport = configurationModelSupport;
+        this.templateUsageValidator = templateUsageValidator;
+    }
+
+    /**
+     * Inspects given configuration for runtime failures/problems.
+     *
+     * @param config
+     * @return list, never null
+     */
+    public SecHubMessagesList inspect(SecHubConfiguration config) {
+        LOG.debug("Start config inspection");
+        SecHubMessagesList messagesList = new SecHubMessagesList();
+
+        appendTemplateRelatedProblems(config, messagesList);
+
+        return messagesList;
+    }
+
+    private void appendTemplateRelatedProblems(SecHubConfiguration config, SecHubMessagesList messagesList) {
+        String projectId = config.getProjectId();
+        List templateDefinitions = templateService.fetchAllTemplateDefinitionsForProject(projectId);
+
+        if (templateDefinitions.isEmpty()) {
+            /*
+             * no templates defined for this project at all - we will do no further
+             * processing here! If the user has defined template data or not, we stop here!
+             */
+            LOG.debug("No template definitions found for project: {} - skip processing", projectId);
+            return;
+        }
+
+        /* check which scan types are wanted by configuration */
+        Set scanTypes = configurationModelSupport.collectScanTypes(config);
+
+        for (ScanType scanType : scanTypes) {
+            List relevantDefinitions = templateDefinitionFilter.filter(templateDefinitions, scanType, config);
+
+            for (TemplateDefinition relevant : relevantDefinitions) {
+                inspectTemplateUsage(config, messagesList, relevant);
+
+            }
+        }
+
+    }
+
+    private void inspectTemplateUsage(SecHubConfiguration config, SecHubMessagesList messagesList, TemplateDefinition relevant) {
+
+        LOG.debug("Inspect usage of templte: {} project: {}", relevant.getId(), config.getProjectId());
+
+        TemplateType type = relevant.getType();
+        TemplateData dataForType = templateDataResolver.resolveTemplateData(type, config);
+
+        if (dataForType == null) {
+            handleNoTemplateDataFound(type, relevant, messagesList);
+        } else {
+            handleTemplateDataUsage(messagesList, relevant, dataForType);
+        }
+    }
+
+    private void handleTemplateDataUsage(SecHubMessagesList messagesList, TemplateDefinition relevant, TemplateData dataForType) {
+        TemplateUsageValidatorResult result = templateUsageValidator.validate(relevant, dataForType);
+        if (!result.isValid()) {
+            messagesList.getSecHubMessages().add(new SecHubMessage(SecHubMessageType.ERROR, result.getMessage()));
+        }
+    }
+
+    private void handleNoTemplateDataFound(TemplateType type, TemplateDefinition relevant, SecHubMessagesList messagesList) {
+        /* create info message with hint for mandatory variables */
+        boolean mandatoryVariableDetected = false;
+        StringBuilder sb = new StringBuilder();
+        for (TemplateVariable variable : relevant.getVariables()) {
+            if (variable.isOptional()) {
+                continue;
+            }
+            if (!mandatoryVariableDetected) {
+                sb.append("Please provide following mandatory variables:");
+                mandatoryVariableDetected = true;
+            }
+            sb.append("\n- ");
+            sb.append(variable.getName());
+        }
+        if (!mandatoryVariableDetected) {
+            /* no variables necessary - so it is okay that user has not defined anything */
+            return;
+        }
+        String message = """
+                Template data missing. This is necessary to provide %s for the project.
+                Please provide this kind of data inside your configuration file!
+                %s
+                """.formatted(type.getId(), sb);
+
+        messagesList.getSecHubMessages().add(new SecHubMessage(SecHubMessageType.ERROR, message));
+
+    }
+
+}
diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspectorTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspectorTest.java
new file mode 100644
index 0000000000..c9fcbcf3be
--- /dev/null
+++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanSecHubConfigurationRuntimeInspectorTest.java
@@ -0,0 +1,190 @@
+package com.mercedesbenz.sechub.domain.scan;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import com.mercedesbenz.sechub.commons.model.ScanType;
+import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport;
+import com.mercedesbenz.sechub.commons.model.SecHubMessage;
+import com.mercedesbenz.sechub.commons.model.SecHubMessageType;
+import com.mercedesbenz.sechub.commons.model.SecHubMessagesList;
+import com.mercedesbenz.sechub.commons.model.template.TemplateData;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition;
+import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable;
+import com.mercedesbenz.sechub.commons.model.template.TemplateType;
+import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator;
+import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator.TemplateUsageValidatorResult;
+import com.mercedesbenz.sechub.domain.scan.template.RelevantScanTemplateDefinitionFilter;
+import com.mercedesbenz.sechub.domain.scan.template.TemplateService;
+import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration;
+
+class ScanSecHubConfigurationRuntimeInspectorTest {
+
+    private static final String TEST_PROJECT_ID = "p1";
+    private TemplateService templateService;
+    private TemplateDataResolver templateDataResolver;
+    private RelevantScanTemplateDefinitionFilter templateDefinitionFilter;
+    private SecHubConfigurationModelSupport configurationModelSupport;
+    private TemplateUsageValidator templateUsageValidator;
+
+    private TemplateData templateData;
+    private SecHubConfiguration config;
+    private TemplateDefinition templateDefinition;
+    private List listOfTemplateDefinitionsforProject;
+
+    private ScanSecHubConfigurationRuntimeInspector inspectorToTest;
+
+    @BeforeEach
+    void beforeEach() {
+
+        /* services */
+        templateService = mock();
+        templateDataResolver = mock();
+        templateDefinitionFilter = mock();
+        configurationModelSupport = mock();
+        templateUsageValidator = mock();
+
+        /* test data */
+        templateData = mock();
+        config = mock();
+        templateDefinition = mock();
+
+        listOfTemplateDefinitionsforProject = new ArrayList<>();
+
+        /* defaults in test data */
+        when(config.getProjectId()).thenReturn(TEST_PROJECT_ID);
+        when(templateService.fetchAllTemplateDefinitionsForProject(TEST_PROJECT_ID)).thenReturn(listOfTemplateDefinitionsforProject);
+        when(templateDefinition.getType()).thenReturn(TemplateType.WEBSCAN_LOGIN);
+
+        /* initialize object to test */
+        inspectorToTest = new ScanSecHubConfigurationRuntimeInspector(templateService, templateDefinitionFilter, templateDataResolver,
+                configurationModelSupport, templateUsageValidator);
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = { true, false })
+    void template_defined_for_project_but_not_in_config_but_variables_without_mandatory(boolean atLeastOneOptional) {
+        /* prepare */
+        listOfTemplateDefinitionsforProject.add(templateDefinition);
+
+        when(configurationModelSupport.collectScanTypes(config)).thenReturn(Set.of(ScanType.WEB_SCAN));
+        when(templateDefinitionFilter.filter(listOfTemplateDefinitionsforProject, ScanType.WEB_SCAN, config)).thenReturn(listOfTemplateDefinitionsforProject);
+
+        when(templateDataResolver.resolveTemplateData(any(), any())).thenReturn(null);
+
+        if (atLeastOneOptional) {
+            TemplateVariable variable = new TemplateVariable();
+            variable.setName("optional-variable");
+            variable.setOptional(true);
+
+            when(templateDefinition.getVariables()).thenReturn(List.of(variable));
+        }
+
+        /* execute */
+        SecHubMessagesList result = inspectorToTest.inspect(config);
+
+        /* test */
+        assertThat(result.getSecHubMessages()).hasSize(0);
+        verify(templateUsageValidator, never()).validate(any(), any());
+
+    }
+
+    @Test
+    void template_defined_for_project_but_not_in_config_and_variables_are_mandatory() {
+        /* prepare */
+        listOfTemplateDefinitionsforProject.add(templateDefinition);
+
+        when(configurationModelSupport.collectScanTypes(config)).thenReturn(Set.of(ScanType.WEB_SCAN));
+        when(templateDefinitionFilter.filter(listOfTemplateDefinitionsforProject, ScanType.WEB_SCAN, config)).thenReturn(listOfTemplateDefinitionsforProject);
+
+        when(templateDataResolver.resolveTemplateData(any(), any())).thenReturn(null);
+
+        TemplateVariable var1 = new TemplateVariable();
+        var1.setName("variable1");
+        var1.setOptional(false);
+
+        when(templateDefinition.getVariables()).thenReturn(List.of(var1));
+
+        /* execute */
+        SecHubMessagesList result = inspectorToTest.inspect(config);
+
+        /* test */
+        assertThat(result.getSecHubMessages()).hasSize(1);
+        SecHubMessage message1 = result.getSecHubMessages().iterator().next();
+        assertThat(message1.getText()).contains("Template data missing. This is necessary to provide webscan-login for the project.").contains("- variable1");
+
+        verify(templateUsageValidator, never()).validate(any(), any());
+
+    }
+
+    @Test
+    void when_template_NOT_defined_for_project_and_no_data_available_result_will_not_contain_error_messages() {
+        /* prepare */
+        listOfTemplateDefinitionsforProject = Collections.emptyList();
+
+        /* execute */
+        SecHubMessagesList result = inspectorToTest.inspect(config);
+
+        /* test */
+        assertThat(result.getSecHubMessages()).isEmpty();
+        verify(templateUsageValidator, never()).validate(any(), any());
+
+    }
+
+    @Test
+    void when_validator_does_not_fail_result_contains_no_sechubmessages() {
+        /* prepare */
+        prepareValidationResultWillBe(true, "I found no failure");
+
+        /* execute */
+        SecHubMessagesList result = inspectorToTest.inspect(config);
+
+        /* test */
+        assertThat(result.getSecHubMessages()).isEmpty();
+
+    }
+
+    @Test
+    void when_validator_does_fail_result_contains_sechubmessage_error_with_validator_message() {
+        /* prepare */
+        prepareValidationResultWillBe(false, "I found a failure");
+
+        /* execute */
+        SecHubMessagesList result = inspectorToTest.inspect(config);
+
+        /* test */
+        assertThat(result.getSecHubMessages()).isNotEmpty().hasSize(1);
+        SecHubMessage message = result.getSecHubMessages().iterator().next();
+
+        assertThat(message.getType()).isEqualTo(SecHubMessageType.ERROR);
+        assertThat(message.getText()).isEqualTo("I found a failure");
+
+    }
+
+    private void prepareValidationResultWillBe(boolean validResult, String text) {
+        listOfTemplateDefinitionsforProject.add(templateDefinition);
+
+        when(configurationModelSupport.collectScanTypes(config)).thenReturn(Set.of(ScanType.WEB_SCAN));
+        when(templateDefinitionFilter.filter(listOfTemplateDefinitionsforProject, ScanType.WEB_SCAN, config)).thenReturn(listOfTemplateDefinitionsforProject);
+        when(templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, config)).thenReturn(templateData);
+
+        TemplateUsageValidatorResult validatorResult = mock();
+        when(validatorResult.isValid()).thenReturn(validResult);
+        when(validatorResult.getMessage()).thenReturn(text);
+
+        when(templateUsageValidator.validate(templateDefinition, templateData)).thenReturn(validatorResult);
+    }
+
+}
diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleAssertService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleAssertService.java
index e3046e7b5e..0e1af4e32a 100644
--- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleAssertService.java
+++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleAssertService.java
@@ -7,6 +7,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
 
 import com.mercedesbenz.sechub.domain.schedule.access.ScheduleUserAccessToProjectValidationService;
 import com.mercedesbenz.sechub.domain.schedule.config.SchedulerProjectConfigService;
@@ -40,6 +41,9 @@ public class ScheduleAssertService {
     @Autowired
     ProjectIdValidation projectIdValidation;
 
+    @Autowired
+    ScheduleSecHubConfigurationRuntimeValidation configurationRuntimeValidation;
+
     /**
      * Assert current logged in user has access to project
      *
@@ -92,4 +96,21 @@ public void assertProjectAllowsWriteAccess(String projectId) {
         }
     }
 
+    /**
+     * Asserts given configuration is valid at runtime. This will check in deep, if
+     * the current runtime setup of SecHub accepts the configuration.
+ *
+ * For example: A user defines a web scan with a template configuration. The + * template configuration needs an mandatory parameter "param1" - but the given + * configuration does not contain this parameter. In this case the assertion + * will fail with a dedicated message. + * + * @param configuration + * @throws ResponseStatusException + */ + public void assertValidAtRuntime(SecHubConfiguration configuration) { + configurationRuntimeValidation.assertConfigurationValidAtRuntime(configuration); + + } + } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidation.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidation.java new file mode 100644 index 0000000000..5263dfd00a --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidation.java @@ -0,0 +1,78 @@ +package com.mercedesbenz.sechub.domain.schedule; + +import static java.util.Objects.*; + +import java.util.List; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ResponseStatusException; + +import com.mercedesbenz.sechub.commons.model.SecHubMessage; +import com.mercedesbenz.sechub.commons.model.SecHubMessageType; +import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; +import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsRecevingSyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingSyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; + +@Component +public class ScheduleSecHubConfigurationRuntimeValidation { + + private DomainMessageService eventBus; + + @Lazy + public ScheduleSecHubConfigurationRuntimeValidation(DomainMessageService eventBus) { + this.eventBus = eventBus; + } + + /** + * This validation asserts that a SecHub configuration is valid at runtime. This + * is done by sending a synchronous domain event which will return a check + * result in form of a {@link SecHubMessagesList}. If the list contains error + * messages, the validation has failed.
+ *
+ * Attention: This method should only be used at job creation time. + * + * @throws ResponseStatusException + */ + @IsSendingSyncMessage(MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION) + @IsRecevingSyncMessage(MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION) + public void assertConfigurationValidAtRuntime(SecHubConfiguration configuration) { + requireNonNull(configuration, "SecHub configuration may not be null!"); + + DomainMessage request = new DomainMessage(MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION); + request.set(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG, configuration); + + /* send request */ + DomainMessageSynchronousResult result = eventBus.sendSynchron(request); + + /* inspect result */ + SecHubMessagesList messagesList = result.get(MessageDataKeys.ERROR_MESSAGES); + if (messagesList == null) { + throw new IllegalStateException( + "For the key " + MessageDataKeys.ERROR_MESSAGES.getId() + " there must be at least an empty list of SecHub messages!"); + } + List sechubMessages = messagesList.getSecHubMessages(); + + SecHubMessage validationErrorMessage = null; + for (SecHubMessage sechubMessage : sechubMessages) { + if (SecHubMessageType.ERROR.equals(sechubMessage.getType())) { + validationErrorMessage = sechubMessage; + break;/* we just use the first error message - and skip further processing here */ + } + } + if (validationErrorMessage == null) { + /* no error found, means valid */ + return; + } + throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, validationErrorMessage.getText()); + + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobService.java index e7e21d75be..e5d479c91f 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobService.java @@ -67,6 +67,8 @@ public SchedulerResult createJob(String projectId, @Valid SecHubConfiguration co assertService.assertProjectAllowsWriteAccess(projectId); assertService.assertExecutionAllowed(configuration); + assertService.assertValidAtRuntime(configuration); + ScheduleSecHubJob secHubJob = secHubJobFactory.createJob(configuration); jobRepository.save(secHubJob); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java index f80c911b9f..e705e944d6 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java @@ -63,7 +63,7 @@ public ScheduleSecHubJob createJob(@Valid SecHubConfiguration configuration) { job.owner = userId; job.created = LocalDateTime.now(); - Set scanTypes = modelSupport.collectPublicScanTypes(configuration); + Set scanTypes = modelSupport.collectScanTypes(configuration); job.moduleGroup = ModuleGroup.resolveModuleGroupOrNull(scanTypes); } catch (JSONConverterException e) { diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidationTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidationTest.java new file mode 100644 index 0000000000..ba829159b6 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/ScheduleSecHubConfigurationRuntimeValidationTest.java @@ -0,0 +1,101 @@ +package com.mercedesbenz.sechub.domain.schedule; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatusCode; +import org.springframework.web.server.ResponseStatusException; + +import com.mercedesbenz.sechub.commons.model.SecHubMessage; +import com.mercedesbenz.sechub.commons.model.SecHubMessageType; +import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; +import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; + +class ScheduleSecHubConfigurationRuntimeValidationTest { + + @Mock + DomainMessageService domainMessageService; + private ScheduleSecHubConfigurationRuntimeValidation validationToTest; + + @BeforeEach + void beforeEach() { + MockitoAnnotations.openMocks(this); + validationToTest = new ScheduleSecHubConfigurationRuntimeValidation(domainMessageService); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2 }) + void sends_event_with_unencrypted_sechub_config_and_throws_no_exception_when_result_contains_no_errors(int variant) { + /* prepare */ + SecHubConfiguration configuration = new SecHubConfiguration(); + configuration.setApiVersion("0815-just-for-testing"); + SecHubMessagesList errorMessages = new SecHubMessagesList(); + switch (variant) { + case 0: + /* nothing added */ + break; + case 1: + errorMessages.getSecHubMessages().add(new SecHubMessage(SecHubMessageType.INFO, "i am just an info - so I will not throw an exception")); + break; + case 2: + errorMessages.getSecHubMessages().add(new SecHubMessage(SecHubMessageType.WARNING, "i am jut a warning - so I will not throw an exception")); + break; + + default: + fail("Not implemented variant!"); + + } + + DomainMessageSynchronousResult domainMessageResult = mock(DomainMessageSynchronousResult.class); + when(domainMessageResult.getMessageId()).thenReturn(MessageID.RESULT_FULL_CONFIGURATION_VALIDATION); + + when(domainMessageResult.get(MessageDataKeys.ERROR_MESSAGES)).thenReturn(errorMessages); + when(domainMessageService.sendSynchron(any())).thenReturn(domainMessageResult); + + /* execute */ + validationToTest.assertConfigurationValidAtRuntime(configuration); + + /* test */ + ArgumentCaptor captor = ArgumentCaptor.forClass(DomainMessage.class); + verify(domainMessageService).sendSynchron(captor.capture()); + + DomainMessage domainMessageSent = captor.getValue(); + assertThat(domainMessageSent.getMessageId()).isEqualTo(MessageID.REQUEST_FULL_CONFIGURATION_VALIDATION); + assertThat(domainMessageSent.get(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG).toFormattedJSON()).isEqualTo(configuration.toFormattedJSON()); + } + + @Test + void when_received_result_contains_an_error_message_an_response_excpetion_with_422_and_error_message_is_thrown() { + /* prepare */ + SecHubConfiguration configuration = new SecHubConfiguration(); + SecHubMessagesList errorMessages = new SecHubMessagesList(); + errorMessages.getSecHubMessages().add(new SecHubMessage(SecHubMessageType.ERROR, "i am an error")); + + DomainMessageSynchronousResult result = mock(DomainMessageSynchronousResult.class); + when(result.getMessageId()).thenReturn(MessageID.RESULT_FULL_CONFIGURATION_VALIDATION); + + when(result.get(MessageDataKeys.ERROR_MESSAGES)).thenReturn(errorMessages); + when(domainMessageService.sendSynchron(any())).thenReturn(result); + + /* execute + test @formatter:off */ + assertThatThrownBy(() -> validationToTest.assertConfigurationValidAtRuntime(configuration)). + isInstanceOf(ResponseStatusException.class). + satisfies((s) -> ((ResponseStatusException) s).getReason().equals("i am an error")). + satisfies((s) -> ((ResponseStatusException) s).getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(422))); + /*@formatter:on*/ + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java index 48bb479746..e9d643116f 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java @@ -139,7 +139,7 @@ void createJob_calculates_scan_group_from_data_given_by_model_suppport(ScanType /* prepare */ when(userContextService.getUserId()).thenReturn("user1"); SecHubConfiguration configuration = mock(SecHubConfiguration.class); - when(modelSupport.collectPublicScanTypes(configuration)).thenReturn(Collections.singleton(type)); + when(modelSupport.collectScanTypes(configuration)).thenReturn(Collections.singleton(type)); /* execute */ ScheduleSecHubJob result = factoryToTest.createJob(configuration); diff --git a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerPojoFactory.java b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerPojoFactory.java index 61a3f5cfc7..d7380ce2c8 100644 --- a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerPojoFactory.java +++ b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerPojoFactory.java @@ -5,6 +5,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.commons.core.CachingPatternProvider; +import com.mercedesbenz.sechub.commons.core.PatternCompiler; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironment; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; import com.mercedesbenz.sechub.commons.core.security.CheckSumSupport; @@ -13,6 +15,7 @@ import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelValidator; import com.mercedesbenz.sechub.commons.model.TrafficLightSupport; import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver; +import com.mercedesbenz.sechub.commons.model.template.TemplateUsageValidator; /** * This factory creates some "plain old java" objects and inject them into @@ -64,4 +67,12 @@ SecHubConfigurationModelValidator createSecHubConfigurationValidator() { TemplateDataResolver createTemplateDataResolver() { return new TemplateDataResolver(); } + + @Bean + TemplateUsageValidator createTemplateUsageValidator() { + // we set here a dedicated pattern provider with a fixed size and compiler + // instance + // (shall not be injectable by any other location, so directly created here) + return new TemplateUsageValidator(new CachingPatternProvider(30, new PatternCompiler())); + } } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java index 63b48d4e27..d5090d0207 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java @@ -56,6 +56,8 @@ private MessageDataKeys() { public static final MessageDataKey EXECUTED_BY = createKey("common.executedby", STRING_MESSAGE_DATA_PROVIDER); public static final MessageDataKey REPORT_TRAFFIC_LIGHT = createKey("report.trafficlight", STRING_MESSAGE_DATA_PROVIDER); public static final MessageDataKey REPORT_MESSAGES = createKey("report.messages", SECHUB_MESSAGES_LIST_MESSAGE_DATA_PROVIDER); + public static final MessageDataKey ERROR_MESSAGES = createKey("full.config.validation.result.messages", + SECHUB_MESSAGES_LIST_MESSAGE_DATA_PROVIDER); public static final MessageDataKey SECHUB_JOB_UUID = createKey("sechub.job.uuid", UID_MESSAGE_DATA_PROVIDER); public static final MessageDataKey SECHUB_EXECUTION_UUID = createKey("sechub.execution.uuid", UID_MESSAGE_DATA_PROVIDER); diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java index eca490f010..52f9d3afe5 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java @@ -230,6 +230,19 @@ public enum MessageID { RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT(MessageDataKeys.PROJECT_TEMPLATES), + /** + * Message to request a full validation of a SecHub configuration. + */ + REQUEST_FULL_CONFIGURATION_VALIDATION(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG), + + /** + * Result of a full validation of a SecHub configuration. If one of the + * MessageDataKeys#ERROR_MESSAGES contains at least one error message, the + * validation has failed. + * + */ + RESULT_FULL_CONFIGURATION_VALIDATION(MessageDataKeys.ERROR_MESSAGES), + ; private Set> unmodifiableKeys; From 6cc1185f7e78b2b9797ffc982c591746a4307dc1 Mon Sep 17 00:00:00 2001 From: Albert Tregnaghi Date: Wed, 12 Feb 2025 15:54:57 +0100 Subject: [PATCH 3/3] Fix broken tests + templateData has now sorted variables #3610 --- .../commons/model/template/TemplateData.java | 14 ++++++++++---- .../template/TemplateUsageValidatorTest.java | 2 -- .../PDSWebScanJobScenario12IntTest.java | 15 +++++++++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java index e2d9241233..69fa94326e 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.commons.model.template; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; /** * Template data for SecHub configuration model. Here users can define user @@ -13,9 +13,15 @@ */ public class TemplateData { - private Map variables = new LinkedHashMap<>(); // entries are ordered in model like ordered in json + private SortedMap variables = new TreeMap<>(); - public Map getVariables() { + /** + * Return a sorted map containing variable names as keys and variable values as + * values. + * + * @return sorted variable map + */ + public SortedMap getVariables() { return variables; } diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java index ebb196adfd..8003500f91 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateUsageValidatorTest.java @@ -97,7 +97,6 @@ void mandatory_variable_not_defined_in_template_data(String nameOfOtherVariable) @ParameterizedTest @EmptySource - @NullSource @ValueSource(strings = { "mandatory-var", "other" }) void mandatory_variable_is_defined_in_template_data(String variableName) { /* prepare */ @@ -138,7 +137,6 @@ void optional_variable_not_defined_in_template_data(String nameOfOtherVariable) @ParameterizedTest @EmptySource - @NullSource @ValueSource(strings = { "optional-var", "other" }) void optional_variable_is_defined_in_template_data(String variableName) { /* prepare */ diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java index f813483f04..136f915a2d 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java @@ -44,6 +44,10 @@ */ public class PDSWebScanJobScenario12IntTest { + private static final String TEMPLATE_VARIABLE_PASSWORD = "test-password"; + + private static final String TEMPLATE_VARIABLE_USERNAME = "test-username"; + @Rule public IntegrationTestSetup setup = IntegrationTestSetup.forScenario(Scenario12.class); @@ -94,10 +98,10 @@ public void pds_web_scan_can_be_executed_and_works() throws Exception { String targetURL = configuration.getWebScan().get().getUrl().toString(); TemplateVariable userNameVariable = new TemplateVariable(); - userNameVariable.setName("username"); + userNameVariable.setName(TEMPLATE_VARIABLE_USERNAME); TemplateVariable passwordVariable = new TemplateVariable(); - passwordVariable.setName("password"); + passwordVariable.setName(TEMPLATE_VARIABLE_PASSWORD); TemplateDefinition templateDefinition = new TemplateDefinition(); templateDefinition.setAssetId(assetId); @@ -106,6 +110,13 @@ public void pds_web_scan_can_be_executed_and_works() throws Exception { templateDefinition.getVariables().add(passwordVariable); String templateId = "template-scenario12-1"; + + // we must add the mandatory variables from template definition also to template data section of configuration (or job could not be created) + var templateDataVariables = configuration.getWebScan().get().getLogin().get().getTemplateData().getVariables(); + templateDataVariables.put(TEMPLATE_VARIABLE_USERNAME, "test-user"); + templateDataVariables.put(TEMPLATE_VARIABLE_PASSWORD, "test-fake-password"); + + as(SUPER_ADMIN). updateWhiteListForProject(project, Arrays.asList(targetURL)). uploadAssetFile(assetId, productZipFile).