All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.security.abac.policy.PolicyValidator Maven / Gradle / Ivy

There is a newer version: 4.1.6
Show newest version
/*
 * Copyright (c) 2018, 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.security.abac.policy;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.helidon.common.Errors;
import io.helidon.common.serviceloader.HelidonServiceLoader;
import io.helidon.config.Config;
import io.helidon.security.EndpointConfig;
import io.helidon.security.ProviderRequest;
import io.helidon.security.SecurityLevel;
import io.helidon.security.abac.policy.spi.PolicyExecutor;
import io.helidon.security.abac.policy.spi.PolicyExecutorService;
import io.helidon.security.providers.abac.AbacAnnotation;
import io.helidon.security.providers.abac.AbacValidatorConfig;
import io.helidon.security.providers.abac.spi.AbacValidator;

/**
 * Abac validator based on a {@link PolicyStatement}. The statement itself is not resolved by this validator
 * and is delegated to another module implementing the {@link PolicyExecutor} obtained through a {@link PolicyExecutorService}
 * java service.
 * 

* Implementations provided by Helidon security: *

    *
  • Java EE expression language support, artifact id: "helidon-security-abac-policy-el"
  • *
* * Example of a policy statement:
* @PolicyStatement("${env.time.year >= 2017 && object.owner == subject.principal.id}") */ public final class PolicyValidator implements AbacValidator { private static final Logger LOGGER = Logger.getLogger(PolicyValidator.class.getName()); private final List executors = new LinkedList<>(); private PolicyValidator(Builder builder) { //first find all services HelidonServiceLoader services = HelidonServiceLoader.create(ServiceLoader.load(PolicyExecutorService.class)); for (PolicyExecutorService service : services) { executors.add(service.instantiate(builder.config.get(service.configKey()))); } //then add explicit this.executors.addAll(builder.executors); } /** * Creates a fluent API builder to build new instances of this class. * * @return a new builder instance */ public static Builder builder() { return new Builder(); } /** * Create an instance from configuration. * Example: *
     * # configuration of this validator (current key in config passed to this instance)
     * policy-validator:
     *   # explicit validators - only needed if not implementing service interface {@link PolicyExecutorService}
     *   validators:
     *     - class: "io.helidon.security.abac.policy.DefaultPolicyValidator"
     *     - class: "..."
     *   # configuration of a policy executor - provide this name through {@link PolicyExecutorService#configKey()}
     *   my-custom-policy-engine:
     *     some-key: "some value"
     *     another-key: "another value"
     * 
* * @param config configuration to load this class from * @return a new instance from config */ public static PolicyValidator create(Config config) { return builder().config(config).build(); } @Override public Collection> supportedAnnotations() { return Set.of(PolicyStatement.class); } @Override public Class configClass() { return PolicyConfig.class; } @Override public String configKey() { return "policy-validator"; } @Override public PolicyConfig fromConfig(Config config) { return PolicyConfig.builder() .config(config) .build(); } @Override public PolicyConfig fromAnnotations(EndpointConfig endpointConfig) { PolicyConfig.Builder resultBuilder = PolicyConfig.builder(); for (SecurityLevel securityLevel : endpointConfig.securityLevels()) { for (EndpointConfig.AnnotationScope scope : EndpointConfig.AnnotationScope.values()) { List annotations = new ArrayList<>(); for (Class annotation : supportedAnnotations()) { annotations.addAll(securityLevel.filterAnnotations(annotation, scope)); } for (Annotation annotation : annotations) { if (annotation instanceof PolicyStatement) { PolicyStatement statement = (PolicyStatement) annotation; resultBuilder.from(PolicyConfig.builder().from(statement).build()); } } } } return resultBuilder.build(); } @Override public void validate(PolicyConfig config, Errors.Collector collector, ProviderRequest request) { List unvalidatedStatements = new LinkedList<>(); boolean isValidated; for (String statement : config.policyStatements()) { isValidated = false; for (PolicyExecutor executor : executors) { if (executor.supports(statement, request)) { executor.executePolicy(statement, collector, request); isValidated = true; break; } } if (!isValidated) { unvalidatedStatements.add(statement); } } if (!unvalidatedStatements.isEmpty()) { throw new SecurityException("Missing a policy executor for policy statement(s). Statements: " + unvalidatedStatements + ", known executors: " + executors); } } /** * Annotate resource classes, methods, application etc. with this annotation * to enforce validation of the policy statement defined. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented @Inherited @AbacAnnotation public @interface PolicyStatement { /** * The statement of this policy. Actual value depends on policy engine in use. * * @return policy statement value */ String value(); /** * By default, all policy statements defined on all levels (application, class, method, field) are executed * in order. If set to false, on the last policy in the list is executed (e.g. only on method for resource method * of Jersey integration, if it is defined on method). * NOTE: if there is no statement defined on current level, we will use a statement from nearest level above * * @return whether to inherit policies from annotations defined on higher level */ boolean inherit() default true; } /** * A fluent API builder for {@link PolicyValidator}. */ public static final class Builder implements io.helidon.common.Builder { private final List executors = new LinkedList<>(); private Config config = Config.empty(); private Builder() { } @Override public PolicyValidator build() { return new PolicyValidator(this); } /** * Add an executor (that is not available as a java service). * * @param executor to evaluate policy statements * @return updated builder instance */ public Builder addExecutor(PolicyExecutor executor) { this.executors.add(executor); return this; } /** * Update this builder from configuration. * * @param config configuration instance located on {@link PolicyValidatorService#configKey()} * @return updated builder instance */ public Builder config(Config config) { this.config = config; config.get("validators").asList(Config.class).ifPresent(configs -> { for (Config validatorConfig : configs) { validatorConfig.get("class").asString() .ifPresentOrElse(clazz -> { //attempt to instantiate addExecutor(instantiate(clazz)); }, () -> { throw new SecurityException( "validators key may only contain an array of class to class names, at key: " + validatorConfig.key()); }); } }); return this; } private PolicyExecutor instantiate(String className) { Class clazz; try { clazz = Class.forName(className); } catch (Exception e) { throw new SecurityException("Failed to get class " + className, e); } try { return (PolicyExecutor) clazz.getConstructor().newInstance(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Could not instantiate: " + className + ". Class must have a default public"); throw new SecurityException("Failed to load PolicyExecutor from class " + clazz, e); } } } /** * Configuration of policy validator - a statement and whether to inherit value * from parents. */ public static final class PolicyConfig implements AbacValidatorConfig { private final List policyStatements; private final boolean inherit; private PolicyConfig(Builder builder) { this.policyStatements = builder.policyStatements; this.inherit = builder.inherit; } /** * Creates a fluent API builder to build new instances of this class. * * @return a new builder instance */ public static Builder builder() { return new Builder(); } /** * The policy statements collected through configuration hierarchy. * * @return list of statements in order they should be executed */ public List policyStatements() { return Collections.unmodifiableList(policyStatements); } /** * Whether to inherit statements from parent configurations. * * @return true to inherit, false to execute only this class's statements */ public boolean shouldInherit() { return inherit; } /** * A fluent API builder for {@link PolicyConfig}. */ public static final class Builder implements io.helidon.common.Builder { private final List policyStatements = new LinkedList<>(); private boolean inherit = true; private Builder() { } /** * The statement of this policy. Actual value depends on policy engine in use. * * @param policyStatement statement to validate access against * @return updated builder instance */ public Builder statement(String policyStatement) { this.policyStatements.clear(); this.policyStatements.add(policyStatement); return this; } /** * By default, all policy statements defined on all levels (application, class, method, field) are executed * in order. If set to false, on the last policy in the list is executed (e.g. only on method for resource method * of Jersey integration, if it is defined on method). * NOTE: if there is no statement defined on current level, we will use a statement from nearest level above * * @param inherit whether to inherit policies from annotations defined on higher level * @return updated builder instance */ public Builder inherit(boolean inherit) { this.inherit = inherit; return this; } /** * Update this builder from configuration. * * @param config config instance located on the key {@link PolicyValidator#configKey()} * @return updated builder instance */ public Builder config(Config config) { config.get("inherit").asBoolean().ifPresent(this::inherit); config.get("statement").asString().ifPresent(this::statement); return this; } Builder from(PolicyStatement annot) { return inherit(annot.inherit()).statement(annot.value()); } Builder from(PolicyConfig config) { if (!config.inherit) { policyStatements.clear(); } inherit(config.inherit); policyStatements.addAll(config.policyStatements); return this; } @Override public PolicyConfig build() { return new PolicyConfig(this); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy