Support for overriding configuration of security providers using simple properties. (#2511)

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
This commit is contained in:
Tomas Langer
2020-11-13 17:36:31 +01:00
committed by GitHub
parent f97ae1bb0a
commit e3a804216a
6 changed files with 187 additions and 18 deletions

View File

@@ -138,6 +138,21 @@ security:
roles: ["user"] roles: ["user"]
---- ----
==== Overriding configuration
When a configuration needs to be overridden, we may have problems with the list
type of the `providers` configuration. To simplify overrides using properties,
you can explicitly setup a type of provider using a `type` key.
Example:
[source,properties]
----
security.providers.1.type=header-atn
security.providers.1.header-atn.authenticate=false
----
Would explicitly override the second provider (`http-basic-auth` in example above) with
`header-atn` provider. Note that the `type` and the key of the provider must match.
=== Hybrid pattern (Builder & Configuration) === Hybrid pattern (Builder & Configuration)
[source,java] [source,java]

View File

@@ -44,6 +44,7 @@ import java.util.stream.Collectors;
import io.helidon.common.configurable.ThreadPoolSupplier; import io.helidon.common.configurable.ThreadPoolSupplier;
import io.helidon.common.serviceloader.HelidonServiceLoader; import io.helidon.common.serviceloader.HelidonServiceLoader;
import io.helidon.config.Config; import io.helidon.config.Config;
import io.helidon.config.ConfigValue;
import io.helidon.security.internal.SecurityAuditEvent; import io.helidon.security.internal.SecurityAuditEvent;
import io.helidon.security.spi.AuditProvider; import io.helidon.security.spi.AuditProvider;
import io.helidon.security.spi.AuthenticationProvider; import io.helidon.security.spi.AuthenticationProvider;
@@ -80,6 +81,7 @@ public class Security {
private static final Set<String> RESERVED_PROVIDER_KEYS = Set.of( private static final Set<String> RESERVED_PROVIDER_KEYS = Set.of(
"name", "name",
"type",
"class", "class",
"is-authentication-provider", "is-authentication-provider",
"is-authorization-provider", "is-authorization-provider",
@@ -951,7 +953,9 @@ public class Security {
} }
private void providerFromConfig(Map<String, SecurityProviderService> configKeyToService, private void providerFromConfig(Map<String, SecurityProviderService> configKeyToService,
Map<String, SecurityProviderService> classNameToService, String knownKeys, Config pConf) { Map<String, SecurityProviderService> classNameToService,
String knownKeys,
Config pConf) {
AtomicReference<SecurityProviderService> service = new AtomicReference<>(); AtomicReference<SecurityProviderService> service = new AtomicReference<>();
AtomicReference<Config> providerSpecific = new AtomicReference<>(); AtomicReference<Config> providerSpecific = new AtomicReference<>();
@@ -1053,24 +1057,38 @@ public class Security {
Config pConf, Config pConf,
AtomicReference<SecurityProviderService> service, AtomicReference<SecurityProviderService> service,
AtomicReference<Config> providerSpecific) { AtomicReference<Config> providerSpecific) {
// everything else is based on provider specific configuration
pConf.asNodeList().get().stream().filter(this::notReservedProviderKey).forEach(providerSpecificConf -> {
if (!providerSpecific.compareAndSet(null, providerSpecificConf)) {
throw new SecurityException("More than one provider configurations found, each provider can only"
+ " have one provider specific config. Conflict: "
+ providerSpecific.get().key()
+ " and " + providerSpecificConf.key());
}
String keyName = providerSpecificConf.name(); ConfigValue<String> type = pConf.get("type").asString();
if (configKeyToService.containsKey(keyName)) { if (type.isPresent()) {
service.set(configKeyToService.get(keyName)); // explicit type, ignore search below
} else { findProviderService(service, configKeyToService, type.get(), knownKeys);
throw new SecurityException("Configuration key " + providerSpecificConf.key() providerSpecific.set(pConf.get(type.get()));
+ " is not a valid provider configuration. Supported keys: " } else {
+ knownKeys); // everything else is based on provider specific configuration
} pConf.asNodeList().get().stream().filter(this::notReservedProviderKey).forEach(providerSpecificConf -> {
}); if (!providerSpecific.compareAndSet(null, providerSpecificConf)) {
throw new SecurityException("More than one provider configurations found, each provider can only"
+ " have one provider specific config. Conflict: "
+ providerSpecific.get().key()
+ " and " + providerSpecificConf.key());
}
findProviderService(service, configKeyToService, providerSpecificConf.name(), knownKeys);
});
}
}
private void findProviderService(AtomicReference<SecurityProviderService> service,
Map<String, SecurityProviderService> configKeyToService,
String name,
String knownKeys) {
if (configKeyToService.containsKey(name)) {
service.set(configKeyToService.get(name));
} else {
throw new SecurityException("Configuration key " + name
+ " is not a valid provider configuration. Supported keys: "
+ knownKeys);
}
} }
private String loadProviderServices(Map<String, SecurityProviderService> configKeyToService, private String loadProviderServices(Map<String, SecurityProviderService> configKeyToService,

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2020 Oracle and/or its affiliates.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>helidon-tests-integration-security</artifactId>
<groupId>io.helidon.tests.integration</groupId>
<version>2.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>helidon-tests-integration-security-gh2455</artifactId>
<name>Helidon Tests Integration Security GH-2455</name>
<description>
Integration test for issue https://github.com/oracle/helidon/issues/2455
</description>
<dependencies>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-http-auth</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-header</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.security.providers</groupId>
<artifactId>helidon-security-providers-abac</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
security:
providers:
- abac:
- http-basic-auth:
realm: "helidon"
users:
- login: "tomas"
password: "password"
roles: ["admin"]

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.tests.integration.security.gh2455;
import java.util.Map;
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.security.Security;
import org.junit.jupiter.api.Test;
class TestProviderOverrides {
@Test
void testOverride() {
Map<String, String> map = Map.of(
"security.providers.1.type", "header-atn",
"security.providers.1.header-atn.authenticate", "false"
);
Config config = Config.builder()
.addSource(ConfigSources.create(map))
.addSource(ConfigSources.classpath("application.yaml"))
.disableEnvironmentVariablesSource()
.disableSystemPropertiesSource()
.build();
Security security = Security.create(config.get("security"));
}
}

View File

@@ -37,5 +37,6 @@
<modules> <modules>
<module>gh1487</module> <module>gh1487</module>
<module>gh2297</module> <module>gh2297</module>
<module>gh2455</module>
</modules> </modules>
</project> </project>