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

io.gravitee.am.gateway.handler.common.flow.impl.FlowManagerImpl Maven / Gradle / Ivy

/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * 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.gravitee.am.gateway.handler.common.flow.impl;

import io.gravitee.am.common.event.EventManager;
import io.gravitee.am.common.event.FlowEvent;
import io.gravitee.am.common.policy.ExtensionPoint;
import io.gravitee.am.gateway.handler.common.flow.ExecutionPredicate;
import io.gravitee.am.gateway.handler.common.flow.FlowManager;
import io.gravitee.am.gateway.handler.common.flow.execution.ExecutionFlow;
import io.gravitee.am.gateway.policy.Policy;
import io.gravitee.am.model.Domain;
import io.gravitee.am.model.ReferenceType;
import io.gravitee.am.model.common.event.Payload;
import io.gravitee.am.model.flow.Flow;
import io.gravitee.am.model.flow.Step;
import io.gravitee.am.model.flow.Type;
import io.gravitee.am.model.oidc.Client;
import io.gravitee.am.plugins.policy.core.PolicyPluginManager;
import io.gravitee.am.service.FlowService;
import io.gravitee.common.event.Event;
import io.gravitee.common.event.EventListener;
import io.gravitee.common.service.AbstractService;
import io.reactivex.rxjava3.core.Single;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;

import static io.gravitee.am.common.policy.ExtensionPoint.POST_CONSENT;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_LOGIN;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_LOGIN_IDENTIFIER;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_REGISTER;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_REGISTRATION_CONFIRMATION;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_RESET_PASSWORD;
import static io.gravitee.am.common.policy.ExtensionPoint.POST_TOKEN;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_CONSENT;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_LOGIN;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_LOGIN_IDENTIFIER;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_REGISTER;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_REGISTRATION_CONFIRMATION;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_RESET_PASSWORD;
import static io.gravitee.am.common.policy.ExtensionPoint.PRE_TOKEN;
import static io.gravitee.am.common.policy.ExtensionPoint.ROOT;
import static io.gravitee.am.gateway.handler.common.flow.ExecutionPredicate.alwaysTrue;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.toList;

/**
 * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
 * @author GraviteeSource Team
 */
public class FlowManagerImpl extends AbstractService implements FlowManager, InitializingBean, EventListener {

    private static final Logger logger = LoggerFactory.getLogger(FlowManagerImpl.class);
    private static final Map> extensionPoints;

    static {
        extensionPoints = Map.of(
                Type.ROOT, List.of(ExtensionPoint.ROOT),
                Type.LOGIN_IDENTIFIER, List.of(PRE_LOGIN_IDENTIFIER, POST_LOGIN_IDENTIFIER),
                Type.CONSENT, List.of(PRE_CONSENT, POST_CONSENT),
                Type.LOGIN, List.of(PRE_LOGIN, POST_LOGIN),
                Type.REGISTER, List.of(PRE_REGISTER, POST_REGISTER),
                Type.RESET_PASSWORD, List.of(PRE_RESET_PASSWORD, POST_RESET_PASSWORD),
                Type.REGISTRATION_CONFIRMATION, List.of(PRE_REGISTRATION_CONFIRMATION, POST_REGISTRATION_CONFIRMATION),
                Type.TOKEN, List.of(PRE_TOKEN, POST_TOKEN)
        );
    }

    @Autowired
    private Domain domain;

    @Autowired
    private FlowService flowService;

    @Autowired
    private PolicyPluginManager policyPluginManager;

    @Autowired
    private EventManager eventManager;

    private final ConcurrentMap flows = new ConcurrentHashMap<>();
    private final ConcurrentMap> policies = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() {
        logger.info("Initializing flows for domain {}", domain.getName());
        loadFlows();
    }

    @Override
    protected void doStart() throws Exception {
        super.doStart();

        logger.info("Register event listener for flow events for domain {}", domain.getName());
        eventManager.subscribeForEvents(this, FlowEvent.class, domain.getId());
    }

    @Override
    protected void doStop() throws Exception {
        super.doStop();

        logger.info("Dispose event listener for flow events for domain {}", domain.getName());
        eventManager.unsubscribeForEvents(this, FlowEvent.class, domain.getId());
    }

    @Override
    public void onEvent(Event event) {
        if (event.content().getReferenceType() == ReferenceType.DOMAIN &&
                domain.getId().equals(event.content().getReferenceId())) {
            switch (event.type()) {
                case DEPLOY:
                case UPDATE:
                    updateFlow(event.content().getId(), event.type());
                    break;
                case UNDEPLOY:
                    removeFlow(event.content().getId());
                    break;
            }
        }
    }

    @Override
    public Single> findByExtensionPoint(ExtensionPoint extensionPoint, Client client, ExecutionPredicate filter) {
        if (filter == null) {
            filter = alwaysTrue();
        }

        Set executionFlows = policies.get(extensionPoint);
        // if no flow, returns empty list
        if (executionFlows == null) {
            return Single.just(Collections.emptyList());
        }

        // get domain policies
        List domainExecutionPolicies = getExecutionPolicies(executionFlows, client, true, filter);

        // if client is null, executes only security domain flows
        if (client == null) {
            return Single.just(domainExecutionPolicies);
        }

        // get application policies
        List applicationExecutionPolicies = getExecutionPolicies(executionFlows, client, false, filter);

        // if client does not inherit domain flows, executes only application flows
        if (!client.isFlowsInherited()) {
            return Single.just(applicationExecutionPolicies);
        }

        return Single.just(
                Stream.concat(
                        domainExecutionPolicies.stream(),
                        applicationExecutionPolicies.stream()
                ).collect(toList()));
    }

    private void updateFlow(String flowId, FlowEvent flowEvent) {
        final String eventType = flowEvent.toString().toLowerCase();
        logger.info("Domain {} has received {} flow event for {}", domain.getName(), eventType, flowId);
        flowService.findById(flowId)
                .subscribe(
                        flow -> {
                            loadFlow(flow);
                            flows.put(flow.getId(), flow);
                            logger.info("Flow {} has been deployed for domain {}", flowId, domain.getName());
                        },
                        error -> logger.error("Unable to deploy flow {} for domain {}", flowId, domain.getName(), error),
                        () -> logger.error("No flow found with id {}", flowId));
    }

    private void removeFlow(String flowId) {
        logger.info("Domain {} has received flow event, delete flow {}", domain.getName(), flowId);
        Flow deletedFlow = flows.remove(flowId);
        if (deletedFlow != null) {
            extensionPoints.get(deletedFlow.getType()).forEach(extensionPoint -> removeExecutionFlow(extensionPoint, deletedFlow.getId()));
        }
    }

    private void loadFlows() {
        flowService.findAll(ReferenceType.DOMAIN, domain.getId())
                .subscribe(
                        flow -> {
                            if (needDeployment(flow)) {
                                loadFlow(flow);
                                flows.put(flow.getId(), flow);
                                logger.info("Flow {} loaded for domain {}", flow.getType(), domain.getName());
                            }
                        },
                        error -> logger.error("Unable to initialize flows for domain {}", domain.getName(), error)
                );
    }

    private void loadFlow(Flow flow) {
        if (!flow.isEnabled()) {
            logger.debug("Flow {} is disabled, skip process", flow.getId());
            extensionPoints.get(flow.getType()).forEach(extensionPoint -> removeExecutionFlow(extensionPoint, flow.getId()));
            return;
        }

        // load policies
        var prePolicies = loadPolicies(flow.getPre());
        var postPolicies = loadPolicies(flow.getPost());

        switch (flow.getType()) {
            case ROOT:
                // for root type, fetch only the pre step policies
                addExecutionFlow(ROOT, flow, prePolicies);
                break;
            case CONSENT:
                addExecutionFlow(PRE_CONSENT, flow, prePolicies);
                addExecutionFlow(POST_CONSENT, flow, postPolicies);
                break;
            case LOGIN:
                addExecutionFlow(PRE_LOGIN, flow, prePolicies);
                addExecutionFlow(POST_LOGIN, flow, postPolicies);
                break;
            case LOGIN_IDENTIFIER:
                addExecutionFlow(PRE_LOGIN_IDENTIFIER, flow, prePolicies);
                addExecutionFlow(POST_LOGIN_IDENTIFIER, flow, postPolicies);
                break;
            case REGISTER:
                addExecutionFlow(PRE_REGISTER, flow, prePolicies);
                addExecutionFlow(POST_REGISTER, flow, postPolicies);
                break;
            case RESET_PASSWORD:
                addExecutionFlow(PRE_RESET_PASSWORD, flow, prePolicies);
                addExecutionFlow(POST_RESET_PASSWORD, flow, postPolicies);
                break;
            case REGISTRATION_CONFIRMATION:
                addExecutionFlow(PRE_REGISTRATION_CONFIRMATION, flow, prePolicies);
                addExecutionFlow(POST_REGISTRATION_CONFIRMATION, flow, postPolicies);
                break;
            case TOKEN:
                addExecutionFlow(PRE_TOKEN, flow, prePolicies);
                addExecutionFlow(POST_TOKEN, flow, postPolicies);
                break;
            default:
                throw new IllegalArgumentException("No suitable flow type found for : " + flow.getType());
        }
    }

    private List loadPolicies(List steps) {
        return steps.stream()
                .filter(Step::isEnabled)
                .map(this::createPolicy)
                .filter(Objects::nonNull)
                .collect(toList());
    }

    private Policy createPolicy(Step step) {
        try {
            logger.info("\tInitializing policy: {} [{}]", step.getName(), step.getPolicy());
            Policy policy = policyPluginManager.create(step.getPolicy(), step.getCondition(), step.getConfiguration());
            logger.info("\tPolicy : {} [{}] has been loaded", step.getName(), step.getPolicy());
            policy.activate();
            return policy;
        } catch (Exception ex) {
            return null;
        }
    }

    private void addExecutionFlow(ExtensionPoint extensionPoint, Flow flow, List executionPolicies) {
        Set existingFlows = policies.get(extensionPoint);
        if (existingFlows == null) {
            existingFlows = new HashSet<>();
        }
        ExecutionFlow executionFlow = new ExecutionFlow(flow, executionPolicies);
        existingFlows.remove(executionFlow);
        existingFlows.add(executionFlow);
        policies.put(extensionPoint, existingFlows);
    }

    private void removeExecutionFlow(ExtensionPoint extensionPoint, String flowId) {
        Set existingFlows = policies.get(extensionPoint);
        if (existingFlows == null || existingFlows.isEmpty()) {
            return;
        }
        existingFlows.removeIf(executionFlow -> flowId.equals(executionFlow.getFlowId()));
    }

    private List getExecutionPolicies(Set executionFlows,
                                              Client client,
                                              boolean excludeApps,
                                              ExecutionPredicate predicate) {
        return executionFlows.stream()
                .filter(executionFlow -> excludeApps(client, excludeApps, executionFlow))
                .filter(executionFlow -> predicate.evaluate(executionFlow.getCondition()))
                .map(ExecutionFlow::getPolicies)
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .collect(toList());
    }

    private boolean excludeApps(Client client, boolean excludeApps, ExecutionFlow executionFlow) {
        return excludeApps ? isNull(executionFlow.getApplication()) : client.getId().equals(executionFlow.getApplication());
    }

    /**
     * @param flow
     * @return true if the Flow has never been deployed or if the deployed version is not up to date
     */
    private boolean needDeployment(Flow flow) {
        if (flow != null && flow.getId() != null) {
            final Flow deployedFlow = this.flows.get(flow.getId());
            return (deployedFlow == null || deployedFlow.getUpdatedAt().before(flow.getUpdatedAt()));
        } else {
            return false;
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy