io.helidon.security.CompositeProviderSelectionPolicy Maven / Gradle / Ivy
/*
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
*
* 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.security;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import io.helidon.config.Config;
import io.helidon.security.spi.AuthenticationProvider;
import io.helidon.security.spi.AuthorizationProvider;
import io.helidon.security.spi.OutboundSecurityProvider;
import io.helidon.security.spi.ProviderSelectionPolicy;
import io.helidon.security.spi.SecurityProvider;
/**
* A provider selection policy that supports composing multiple providers (current Authentication and Outbound)
* into a single virtual security provider.
*
* Example configuration:
*
* security.provider-policy {
* type = "COMPOSITE"
* # explicit name of this policy (to be used when this is not the default or when we want to explicitly reference it)
* name = "composite"
* # whether this is the default provider or not (if not, must be explicitly defined by name, if yes, it is returned)
* default = true
* authentication: [
* {
* name = "first"
* flag = "REQUIRED"
* },
* {
* name = "second"
* }]
* outbound: [
* {
* name = "first"
* },
* {
* name = "second"
* }]
* }
*
*/
public final class CompositeProviderSelectionPolicy implements ProviderSelectionPolicy {
private final CompositeOutboundProvider outbound;
private final CompositeAuthenticationProvider atn;
private final CompositeAuthorizationProvider atz;
private final Set configuredOutbound = new HashSet<>();
private final List> allOutbound = new LinkedList<>();
private final boolean isDefault;
private final String name;
private final FirstProviderSelectionPolicy fallback;
@SuppressWarnings("unchecked")
private CompositeProviderSelectionPolicy(Providers providers, Builder builder) {
this.fallback = new FirstProviderSelectionPolicy(providers);
this.isDefault = builder.isDefault;
this.name = builder.name;
if (!builder.authenticators.isEmpty()) {
List parts = new LinkedList<>();
builder.authenticators
.forEach(flaggedProvider -> parts.add(new CompositeAuthenticationProvider.Atn(
flaggedProvider,
providers.getProviders(AuthenticationProvider.class)
.stream()
.filter(np -> np.getName().equals(flaggedProvider.providerName()))
.findFirst()
.map(NamedProvider::getProvider)
.orElseThrow(() -> new SecurityException(
"Misconfigured composite provider selection policy. There is no authentication "
+ "provider named " + flaggedProvider
.providerName() + " configured."))
)));
atn = new CompositeAuthenticationProvider(parts);
} else {
atn = null;
}
if (!builder.authorizers.isEmpty()) {
List parts = new LinkedList<>();
builder.authorizers
.forEach(flaggedProvider -> parts.add(new CompositeAuthorizationProvider.Atz(
flaggedProvider,
providers.getProviders(AuthorizationProvider.class)
.stream()
.filter(np -> np.getName().equals(flaggedProvider.providerName()))
.findFirst()
.map(NamedProvider::getProvider)
.orElseThrow(() -> new SecurityException(
"Misconfigured composite provider selection policy. There is no authorization "
+ "provider named " + flaggedProvider
.providerName() + " configured."))
)));
atz = new CompositeAuthorizationProvider(parts);
} else {
atz = null;
}
allOutbound.addAll(providers.getProviders(OutboundSecurityProvider.class));
if (!builder.outbound.isEmpty()) {
List parts = new LinkedList<>();
configuredOutbound.addAll(builder.outbound);
builder.outbound
.forEach(name -> parts.add(
providers.getProviders(OutboundSecurityProvider.class)
.stream()
.filter(np -> np.getName().equals(name))
.findFirst()
.map(NamedProvider::getProvider)
.orElseThrow(() -> new SecurityException(
"Misconfigured composite provider selection policy. There is no outbound security "
+ "provider "
+ "provider named " + name + " configured."))
));
outbound = new CompositeOutboundProvider(parts);
} else {
outbound = null;
}
}
/**
* Builder for this selection policy.
*
* @return builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Load this policy from config. See {@link CompositeProviderSelectionPolicy} for example.
*
* @param config configuration instance
* @return function as expected by {@link Security.Builder#providerSelectionPolicy(Function)}
*/
public static Function create(Config config) {
return builder().config(config).build();
}
@Override
public Optional selectProvider(Class providerType) {
if (isDefault) {
if (null != atn && providerType.equals(AuthenticationProvider.class)) {
return Optional.of(providerType.cast(atn));
} else if (null != atz && providerType.equals(AuthorizationProvider.class)) {
return Optional.of(providerType.cast(atz));
}
}
return fallback.selectProvider(providerType);
}
@Override
public List selectOutboundProviders() {
LinkedList result = new LinkedList<>();
// only add the ones we are not composing
allOutbound.stream()
.filter(np -> !configuredOutbound.contains(np.getName()))
.forEach(np -> result.add(np.getProvider()));
if (null != outbound) {
if (isDefault) {
result.addFirst(outbound);
} else {
result.addLast(outbound);
}
}
return result;
}
@Override
public Optional selectProvider(Class providerType, String requestedName) {
if (name.equals(requestedName)) {
if (null != atn && providerType.equals(AuthenticationProvider.class)) {
return Optional.of(providerType.cast(atn));
} else if (null != atz && providerType.equals(AuthorizationProvider.class)) {
return Optional.of(providerType.cast(atz));
} else if (null != outbound && providerType.equals(OutboundSecurityProvider.class)) {
return Optional.of(providerType.cast(outbound));
}
}
return fallback.selectProvider(providerType, requestedName);
}
/**
* Fluent API builder to create {@link CompositeProviderSelectionPolicy}.
* Invoke {@link #build()} to get a function to be sent to {@link Security.Builder#providerSelectionPolicy(Function)}.
*/
public static final class Builder implements io.helidon.common.Builder> {
private final List authenticators = new LinkedList<>();
private final List authorizers = new LinkedList<>();
private final List outbound = new LinkedList<>();
private String name = "composite";
private boolean isDefault = true;
private Builder() {
}
/**
* Name of this provider to use for explicit provider configuration.
* The same name is used for authentication, authorization and outbound security.
*
* @param name name of the virtual provider create by this policy
* @return updated builder instance
*/
public Builder name(String name) {
this.name = name;
return this;
}
/**
* If set to true (default value) then this policy returns a virtual
* provider combining the configured providers.
* If set to false, the virtual provider is returned only when explicitly
* called by {@link #name}.
*
* @param isDefault whether the composite provider is the default
* @return updated builder instance
*/
public Builder isDefault(boolean isDefault) {
this.isDefault = isDefault;
return this;
}
/**
* Add a required provider to this composite provider.
*
* @param namedProvider name of the provider as configured with security
* @return updated builder instance
*/
public Builder addAuthenticationProvider(String namedProvider) {
authenticators.add(new FlaggedProvider(CompositeProviderFlag.REQUIRED, namedProvider));
return this;
}
/**
* Add a provider to this composite policy.
*
* @param namedProvider name of the provider as configured with security
* @param flag to indicate how to handle provider's response
* @return updated builder instance
*/
public Builder addAuthenticationProvider(String namedProvider, CompositeProviderFlag flag) {
authenticators.add(new FlaggedProvider(flag, namedProvider));
return this;
}
/**
* Add a required provider to this composite provider.
*
* @param namedProvider name of the provider as configured with security
* @return updated builder instance
*/
public Builder addAuthorizationProvider(String namedProvider) {
authorizers.add(new FlaggedProvider(CompositeProviderFlag.REQUIRED, namedProvider));
return this;
}
/**
* Add a provider to this composite policy.
*
* @param namedProvider name of the provider as configured with security
* @param flag to indicate how to handle provider's response
* @return updated builder instance
*/
public Builder addAuthorizationProvider(String namedProvider, CompositeProviderFlag flag) {
authorizers.add(new FlaggedProvider(flag, namedProvider));
return this;
}
/**
* Add a provider to this composite policy.
*
* @param namedProvider name of the provider as configured with security
* @return updated builder instance
*/
public Builder addOutboundProvider(String namedProvider) {
outbound.add(namedProvider);
return this;
}
/**
* Update fields from configuration.
*
* @param config Configuration
* @return updated builder instance
*/
public Builder config(Config config) {
config.get("name").asString().ifPresent(this::name);
config.get("default").asBoolean().ifPresent(this::isDefault);
config.get("authentication").asList(FlaggedProvider::create)
.ifPresent(this.authenticators::addAll);
config.get("authorization").asList(FlaggedProvider::create)
.ifPresent(this.authorizers::addAll);
config.get("outbound").asNodeList()
.ifPresent(configs -> configs.forEach(outConfig -> addOutboundProvider(outConfig.get("name")
.asString()
.get())));
return this;
}
/**
* Build a function to create an instance of this provider as expected by
* {@link Security.Builder#providerSelectionPolicy(Function)}.
*
* @return function to build this policy
*/
@Override
public Function build() {
return providers -> new CompositeProviderSelectionPolicy(providers, this);
}
}
static class FlaggedProvider {
private final CompositeProviderFlag flag;
private final String providerName;
FlaggedProvider(CompositeProviderFlag flag, String providerName) {
this.flag = flag;
this.providerName = providerName;
}
/**
* Load an instance from configuration.
*
* @param config configuration instance
* @return instance configured from config
*/
static FlaggedProvider create(Config config) {
String name = config.get("name").asString().get();
CompositeProviderFlag flag = config.get("flag")
.asString()
.as(CompositeProviderFlag::valueOf)
.orElse(CompositeProviderFlag.REQUIRED);
return new FlaggedProvider(flag, name);
}
String providerName() {
return providerName;
}
CompositeProviderFlag flag() {
return flag;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy