Repository: gravitee-io/graviteeio-access-management
Pull request #974 (2020-11-27 10:50:12)

feat(policy-studio): technical integration for both gateway and management API
closes gravitee-io/issues#4593


Patch:
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
index 5a1d72f2cc..e40baefb00 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
@@ -42,4 +42,5 @@ public interface EntityType {
     String ORGANIZATION = "ORGANIZATION";
     String ENVIRONMENT = "ENVIRONMENT";
     String ENTRYPOINT = "ENTRYPOINT";
+    String FLOW = "FLOW";
 }
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
index c00ad63625..d08e6d7362 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
@@ -225,12 +225,21 @@ public interface EventType {
     String ENTRYPOINT_DELETED = "ENTRYPOINT_DELETED";
 
 
+    /**
+     * ----------
+     * Flow audit log actions
+     * ----------
+     */
+    String FLOW_CREATED = "FLOW_CREATED";
+    String FLOW_UPDATED = "FLOW_UPDATED";
+    String FLOW_DELETED = "FLOW_DELETED";
+
     static Collection<String> types() {
         return new TreeSet(Arrays.asList(
                 APPLICATION_CREATED, APPLICATION_UPDATED, APPLICATION_DELETED, APPLICATION_CLIENT_SECRET_RENEWED,
                 USER_LOGIN, USER_LOGOUT, USER_CREATED, USER_UPDATED, USER_DELETED, USER_LOCKED, USER_UNLOCKED, USER_ENABLED, USER_DISABLED, USER_PASSWORD_RESET, USER_REGISTERED, USER_CONSENT_CONSENTED, USER_CONSENT_REVOKED, USER_ROLES_ASSIGNED,
                 REGISTRATION_CONFIRMATION, REGISTRATION_CONFIRMATION_REQUESTED, REGISTRATION_CONFIRMATION_EMAIL_SENT,FORGOT_PASSWORD_REQUESTED,
-               FORGOT_PASSWORD_EMAIL_SENT, DOMAIN_CREATED, DOMAIN_UPDATED, DOMAIN_DELETED,
+                FORGOT_PASSWORD_EMAIL_SENT, DOMAIN_CREATED, DOMAIN_UPDATED, DOMAIN_DELETED,
                 CLIENT_CREATED, CLIENT_UPDATED, CLIENT_DELETED, CLIENT_SECRET_RENEWED,
                 CERTIFICATE_CREATED, CERTIFICATE_UPDATED, CERTIFICATE_DELETED,
                 EMAIL_TEMPLATE_CREATED, EMAIL_TEMPLATE_UPDATED, EMAIL_TEMPLATE_DELETED,
@@ -247,7 +256,8 @@ public interface EventType {
                 FACTOR_CREATED, FACTOR_UPDATED, FACTOR_DELETED,
                 ORGANIZATION_CREATED, ORGANIZATION_UPDATED,
                 ENVIRONMENT_CREATED, ENVIRONMENT_UPDATED,
-                ENTRYPOINT_CREATED, ENTRYPOINT_UPDATED, ENTRYPOINT_DELETED
+                ENTRYPOINT_CREATED, ENTRYPOINT_UPDATED, ENTRYPOINT_DELETED,
+                FLOW_CREATED, FLOW_UPDATED, FLOW_DELETED
         ));
     }
 }
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Event.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Event.java
index 383f570945..c65e2285d7 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Event.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Event.java
@@ -69,6 +69,9 @@ public abstract class Event {
             case FACTOR:
                 event1 =  FactorEvent.actionOf(action);
                 break;
+            case FLOW:
+                event1 =  FlowEvent.actionOf(action);
+                break;
         }
 
         return event1;
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/event/FlowEvent.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/FlowEvent.java
new file mode 100644
index 0000000000..e19fb8442c
--- /dev/null
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/FlowEvent.java
@@ -0,0 +1,46 @@
+/**
+ * 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.common.event;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public enum FlowEvent {
+
+    DEPLOY,
+    UPDATE,
+    UNDEPLOY;
+
+    public static FlowEvent actionOf(Action action) {
+        FlowEvent flowEvent = null;
+        switch (action) {
+            case CREATE:
+                flowEvent = FlowEvent.DEPLOY;
+                break;
+            case UPDATE:
+                flowEvent = FlowEvent.UPDATE;
+                break;
+            case DELETE:
+                flowEvent = FlowEvent.UNDEPLOY;
+                break;
+            case BULK_UPDATE:
+                flowEvent = FlowEvent.UPDATE;
+                break;
+        }
+        return flowEvent;
+    }
+}
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Type.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Type.java
index ba9f0a2d5e..6765b6ff7e 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Type.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/event/Type.java
@@ -36,5 +36,6 @@ public enum Type {
     USER,
     MEMBERSHIP,
     GROUP,
-    FACTOR
+    FACTOR,
+    FLOW
 }
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/policy/ExtensionPoint.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/policy/ExtensionPoint.java
index 9766fcc3ed..81b612c4dc 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/policy/ExtensionPoint.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/policy/ExtensionPoint.java
@@ -24,6 +24,10 @@ package io.gravitee.am.common.policy;
 public enum ExtensionPoint {
 
     ROOT,
+    PRE_LOGIN,
+    POST_LOGIN,
     PRE_CONSENT,
-    POST_CONSENT
+    POST_CONSENT,
+    PRE_REGISTER,
+    POST_REGISTER
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/FlowManager.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/FlowManager.java
new file mode 100644
index 0000000000..4cca6285a2
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/FlowManager.java
@@ -0,0 +1,32 @@
+/**
+ * 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;
+
+import io.gravitee.am.common.policy.ExtensionPoint;
+import io.gravitee.am.gateway.policy.Policy;
+import io.gravitee.common.service.Service;
+import io.reactivex.Single;
+
+import java.util.List;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface FlowManager extends Service {
+
+    Single<List<Policy>> findByExtensionPoint(ExtensionPoint extensionPoint);
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/impl/FlowManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/impl/FlowManagerImpl.java
new file mode 100644
index 0000000000..01ed81aae8
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/flow/impl/FlowManagerImpl.java
@@ -0,0 +1,217 @@
+/**
+ * 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.FlowManager;
+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.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.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.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowManagerImpl extends AbstractService implements FlowManager, InitializingBean, EventListener<FlowEvent, Payload> {
+
+    private static final Logger logger = LoggerFactory.getLogger(FlowManagerImpl.class);
+    private static final Map<Type, List<ExtensionPoint>> extensionPoints;
+    static {
+        Map<Type, List<ExtensionPoint>> aMap = new HashMap<>();
+        aMap.put(Type.ROOT, Arrays.asList(ExtensionPoint.ROOT));
+        aMap.put(Type.CONSENT, Arrays.asList(ExtensionPoint.PRE_CONSENT, ExtensionPoint.POST_CONSENT));
+        aMap.put(Type.LOGIN, Arrays.asList(ExtensionPoint.PRE_LOGIN, ExtensionPoint.POST_LOGIN));
+        aMap.put(Type.REGISTER, Arrays.asList(ExtensionPoint.PRE_REGISTER, ExtensionPoint.POST_REGISTER));
+        extensionPoints = Collections.unmodifiableMap(aMap);
+    }
+
+    @Autowired
+    private Domain domain;
+
+    @Autowired
+    private FlowService flowService;
+
+    @Autowired
+    private PolicyPluginManager policyPluginManager;
+
+    @Autowired
+    private EventManager eventManager;
+
+    private ConcurrentMap<String, Flow> flows = new ConcurrentHashMap<>();
+    private ConcurrentMap<ExtensionPoint, List<Policy>> 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<FlowEvent, Payload> event) {
+        if (event.content().getReferenceType() == ReferenceType.DOMAIN &&
+                domain.getId().equals(event.content().getReferenceId())) {
+            switch (event.type()) {
+                case DEPLOY:
+                case UPDATE:
+                    String flowId = event.content().getId();
+                    if (flowId != null) {
+                        updateFlow(flowId, event.type());
+                    } else {
+                        logger.info("Domain {} has received bulk_update flows event", domain.getName());
+                        loadFlows();
+                    }
+                    break;
+                case UNDEPLOY:
+                    removeFlow(event.content().getId());
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public Single<List<Policy>> findByExtensionPoint(ExtensionPoint extensionPoint) {
+        return Single.just(policies.get(extensionPoint));
+    }
+
+    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 -> {
+                            flows.put(flow.getId(), flow);
+                            loadFlow(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);
+        extensionPoints.get(deletedFlow.getType()).forEach(extensionPoint -> {
+            policies.remove(extensionPoint);
+            flows.remove(flowId);
+        });
+    }
+
+    private void loadFlows() {
+        flowService.findAll(ReferenceType.DOMAIN, domain.getId())
+                .subscribe(
+                        flows1 -> {
+                            flows1.forEach(flow -> {
+                                flows.put(flow.getId(), flow);
+                                loadFlow(flow);
+                            });
+                            logger.info("Flows loaded for domain {}", 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 -> policies.put(extensionPoint, Collections.emptyList()));
+            return;
+        }
+
+        // load policies
+        List<Policy> prePolicies = flow.getPre()
+                .stream()
+                .filter(Step::isEnabled)
+                .map(this::createPolicy)
+                .filter(policy -> policy != null)
+                .collect(Collectors.toList());
+        List<Policy> postPolicies = flow.getPost()
+                .stream()
+                .filter(Step::isEnabled)
+                .map(this::createPolicy)
+                .filter(policy -> policy != null)
+                .collect(Collectors.toList());
+
+        switch (flow.getType()) {
+            case ROOT:
+                // for root type, fetch only the pre step policies
+                policies.put(ExtensionPoint.ROOT, prePolicies);
+                break;
+            case CONSENT:
+                policies.put(ExtensionPoint.PRE_CONSENT, prePolicies);
+                policies.put(ExtensionPoint.POST_CONSENT, postPolicies);
+                break;
+            case LOGIN:
+                policies.put(ExtensionPoint.PRE_LOGIN, prePolicies);
+                policies.put(ExtensionPoint.POST_LOGIN, postPolicies);
+                break;
+            case REGISTER:
+                policies.put(ExtensionPoint.PRE_REGISTER, prePolicies);
+                policies.put(ExtensionPoint.POST_REGISTER, postPolicies);
+                break;
+            default:
+                throw new IllegalArgumentException("No suitable flow type found for : " + flow.getType());
+        }
+    }
+
+    private Policy createPolicy(Step step) {
+        try {
+            logger.info("\tInitializing policy: {} [{}]", step.getName(), step.getPolicy());
+            Policy policy = policyPluginManager.create(step.getPolicy(), step.getConfiguration());
+            logger.info("\tPolicy : {} [{}] has been loaded", step.getName(), step.getPolicy());
+            return policy;
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+}
+
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java
index b7ef2c1969..e65210d9f9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/spring/CommonConfiguration.java
@@ -32,6 +32,8 @@ import io.gravitee.am.gateway.handler.common.email.EmailManager;
 import io.gravitee.am.gateway.handler.common.email.EmailService;
 import io.gravitee.am.gateway.handler.common.email.impl.EmailManagerImpl;
 import io.gravitee.am.gateway.handler.common.email.impl.EmailServiceImpl;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
+import io.gravitee.am.gateway.handler.common.flow.impl.FlowManagerImpl;
 import io.gravitee.am.gateway.handler.common.jwt.JWTService;
 import io.gravitee.am.gateway.handler.common.jwt.impl.JWTServiceImpl;
 import io.gravitee.am.gateway.handler.common.oauth2.IntrospectionTokenService;
@@ -143,6 +145,11 @@ public class CommonConfiguration {
         return new PolicyManagerImpl();
     }
 
+    @Bean
+    public FlowManager flowManager() {
+        return new FlowManagerImpl();
+    }
+
     @Bean
     public ExecutionContextFactory executionContextFactory() {
         return new ExecutionContextFactory();
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/PolicyChainHandlerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/PolicyChainHandlerImpl.java
index 23f7c0b665..6915f14658 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/PolicyChainHandlerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/PolicyChainHandlerImpl.java
@@ -16,7 +16,7 @@
 package io.gravitee.am.gateway.handler.common.vertx.web.handler.impl;
 
 import io.gravitee.am.common.policy.ExtensionPoint;
-import io.gravitee.am.gateway.handler.common.policy.PolicyManager;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
 import io.gravitee.am.gateway.handler.common.vertx.web.handler.PolicyChainHandler;
 import io.gravitee.am.gateway.handler.context.ExecutionContextFactory;
 import io.gravitee.am.gateway.policy.PolicyChainProcessorFactory;
@@ -33,7 +33,7 @@ import java.util.Objects;
 public class PolicyChainHandlerImpl implements PolicyChainHandler {
 
     @Autowired
-    private PolicyManager policyManager;
+    private FlowManager flowManager;
 
     @Autowired
     private PolicyChainProcessorFactory policyChainProcessorFactory;
@@ -44,6 +44,6 @@ public class PolicyChainHandlerImpl implements PolicyChainHandler {
     @Override
     public Handler<RoutingContext> create(ExtensionPoint extensionPoint) {
         Objects.requireNonNull(extensionPoint, "An extension point is required");
-        return new io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.internal.PolicyChainHandlerImpl(policyManager, policyChainProcessorFactory, executionContextFactory, extensionPoint);
+        return new io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.internal.PolicyChainHandlerImpl(flowManager, policyChainProcessorFactory, executionContextFactory, extensionPoint);
     }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/internal/PolicyChainHandlerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/internal/PolicyChainHandlerImpl.java
index 0dad226ea2..e99be7cf24 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/internal/PolicyChainHandlerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/main/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/impl/internal/PolicyChainHandlerImpl.java
@@ -16,7 +16,7 @@
 package io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.internal;
 
 import io.gravitee.am.common.policy.ExtensionPoint;
-import io.gravitee.am.gateway.handler.common.policy.PolicyManager;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
 import io.gravitee.am.gateway.handler.common.vertx.core.http.VertxHttpServerRequest;
 import io.gravitee.am.gateway.handler.common.vertx.core.http.VertxHttpServerResponse;
 import io.gravitee.am.gateway.handler.context.ExecutionContextFactory;
@@ -48,16 +48,16 @@ public class PolicyChainHandlerImpl implements Handler<RoutingContext> {
 
     private static final Logger logger = LoggerFactory.getLogger(PolicyChainHandlerImpl.class);
     private static final List<String> BLACKLIST_CONTEXT_ATTRIBUTES = Arrays.asList("X-XSRF-TOKEN", "_csrf", "__body-handled");
-    private PolicyManager policyManager;
+    private FlowManager flowManager;
     private PolicyChainProcessorFactory policyChainProcessorFactory;
     private ExecutionContextFactory executionContextFactory;
     private ExtensionPoint extensionPoint;
 
-    public PolicyChainHandlerImpl(PolicyManager policyManager,
+    public PolicyChainHandlerImpl(FlowManager flowManager,
                                   PolicyChainProcessorFactory policyChainProcessorFactory,
                                   ExecutionContextFactory executionContextFactory,
                                   ExtensionPoint extensionPoint) {
-        this.policyManager = policyManager;
+        this.flowManager = flowManager;
         this.policyChainProcessorFactory = policyChainProcessorFactory;
         this.executionContextFactory = executionContextFactory;
         this.extensionPoint = extensionPoint;
@@ -106,7 +106,7 @@ public class PolicyChainHandlerImpl implements Handler<RoutingContext> {
     }
 
     private void resolve(ExtensionPoint extensionPoint, Handler<AsyncResult<List<Policy>>> handler) {
-        policyManager.findByExtensionPoint(extensionPoint)
+        flowManager.findByExtensionPoint(extensionPoint)
                 .subscribe(
                         policies -> handler.handle(Future.succeededFuture(policies)),
                         error -> handler.handle(Future.failedFuture(error)));
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/SecurityDomainRouterFactory.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/SecurityDomainRouterFactory.java
index 9cc541b684..bdd9b3000f 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/SecurityDomainRouterFactory.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/SecurityDomainRouterFactory.java
@@ -18,6 +18,7 @@ package io.gravitee.am.gateway.handler;
 import io.gravitee.am.gateway.handler.common.audit.AuditReporterManager;
 import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager;
 import io.gravitee.am.gateway.handler.common.auth.listener.AuthenticationEventListener;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
 import io.gravitee.am.gateway.handler.common.policy.PolicyManager;
 import io.gravitee.am.gateway.handler.common.user.UserManager;
 import io.gravitee.am.gateway.handler.common.email.EmailManager;
@@ -97,6 +98,7 @@ public class SecurityDomainRouterFactory {
         components.add(EmailManager.class);
         components.add(AuditReporterManager.class);
         components.add(PolicyManager.class);
+        components.add(FlowManager.class);
         components.add(AuthenticationEventListener.class);
         components.add(UserManager.class);
         components.add(FactorManager.class);
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
index 72c6580bb5..d27a8e5453 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/vertx/VertxSecurityDomainHandler.java
@@ -19,6 +19,7 @@ import io.gravitee.am.gateway.handler.api.ProtocolProvider;
 import io.gravitee.am.gateway.handler.common.audit.AuditReporterManager;
 import io.gravitee.am.gateway.handler.common.auth.idp.IdentityProviderManager;
 import io.gravitee.am.gateway.handler.common.auth.listener.AuthenticationEventListener;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
 import io.gravitee.am.gateway.handler.common.policy.PolicyManager;
 import io.gravitee.am.gateway.handler.common.user.UserManager;
 import io.gravitee.am.gateway.handler.common.email.EmailManager;
@@ -152,6 +153,7 @@ public class VertxSecurityDomainHandler extends AbstractService<VertxSecurityDom
         components.add(EmailManager.class);
         components.add(AuditReporterManager.class);
         components.add(PolicyManager.class);
+        components.add(FlowManager.class);
         components.add(AuthenticationEventListener.class);
         components.add(UserManager.class);
         components.add(FactorManager.class);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
index cd73f0757b..977c1902a7 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
@@ -259,6 +259,11 @@ public class DomainResource extends AbstractResource {
         return resourceContext.getResource(FactorsResource.class);
     }
 
+    @Path("flows")
+    public FlowsResource getFlowsResource() {
+        return resourceContext.getResource(FlowsResource.class);
+    }
+
     private void updateInternal(String organizationId, String environmentId, String domainId, final PatchDomain patchDomain, final AsyncResponse response) {
 
         final User authenticatedUser = getAuthenticatedUser();
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowResource.java
new file mode 100644
index 0000000000..eaa2171573
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowResource.java
@@ -0,0 +1,99 @@
+/**
+ * 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.management.handlers.management.api.resources.organizations.environments.domains;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
+import io.gravitee.am.model.Acl;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.service.FlowService;
+import io.gravitee.am.service.exception.FlowNotFoundException;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowResource extends AbstractResource {
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @Autowired
+    private FlowService flowService;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Get a flow",
+            notes = "User must have the DOMAIN_FLOW[READ] permission on the specified domain " +
+                    "or DOMAIN_FLOW[READ] permission on the specified environment " +
+                    "or DOMAIN_FLOW[READ] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Flow", response = Flow.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void get(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("environmentId") String environmentId,
+            @PathParam("domain") String domain,
+            @PathParam("flow") String flow,
+            @Suspended final AsyncResponse response) {
+
+        checkAnyPermission(organizationId, environmentId, domain, Permission.DOMAIN_FLOW, Acl.READ)
+                .andThen(flowService.findById(ReferenceType.DOMAIN, domain, flow))
+                        .switchIfEmpty(Maybe.error(new FlowNotFoundException(flow)))
+                .subscribe(response::resume, response::resume);
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Update a flow",
+            notes = "User must have the DOMAIN_FLOW[UPDATE] permission on the specified domain " +
+                    "or DOMAIN_FLOW[UPDATE] permission on the specified environment " +
+                    "or DOMAIN_FLOW[UPDATE] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "Flow successfully updated", response = Flow.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void update(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("environmentId") String environmentId,
+            @PathParam("domain") String domain,
+            @PathParam("flow") String flow,
+            @ApiParam(name = "flow", required = true) @Valid @NotNull Flow updateFlow,
+            @Suspended final AsyncResponse response) {
+        final User authenticatedUser = getAuthenticatedUser();
+
+        checkAnyPermission(organizationId, environmentId, domain, Permission.DOMAIN_FLOW, Acl.UPDATE)
+                .andThen(flowService.update(ReferenceType.DOMAIN, domain, flow, updateFlow, authenticatedUser))
+                .subscribe(response::resume, response::resume);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowsResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowsResource.java
new file mode 100644
index 0000000000..a5181b43b9
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/FlowsResource.java
@@ -0,0 +1,129 @@
+/**
+ * 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.management.handlers.management.api.resources.organizations.environments.domains;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
+import io.gravitee.am.model.Acl;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.FlowService;
+import io.gravitee.am.service.exception.DomainNotFoundException;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+import io.swagger.annotations.*;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Api(tags = {"flow"})
+public class FlowsResource extends AbstractResource {
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @Autowired
+    private DomainService domainService;
+
+    @Autowired
+    private FlowService flowService;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "List registered flows for a security domain",
+            notes = "User must have the DOMAIN_FLOW[LIST] permission on the specified domain " +
+                    "or DOMAIN_FLOW[LIST] permission on the specified environment " +
+                    "or DOMAIN_FLOW[LIST] permission on the specified organization. " +
+                    "Except if user has DOMAIN_FLOW[READ] permission on the domain, environment or organization, each returned flow is filtered and contains only basic information such as id and name and isEnabled.")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "List registered flows for a security domain", response = Flow.class, responseContainer = "List"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void list(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("environmentId") String environmentId,
+            @PathParam("domain") String domain,
+            @Suspended final AsyncResponse response) {
+
+        User authenticatedUser = getAuthenticatedUser();
+
+        checkAnyPermission(organizationId, environmentId, domain, Permission.DOMAIN_FLOW, Acl.LIST)
+                .andThen(flowService.findAll(ReferenceType.DOMAIN, domain)
+                        .flatMap(flows ->
+                                hasAnyPermission(authenticatedUser, organizationId, environmentId, domain, Permission.DOMAIN_FLOW, Acl.READ)
+                                        .map(hasPermission -> flows.stream().map(flow -> filterFlowInfos(hasPermission, flow))
+                                                .collect(Collectors.toList())))
+                )
+                .subscribe(response::resume, response::resume);
+    }
+
+    @PUT
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Update flows",
+            notes = "User must have the DOMAIN_FLOW[UPDATE] permission on the specified domain " +
+                    "or DOMAIN_FLOW[UPDATE] permission on the specified environment " +
+                    "or DOMAIN_FLOW[UPDATE] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "Flows successfully updated"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void update(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("environmentId") String environmentId,
+            @PathParam("domain") String domain,
+            @ApiParam(name = "flows", required = true) @Valid @NotNull final List<Flow> flows,
+            @Suspended final AsyncResponse response) {
+
+        final User authenticatedUser = getAuthenticatedUser();
+
+        checkAnyPermission(organizationId, environmentId, domain, Permission.DOMAIN_EXTENSION_POINT, Acl.UPDATE)
+                .andThen(domainService.findById(domain)
+                        .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                        .flatMapSingle(__ -> flowService.update(ReferenceType.DOMAIN, domain, flows, authenticatedUser)))
+                .subscribe(response::resume, response::resume);
+    }
+
+    @Path("{flow}")
+    public FlowResource getFlowResource() {
+        return resourceContext.getResource(FlowResource.class);
+    }
+
+    private Flow filterFlowInfos(Boolean hasPermission, Flow flow) {
+        if (hasPermission) {
+            return flow;
+        }
+
+        Flow filteredFlow = new Flow();
+        filteredFlow.setId(flow.getId());
+        filteredFlow.setName(flow.getName());
+        filteredFlow.setEnabled(flow.isEnabled());
+
+        return filteredFlow;
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
index 0be589ba9d..d7da0671d4 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
@@ -16,9 +16,9 @@
 package io.gravitee.am.management.handlers.management.api.resources.platform;
 
 import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.management.handlers.management.api.resources.platform.configuration.ConfigurationResource;
 import io.gravitee.am.management.handlers.management.api.resources.platform.plugins.PluginsResource;
 import io.gravitee.am.management.handlers.management.api.resources.platform.roles.SystemRoleResource;
-import io.reactivex.Single;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 
@@ -48,7 +48,6 @@ public class PlatformResource {
     @ApiOperation(value = "List audit event types",
             notes = "There is no particular permission needed. User must be authenticated.")
     public void list(@Suspended final AsyncResponse response) {
-
         response.resume(EventType.types());
     }
 
@@ -61,4 +60,9 @@ public class PlatformResource {
     public SystemRoleResource getSystemRoleResource() {
         return resourceContext.getResource(SystemRoleResource.class);
     }
-}
\ No newline at end of file
+
+    @Path("configuration")
+    public ConfigurationResource getConfigurationResource() {
+        return resourceContext.getResource(ConfigurationResource.class);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/configuration/ConfigurationResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/configuration/ConfigurationResource.java
new file mode 100644
index 0000000000..e7637e253d
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/configuration/ConfigurationResource.java
@@ -0,0 +1,47 @@
+/**
+ * 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.management.handlers.management.api.resources.platform.configuration;
+
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.service.FlowService;
+import io.swagger.annotations.ApiOperation;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class ConfigurationResource {
+
+    @Inject
+    private FlowService flowService;
+
+    @GET
+    @Path("/flow/schema")
+    @Produces(javax.ws.rs.core.MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Get the Policy Studio flow schema",
+            notes = "There is no particular permission needed. User must be authenticated.")
+    public void list(@Suspended final AsyncResponse response) {
+        flowService.getSchema()
+                .subscribe(response::resume, response::resume);
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Flow.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Flow.java
new file mode 100644
index 0000000000..07c268dada
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Flow.java
@@ -0,0 +1,189 @@
+/**
+ * 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.model.flow;
+
+import io.gravitee.am.model.ReferenceType;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class Flow {
+
+    /**
+     * Flow technical id
+     */
+    private String id;
+    /**
+     * The type of reference the flow is attached to (for now, should be DOMAIN).
+     */
+    private ReferenceType referenceType;
+    /**
+     * The id of the reference the flow is attached to (for now, should be the domain id).
+     */
+    private String referenceId;
+    /**
+     * Flow name
+     */
+    private String name;
+    /**
+     * Flow pre steps
+     */
+    private List<Step> pre = new ArrayList<>();
+    /**
+     * Flow post steps
+     */
+    private List<Step> post = new ArrayList<>();
+    /**
+     * Flow state
+     */
+    private boolean enabled;
+    /**
+     * The type of flow
+     */
+    private Type type;
+    /**
+     * Condition attached to the Flow
+     */
+    private String condition;
+    /**
+     * Flow created date
+     */
+    private Date createdAt;
+    /**
+     * Flow updated date
+     */
+    private Date updatedAt;
+
+    public Flow() {
+    }
+
+    public Flow(Flow other) {
+        this.id = other.id;
+        this.referenceType = other.referenceType;
+        this.referenceId = other.referenceId;
+        this.name = other.name;
+        this.pre = other.pre;
+        this.post = other.post;
+        this.enabled = other.enabled;
+        this.type = other.type;
+        this.condition = other.condition;
+        this.createdAt = other.createdAt;
+        this.updatedAt = other.updatedAt;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public ReferenceType getReferenceType() {
+        return referenceType;
+    }
+
+    public void setReferenceType(ReferenceType referenceType) {
+        this.referenceType = referenceType;
+    }
+
+    public String getReferenceId() {
+        return referenceId;
+    }
+
+    public void setReferenceId(String referenceId) {
+        this.referenceId = referenceId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Step> getPre() {
+        return pre;
+    }
+
+    public void setPre(List<Step> pre) {
+        this.pre = pre;
+    }
+
+    public List<Step> getPost() {
+        return post;
+    }
+
+    public void setPost(List<Step> post) {
+        this.post = post;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public String getCondition() {
+        return condition;
+    }
+
+    public void setCondition(String condition) {
+        this.condition = condition;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    @Override
+    public String toString() {
+        return "{\"_class\":\"Flow\", " +
+                "\"id\":" + (id == null ? "null" : "\"" + id + "\"") + ", " +
+                "\"referenceType\":" + (referenceType == null ? "null" : referenceType) + ", " +
+                "\"referenceId\":" + (referenceId == null ? "null" : "\"" + referenceId + "\"") + ", " +
+                "\"name\":" + (name == null ? "null" : "\"" + name + "\"") +
+                "}";
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Step.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Step.java
new file mode 100644
index 0000000000..0957117f3f
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Step.java
@@ -0,0 +1,84 @@
+/**
+ * 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.model.flow;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class Step {
+
+    /**
+     * Step name
+     */
+    private String name;
+    /**
+     * Step policy
+     */
+    private String policy;
+    /**
+     * Step description
+     */
+    private String description;
+    /**
+     * Step policy configuration
+     */
+    private String configuration;
+    /**
+     * Step state
+     */
+    private boolean enabled = true;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPolicy() {
+        return policy;
+    }
+
+    public void setPolicy(String policy) {
+        this.policy = policy;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(String configuration) {
+        this.configuration = configuration;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Type.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Type.java
new file mode 100644
index 0000000000..7af16d503d
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/flow/Type.java
@@ -0,0 +1,27 @@
+/**
+ * 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.model.flow;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public enum Type {
+    ROOT,
+    LOGIN,
+    CONSENT,
+    REGISTER
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
index d609c3ffcd..7363316a94 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
@@ -66,6 +66,7 @@ public enum Permission {
     DOMAIN_MEMBER(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN),
     DOMAIN_ANALYTICS(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN),
     DOMAIN_FACTOR(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN),
+    DOMAIN_FLOW(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN),
 
     APPLICATION(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
     APPLICATION_SETTINGS(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FlowRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FlowRepository.java
new file mode 100644
index 0000000000..39bc3e1cf7
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/FlowRepository.java
@@ -0,0 +1,35 @@
+/**
+ * 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.repository.management.api;
+
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.repository.common.CrudRepository;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+
+import java.util.List;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface FlowRepository extends CrudRepository<Flow, String> {
+
+    Maybe<Flow> findById(ReferenceType referenceType, String referenceId, String id);
+
+    Single<List<Flow>> findAll(ReferenceType referenceType, String referenceId);
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepository.java
new file mode 100644
index 0000000000..ab16a88b27
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepository.java
@@ -0,0 +1,138 @@
+/**
+ * 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.repository.mongodb.management;
+
+import com.mongodb.reactivestreams.client.MongoCollection;
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.model.flow.Type;
+import io.gravitee.am.repository.management.api.FlowRepository;
+import io.gravitee.am.repository.mongodb.management.internal.model.FlowMongo;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.bson.Document;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class MongoFlowRepository extends AbstractManagementMongoRepository implements FlowRepository {
+
+    private MongoCollection<FlowMongo> flowsCollection;
+
+    @PostConstruct
+    public void init() {
+        flowsCollection = mongoOperations.getCollection("flows", FlowMongo.class);
+        super.init(flowsCollection);
+        super.createIndex(flowsCollection, new Document(FIELD_REFERENCE_TYPE, 1).append(FIELD_REFERENCE_ID, 1));
+        super.createIndex(flowsCollection, new Document(FIELD_REFERENCE_TYPE, 1).append(FIELD_REFERENCE_ID, 1).append(FIELD_ID, 1));
+    }
+
+    @Override
+    public Single<List<Flow>> findAll(ReferenceType referenceType, String referenceId) {
+        return Observable.fromPublisher(flowsCollection.find(and(eq(FIELD_REFERENCE_TYPE, referenceType.name()), eq(FIELD_REFERENCE_ID, referenceId))))
+                .map(this::convert).collect(ArrayList::new, List::add);
+    }
+
+    @Override
+    public Maybe<Flow> findById(ReferenceType referenceType, String referenceId, String id) {
+        return Observable.fromPublisher(
+                flowsCollection.find(
+                        and(
+                                eq(FIELD_REFERENCE_TYPE, referenceType.name()),
+                                eq(FIELD_REFERENCE_ID, referenceId),
+                                eq(FIELD_ID, id)
+                        )
+                ).first()).firstElement().map(this::convert);
+    }
+
+    @Override
+    public Maybe<Flow> findById(String id) {
+        return Observable.fromPublisher(flowsCollection.find(eq(FIELD_ID, id)).first()).firstElement().map(this::convert);
+    }
+
+    @Override
+    public Single<Flow> create(Flow item) {
+        FlowMongo flow = convert(item);
+        flow.setId(flow.getId() == null ? RandomString.generate() : flow.getId());
+        return Single.fromPublisher(flowsCollection.insertOne(flow)).flatMap(success -> findById(flow.getId()).toSingle());
+    }
+
+    @Override
+    public Single<Flow> update(Flow item) {
+        FlowMongo flow = convert(item);
+        return Single.fromPublisher(flowsCollection.replaceOne(eq(FIELD_ID, flow.getId()), flow)).flatMap(updateResult -> findById(flow.getId()).toSingle());
+    }
+
+    @Override
+    public Completable delete(String id) {
+        return Completable.fromPublisher(flowsCollection.deleteOne(eq(FIELD_ID, id)));
+    }
+
+    private FlowMongo convert(Flow flow) {
+        if (flow == null) {
+            return null;
+        }
+
+        FlowMongo flowMongo = new FlowMongo();
+        flowMongo.setId(flow.getId());
+        flowMongo.setReferenceType(flow.getReferenceType().toString());
+        flowMongo.setReferenceId(flow.getReferenceId());
+        flowMongo.setName(flow.getName());
+        flowMongo.setPre(flow.getPre());
+        flowMongo.setPost(flow.getPost());
+        flowMongo.setEnabled(flow.isEnabled());
+        flowMongo.setType(flow.getType().toString());
+        flowMongo.setCondition(flow.getCondition());
+        flowMongo.setCreatedAt(flow.getCreatedAt());
+        flowMongo.setUpdatedAt(flow.getUpdatedAt());
+        return flowMongo;
+    }
+
+    private Flow convert(FlowMongo flowMongo) {
+        if (flowMongo == null) {
+            return null;
+        }
+
+        Flow flow = new Flow();
+        flow.setId(flowMongo.getId());
+        flow.setReferenceType(ReferenceType.valueOf(flowMongo.getReferenceType()));
+        flow.setReferenceId(flowMongo.getReferenceId());
+        flow.setName(flowMongo.getName());
+        flow.setPre(flowMongo.getPre());
+        flow.setPost(flowMongo.getPost());
+        flow.setEnabled(flowMongo.isEnabled());
+        flow.setType(Type.valueOf(flowMongo.getType()));
+        flow.setCondition(flowMongo.getCondition());
+        flow.setCreatedAt(flowMongo.getCreatedAt());
+        flow.setUpdatedAt(flowMongo.getUpdatedAt());
+
+        return flow;
+    }
+
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/FlowMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/FlowMongo.java
new file mode 100644
index 0000000000..3254ceda4c
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/FlowMongo.java
@@ -0,0 +1,113 @@
+/**
+ * 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.repository.mongodb.management.internal.model;
+
+import io.gravitee.am.model.flow.Step;
+import io.gravitee.am.repository.mongodb.common.model.Auditable;
+import org.bson.codecs.pojo.annotations.BsonId;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowMongo extends Auditable {
+
+    @BsonId
+    private String id;
+    private String referenceType;
+    private String referenceId;
+    private String name;
+    private List<Step> pre = new ArrayList<>();
+    private List<Step> post = new ArrayList<>();
+    private boolean enabled;
+    private String type;
+    private String condition;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getReferenceType() {
+        return referenceType;
+    }
+
+    public void setReferenceType(String referenceType) {
+        this.referenceType = referenceType;
+    }
+
+    public String getReferenceId() {
+        return referenceId;
+    }
+
+    public void setReferenceId(String referenceId) {
+        this.referenceId = referenceId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Step> getPre() {
+        return pre;
+    }
+
+    public void setPre(List<Step> pre) {
+        this.pre = pre;
+    }
+
+    public List<Step> getPost() {
+        return post;
+    }
+
+    public void setPost(List<Step> post) {
+        this.post = post;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getCondition() {
+        return condition;
+    }
+
+    public void setCondition(String condition) {
+        this.condition = condition;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/FlowService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/FlowService.java
new file mode 100644
index 0000000000..5d43cfab71
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/FlowService.java
@@ -0,0 +1,64 @@
+/**
+ * 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.service;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+
+import java.util.List;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface FlowService {
+
+    Single<List<Flow>> findAll(ReferenceType referenceType, String referenceId);
+
+    Maybe<Flow> findById(ReferenceType referenceType, String referenceId, String id);
+
+    Maybe<Flow> findById(String id);
+
+    Single<Flow> create(ReferenceType referenceType, String referenceId, Flow flow, User principal);
+
+    Single<Flow> update(ReferenceType referenceType, String referenceId, String id, Flow flow, User principal);
+
+    Single<List<Flow>> update(ReferenceType referenceType, String referenceId, List<Flow> flows, User principal);
+
+    Completable delete(String id, User principal);
+
+    Single<String> getSchema();
+
+    default Single<Flow> create(ReferenceType referenceType, String referenceId, Flow flow) {
+        return create(referenceType, referenceId, flow, null);
+    }
+
+    default Single<Flow> update(ReferenceType referenceType, String referenceId, String id, Flow flow) {
+        return update(referenceType, referenceId, id, flow, null);
+    }
+
+    default Single<List<Flow>> update(ReferenceType referenceType, String referenceId, List<Flow> flows) {
+        return update(referenceType, referenceId, flows, null);
+    }
+
+    default Completable delete(String id) {
+        return delete(id, null);
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/ApplicationNotFoundException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/ApplicationNotFoundException.java
index 7088a4b9ee..927d7e99a7 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/ApplicationNotFoundException.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/ApplicationNotFoundException.java
@@ -21,14 +21,14 @@ package io.gravitee.am.service.exception;
  */
 public class ApplicationNotFoundException extends AbstractNotFoundException {
 
-    private final String application;
+    private final String flow;
 
-    public ApplicationNotFoundException(String application) {
-        this.application = application;
+    public ApplicationNotFoundException(String flow) {
+        this.flow = flow;
     }
 
     @Override
     public String getMessage() {
-        return "Application [" + application + "] can not be found.";
+        return "Flow [" + flow + "] can not be found.";
     }
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/FlowNotFoundException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/FlowNotFoundException.java
new file mode 100644
index 0000000000..d4c831c9b4
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/FlowNotFoundException.java
@@ -0,0 +1,34 @@
+/**
+ * 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.service.exception;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowNotFoundException extends AbstractNotFoundException {
+
+    private final String application;
+
+    public FlowNotFoundException(String application) {
+        this.application = application;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Application [" + application + "] can not be found.";
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FlowServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FlowServiceImpl.java
new file mode 100644
index 0000000000..84f3161a63
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/FlowServiceImpl.java
@@ -0,0 +1,229 @@
+/**
+ * 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.service.impl;
+
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.common.event.Action;
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.model.Policy;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.common.event.Event;
+import io.gravitee.am.model.common.event.Payload;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.model.flow.Type;
+import io.gravitee.am.repository.management.api.FlowRepository;
+import io.gravitee.am.service.AuditService;
+import io.gravitee.am.service.EventService;
+import io.gravitee.am.service.FlowService;
+import io.gravitee.am.service.exception.AbstractManagementException;
+import io.gravitee.am.service.exception.FlowNotFoundException;
+import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.reporter.builder.AuditBuilder;
+import io.gravitee.am.service.reporter.builder.management.FlowAuditBuilder;
+import io.micrometer.core.instrument.util.IOUtils;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.nio.charset.Charset.defaultCharset;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class FlowServiceImpl implements FlowService {
+
+    private final Logger LOGGER = LoggerFactory.getLogger(FlowServiceImpl.class);
+    private static final String DEFINITION_PATH = "/flow/am-schema.json";
+
+    @Lazy
+    @Autowired
+    private FlowRepository flowRepository;
+
+    @Autowired
+    private EventService eventService;
+
+    @Autowired
+    private AuditService auditService;
+
+    @Override
+    public Single<List<Flow>> findAll(ReferenceType referenceType, String referenceId) {
+        LOGGER.debug("Find all flows for {} {}", referenceType, referenceId);
+        return flowRepository.findAll(referenceType, referenceId)
+                .map(flows -> {
+                    if (flows == null || flows.isEmpty()) {
+                        // return default flows
+                        Flow root = new Flow();
+                        root.setName("ROOT");
+                        root.setType(Type.ROOT);
+                        flows = Arrays.asList(root);
+                    }
+                    return flows;
+                })
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error has occurred while trying to find all flows for {} {}", referenceType, referenceId, ex);
+                    return Single.error(new TechnicalManagementException(String.format("An error has occurred while trying to find a all flows for %s %s", referenceType, referenceId), ex));
+                });
+    }
+
+    @Override
+    public Maybe<Flow> findById(ReferenceType referenceType, String referenceId, String id) {
+        LOGGER.debug("Find flow by referenceType {}, referenceId {} and id {}", referenceType, referenceId, id);
+        return flowRepository.findById(referenceType, referenceId, id)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error has occurred while trying to find a flow using its referenceType {}, referenceId {} and id {}", referenceType, referenceId, id, ex);
+                    return Maybe.error(new TechnicalManagementException(
+                            String.format("An error has occurred while trying to find a flow using its referenceType %s, referenceId %s and id %s", referenceType, referenceId, id), ex));
+                });
+    }
+
+    @Override
+    public Maybe<Flow> findById(String id) {
+        LOGGER.debug("Find flow by id {}", id);
+        return flowRepository.findById(id)
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error has occurred while trying to find a flow using its id {}", id, ex);
+                    return Maybe.error(new TechnicalManagementException(
+                            String.format("An error has occurred while trying to find a flow using its id %s", id), ex));
+                });
+    }
+
+    @Override
+    public Single<Flow> create(ReferenceType referenceType, String referenceId, Flow flow, User principal) {
+        LOGGER.debug("Create a new flow {} for referenceType {} and referenceId", referenceType, referenceId);
+
+        flow.setId(flow.getId() == null ? RandomString.generate() : flow.getId());
+        flow.setCreatedAt(new Date());
+        flow.setUpdatedAt(flow.getCreatedAt());
+
+        return flowRepository.create(flow)
+                .flatMap(flow1 -> {
+                    // create event for sync process
+                    Event event = new Event(io.gravitee.am.common.event.Type.FLOW, new Payload(flow1.getId(), referenceType, referenceId, Action.CREATE));
+                    return eventService.create(event).flatMap(__ -> Single.just(flow1));
+                })
+                .onErrorResumeNext(ex -> {
+                    LOGGER.error("An error has occurred while trying to create a flow", ex);
+                    return Single.error(new TechnicalManagementException("An error has occurred while trying to create a flow", ex));
+                })
+                .doOnSuccess(flow1 -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_CREATED).flow(flow1)))
+                .doOnError(throwable -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_CREATED).throwable(throwable)));
+    }
+
+    @Override
+    public Single<Flow> update(ReferenceType referenceType, String referenceId, String id, Flow flow, User principal) {
+        LOGGER.debug("Update a flow {} ", flow);
+
+        // update date
+        flow.setUpdatedAt(new Date());
+
+        return flowRepository.findById(referenceType, referenceId, id)
+                .switchIfEmpty(Maybe.error(new FlowNotFoundException(id)))
+                .flatMapSingle(oldFlow -> {
+                    Flow flowToUpdate = new Flow(flow);
+                    flowToUpdate.setUpdatedAt(new Date());
+                    return flowRepository.update(flowToUpdate)
+                            // create event for sync process
+                            .flatMap(flow1 -> {
+                                Event event = new Event(io.gravitee.am.common.event.Type.FLOW, new Payload(flow1.getId(), flow1.getReferenceType(), flow1.getReferenceId(), Action.UPDATE));
+                                return eventService.create(event).flatMap(__ -> Single.just(flow1));
+                            })
+                            .doOnSuccess(flow1 -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_UPDATED).oldValue(oldFlow).flow(flow1)))
+                            .doOnError(throwable -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_UPDATED).throwable(throwable)));
+
+                })
+                .onErrorResumeNext(ex -> {
+                    if (ex instanceof AbstractManagementException) {
+                        return Single.error(ex);
+                    }
+                    LOGGER.error("An error has occurred while trying to update a flow", ex);
+                    return Single.error(new TechnicalManagementException("An error has occurred while trying to update a flow", ex));
+                });
+    }
+
+    @Override
+    public Single<List<Flow>> update(ReferenceType referenceType, String referenceId, List<Flow> flows, User principal) {
+        LOGGER.debug("Update flows {} for domain {}", flows, referenceId);
+
+        return Observable.fromIterable(flows)
+                .flatMapSingle(flow -> flowRepository.update(flow))
+                .toList()
+                .flatMap(flows1 -> {
+                    // create event for sync process
+                    Event event = new Event(io.gravitee.am.common.event.Type.FLOW, new Payload(null, referenceType, referenceId, Action.BULK_UPDATE));
+                    return eventService.create(event).flatMap(__ -> Single.just(flows1));
+                })
+                .onErrorResumeNext(ex -> {
+                    if (ex instanceof AbstractManagementException) {
+                        return Single.error(ex);
+                    }
+                    LOGGER.error("An error has occurred while trying to update flows", ex);
+                    return Single.error(new TechnicalManagementException("An error has occurred while trying to update flows", ex));
+                });
+    }
+
+    @Override
+    public Completable delete(String id, User principal) {
+        LOGGER.debug("Delete flow {}", id);
+        return flowRepository.findById(id)
+                .switchIfEmpty(Maybe.error(new FlowNotFoundException(id)))
+                .flatMapCompletable(flow -> {
+                    // create event for sync process
+                    Event event = new Event(io.gravitee.am.common.event.Type.FLOW, new Payload(flow.getId(), flow.getReferenceType(), flow.getReferenceId(), Action.DELETE));
+                    return flowRepository.delete(id)
+                            .andThen(eventService.create(event))
+                            .ignoreElement()
+                            .doOnComplete(() -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_DELETED).flow(flow)))
+                            .doOnError(throwable -> auditService.report(AuditBuilder.builder(FlowAuditBuilder.class).principal(principal).type(EventType.FLOW_DELETED).throwable(throwable)));
+                })
+                .onErrorResumeNext(ex -> {
+                    if (ex instanceof AbstractManagementException) {
+                        return Completable.error(ex);
+                    }
+
+                    LOGGER.error("An error has occurred while trying to delete flow: {}", id, ex);
+                    return Completable.error(new TechnicalManagementException(
+                            String.format("An error has occurred while trying to delete flow: %s", id), ex));
+                });
+    }
+
+    @Override
+    public Single<String> getSchema() {
+        return Single.create(emitter -> {
+            try {
+                InputStream resourceAsStream = this.getClass().getResourceAsStream(DEFINITION_PATH);
+                String schema = IOUtils.toString(resourceAsStream, defaultCharset());
+                emitter.onSuccess(schema);
+            } catch (Exception e) {
+                emitter.onError(new TechnicalManagementException("An error has occurred while trying load flow schema", e));
+            }
+        });
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/FlowAuditBuilder.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/FlowAuditBuilder.java
new file mode 100644
index 0000000000..a1dc3b1b22
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/FlowAuditBuilder.java
@@ -0,0 +1,43 @@
+/**
+ * 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.service.reporter.builder.management;
+
+import io.gravitee.am.common.audit.EntityType;
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.model.flow.Flow;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowAuditBuilder extends ManagementAuditBuilder<FlowAuditBuilder> {
+
+    public FlowAuditBuilder() {
+        super();
+    }
+
+    public FlowAuditBuilder flow(Flow flow) {
+        if (EventType.FLOW_CREATED.equals(getType()) || EventType.FLOW_UPDATED.equals(getType())) {
+            setNewValue(flow);
+        }
+
+        referenceType(flow.getReferenceType());
+        referenceId(flow.getReferenceId());
+
+        setTarget(flow.getId(), EntityType.FLOW, null, flow.getName(), flow.getReferenceType(), flow.getReferenceId());
+        return this;
+    }
+}
diff --git a/gravitee-am-service/src/main/resources/flow/am-schema.json b/gravitee-am-service/src/main/resources/flow/am-schema.json
new file mode 100644
index 0000000000..79164902e1
--- /dev/null
+++ b/gravitee-am-service/src/main/resources/flow/am-schema.json
@@ -0,0 +1,37 @@
+{
+  "type": "object",
+  "id": "am",
+  "properties": {
+    "name": {
+      "title": "Name",
+      "description": "The name of flow. If empty, the name will be generated with the path and methods",
+      "type": "string"
+    },
+    "description": {
+      "title": "Description",
+      "description": "The description of flow",
+      "type": "string"
+    },
+    "type": {
+      "title": "Type",
+      "description": "The type of flow",
+      "type": "string",
+      "enum": [
+        "ROOT",
+        "LOGIN",
+        "CONSENT",
+        "REGISTER"
+      ]
+    },
+    "condition": {
+      "title": "Condition",
+      "description": "The condition of flow",
+      "type": "string"
+    }
+  },
+  "required": [],
+  "disabled": [
+    "type",
+    "condition"
+  ]
+}
diff --git a/gravitee-am-ui/package-lock.json b/gravitee-am-ui/package-lock.json
index 82bac9fefa..1559ad1bf0 100644
--- a/gravitee-am-ui/package-lock.json
+++ b/gravitee-am-ui/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "gravitee-am-webui",
-  "version": "3.4.0-SNAPSHOT",
+  "version": "3.4.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -554,6 +554,15 @@
         "tslib": "^1.9.0"
       }
     },
+    "@asciidoctor/core": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-2.2.0.tgz",
+      "integrity": "sha512-WN/mFuU4SeWaDqpGvRwAf+Tq2T8NQkVVpZ3Ne1/ZRxDKElzFkqq8bGbmxSMWmMVzC+H9ZO1YxxbOjhWEiNvpOA==",
+      "requires": {
+        "asciidoctor-opal-runtime": "0.3.0",
+        "unxhr": "1.0.1"
+      }
+    },
     "@babel/code-frame": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
@@ -732,6 +741,59 @@
         }
       }
     },
+    "@formatjs/intl-relativetimeformat": {
+      "version": "4.5.16",
+      "resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.16.tgz",
+      "integrity": "sha512-IQ0haY97oHAH5OYUdykNiepdyEWj3SAT+Fp9ZpR85ov2JNiFx+12WWlxlVS8ehdyncC2ZMt/SwFIy2huK2+6/A==",
+      "requires": {
+        "@formatjs/intl-utils": "^2.3.0"
+      }
+    },
+    "@formatjs/intl-utils": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz",
+      "integrity": "sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ=="
+    },
+    "@gravitee/ui-components": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/@gravitee/ui-components/-/ui-components-1.0.12.tgz",
+      "integrity": "sha512-8bgUWkfp0rRJ8tvlw8JRmExv4rf76/VNxBmrvOxgLz4UdPmRE+b1WGJwODg3uePtvOAaBCXXt3bX9Ek0PPXS7A==",
+      "requires": {
+        "@formatjs/intl-relativetimeformat": "^4.5.9",
+        "@highcharts/map-collection": "^1.1.3",
+        "asciidoctor": "^2.2.0",
+        "asciidoctor-highlight.js": "^0.2.1",
+        "clipboard-copy": "^3.1.0",
+        "codemirror": "^5.54.0",
+        "date-fns": "^2.11.1",
+        "highcharts": "^8.2.0",
+        "highlight.js": "^9.18.1",
+        "jdenticon": "^2.2.0",
+        "lit-element": "^2.2.1",
+        "lit-html": "^1.1.2",
+        "messageformat": "^2.3.0",
+        "object-path": "^0.11.5",
+        "resize-observer-polyfill": "^1.5.1",
+        "whatwg-fetch": "^3.4.1"
+      },
+      "dependencies": {
+        "highcharts": {
+          "version": "8.2.2",
+          "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-8.2.2.tgz",
+          "integrity": "sha512-F63TXO7RxsvTcpO/KOubQZWualYpCMyCTuKtoWbt7KCsfQ3Kl7Fr6HEyyJdjkYl+XlnmnKlSRi9d3HjLK9Q0wg=="
+        },
+        "whatwg-fetch": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
+          "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A=="
+        }
+      }
+    },
+    "@highcharts/map-collection": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@highcharts/map-collection/-/map-collection-1.1.3.tgz",
+      "integrity": "sha512-Kb31dEQc7Kf7dxkmJEUjYlZ2wtRftEumJR/bq3SwvcviZeEhz+t6vMLXqS9qxfeO0UzSOaTCLiNMBsg/W6d7/g=="
+    },
     "@ngtools/webpack": {
       "version": "7.3.9",
       "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.9.tgz",
@@ -1033,6 +1095,12 @@
         "@xtuc/long": "4.2.1"
       }
     },
+    "@webcomponents/webcomponentsjs": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.5.0.tgz",
+      "integrity": "sha512-C0l51MWQZ9kLzcxOZtniOMohpIFdCLZum7/TEHv3XWFc1Fvt5HCpbSX84x8ltka/JuNKcuiDnxXFkiB2gaePcg==",
+      "dev": true
+    },
     "@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -1329,6 +1397,201 @@
       "dev": true,
       "optional": true
     },
+    "asciidoctor": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/asciidoctor/-/asciidoctor-2.2.0.tgz",
+      "integrity": "sha512-w5oBMYPaA5RP/Qlv6XQzWE7kSf8d9sLMRBItcU+nn7E/FF9yGLUd1p/dzqWiu600KyNHBpp8Ml/VTaGjUm6LrQ==",
+      "requires": {
+        "@asciidoctor/cli": "3.4.0",
+        "@asciidoctor/core": "2.2.0"
+      },
+      "dependencies": {
+        "@asciidoctor/cli": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-3.4.0.tgz",
+          "integrity": "sha512-jOtxA0I6zB+6z+GGwm9+xhlmGTqCTkFPE902L6fauFlE6v7LxjhLYNxvjDVyn0zMrFLybvoSRcAnM3DcticNoQ==",
+          "requires": {
+            "yargs": "15.3.1"
+          }
+        },
+        "ansi-regex": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "camelcase": {
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+          "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+        },
+        "cliui": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+          "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.0",
+            "wrap-ansi": "^6.2.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+        },
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "get-caller-file": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+          "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+        },
+        "require-main-filename": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+        },
+        "string-width": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+          "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+          "requires": {
+            "ansi-regex": "^5.0.0"
+          }
+        },
+        "which-module": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+          "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+        },
+        "wrap-ansi": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+          "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "yargs": {
+          "version": "15.3.1",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
+          "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+          "requires": {
+            "cliui": "^6.0.0",
+            "decamelize": "^1.2.0",
+            "find-up": "^4.1.0",
+            "get-caller-file": "^2.0.1",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^4.2.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^18.1.1"
+          }
+        },
+        "yargs-parser": {
+          "version": "18.1.3",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+          "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "asciidoctor-highlight.js": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/asciidoctor-highlight.js/-/asciidoctor-highlight.js-0.2.1.tgz",
+      "integrity": "sha512-nDsTOdVBf0e5/ua2bPiCj32rYQl/iprOR5LV79axuW9eMIadEH9LaXLVqDo+tAopQ1iPPmvIX9uhnO08js4kDg==",
+      "requires": {
+        "highlight.js": "^9.0.0"
+      }
+    },
+    "asciidoctor-opal-runtime": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.0.tgz",
+      "integrity": "sha512-YapVwl2qbbs6sIe1dvAlMpBzQksFVTSa2HOduOKFNhZlE9bNmn+moDgGVvjWPbzMPo/g8gItyTHfWB2u7bQxag==",
+      "requires": {
+        "glob": "7.1.3",
+        "unxhr": "1.0.1"
+      }
+    },
     "asn1": {
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@@ -1645,8 +1908,7 @@
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-      "dev": true
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
     },
     "base": {
       "version": "0.11.2",
@@ -1844,7 +2106,6 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -2271,6 +2532,11 @@
       "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
       "dev": true
     },
+    "clipboard-copy": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-3.2.0.tgz",
+      "integrity": "sha512-vooFaGFL6ulEP1liiaWFBmmfuPm3cY3y7T9eB83ZTnYc/oFeAKsq3NcDrOkBC8XaauEE8zHQwI7k0+JSYiVQSQ=="
+    },
     "cliui": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
@@ -2451,8 +2717,7 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-      "dev": true
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
     "concat-stream": {
       "version": "1.6.2",
@@ -2757,6 +3022,11 @@
         "assert-plus": "^1.0.0"
       }
     },
+    "date-fns": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
+      "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
+    },
     "date-format": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz",
@@ -2781,8 +3051,7 @@
     "decamelize": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
-      "dev": true
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
     },
     "decode-uri-component": {
       "version": "0.2.0",
@@ -3086,6 +3355,11 @@
         "minimalistic-crypto-utils": "^1.0.0"
       }
     },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+    },
     "emojis-list": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -3833,8 +4107,7 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
       "version": "1.2.9",
@@ -4468,7 +4741,6 @@
       "version": "7.1.3",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
       "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
-      "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -4703,6 +4975,11 @@
         "tslib": "^1.9.0"
       }
     },
+    "highlight.js": {
+      "version": "9.18.5",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz",
+      "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA=="
+    },
     "hmac-drbg": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -5055,7 +5332,6 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -5064,8 +5340,7 @@
     "inherits": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-      "dev": true
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
     },
     "ini": {
       "version": "1.3.5",
@@ -6040,6 +6315,19 @@
         "immediate": "~3.0.5"
       }
     },
+    "lit-element": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.4.0.tgz",
+      "integrity": "sha512-pBGLglxyhq/Prk2H91nA0KByq/hx/wssJBQFiYqXhGDvEnY31PRGYf1RglVzyLeRysu0IHm2K0P196uLLWmwFg==",
+      "requires": {
+        "lit-html": "^1.1.1"
+      }
+    },
+    "lit-html": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.3.0.tgz",
+      "integrity": "sha512-0Q1bwmaFH9O14vycPHw8C/IeHMk/uSDldVLIefu/kfbTBGIc44KGH6A8p1bDfxUfHdc8q6Ct7kQklWoHgr4t1Q=="
+    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -6305,6 +6593,14 @@
         }
       }
     },
+    "make-plural": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz",
+      "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==",
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
     "map-age-cleaner": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@@ -6399,6 +6695,26 @@
       "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
       "dev": true
     },
+    "messageformat": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz",
+      "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==",
+      "requires": {
+        "make-plural": "^4.3.0",
+        "messageformat-formatters": "^2.0.1",
+        "messageformat-parser": "^4.1.2"
+      }
+    },
+    "messageformat-formatters": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz",
+      "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg=="
+    },
+    "messageformat-parser": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz",
+      "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg=="
+    },
     "methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -6490,7 +6806,6 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
-      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
@@ -6498,8 +6813,7 @@
     "minimist": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
-      "dev": true
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
     },
     "minipass": {
       "version": "2.3.5",
@@ -7055,6 +7369,11 @@
         }
       }
     },
+    "object-path": {
+      "version": "0.11.5",
+      "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz",
+      "integrity": "sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg=="
+    },
     "object-visit": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@@ -7098,7 +7417,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
       "requires": {
         "wrappy": "1"
       }
@@ -7483,8 +7801,7 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
     },
     "path-is-inside": {
       "version": "1.0.2",
@@ -8228,8 +8545,7 @@
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
-      "dev": true
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
     },
     "require-from-string": {
       "version": "2.0.2",
@@ -8249,6 +8565,11 @@
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
       "dev": true
     },
+    "resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "resolve": {
       "version": "1.11.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
@@ -8599,8 +8920,7 @@
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
-      "dev": true
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
     },
     "set-immediate-shim": {
       "version": "1.0.1",
@@ -10084,6 +10404,11 @@
         }
       }
     },
+    "unxhr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.0.1.tgz",
+      "integrity": "sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg=="
+    },
     "upath": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
@@ -10786,8 +11111,7 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
     },
     "ws": {
       "version": "3.3.3",
@@ -10850,8 +11174,7 @@
     "y18n": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
-      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
-      "dev": true
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
     },
     "yallist": {
       "version": "2.1.2",
diff --git a/gravitee-am-ui/package.json b/gravitee-am-ui/package.json
index 186c491618..fd05c8acf5 100644
--- a/gravitee-am-ui/package.json
+++ b/gravitee-am-ui/package.json
@@ -26,6 +26,7 @@
     "@angular/platform-browser-dynamic": "^7.2.16",
     "@angular/platform-server": "^7.2.16",
     "@angular/router": "^7.2.16",
+    "@gravitee/ui-components": "^1.0.12",
     "@swimlane/ngx-datatable": "~14.0.0",
     "@types/hammerjs": "^2.0.36",
     "angular7-json-schema-form": "^1.0.4",
@@ -53,6 +54,7 @@
     "@types/jasmine": "~3.3.12",
     "@types/jasminewd2": "~2.0.6",
     "@types/node": "~12.0.2",
+    "@webcomponents/webcomponentsjs": "^2.5.0",
     "codelyzer": "^5.0.1",
     "jasmine-core": "~3.4.0",
     "jasmine-spec-reporter": "~4.2.1",
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index 88f2ca5df0..76eb21922a 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -40,6 +40,7 @@ import {DomainSettingsExtensionGrantsComponent} from './domain/settings/extensio
 import {DomainSettingsAccountComponent} from './domain/settings/account/account.component';
 import {DomainSettingsPoliciesComponent} from './domain/settings/policies/policies.component';
 import {DomainSettingsMembershipsComponent} from './domain/settings/memberships/memberships.component';
+import {DomainSettingsFlowsComponent} from './domain/settings/flows/flows.component';
 import {DomainCreationComponent} from './domain/creation/domain-creation.component';
 import {ProviderCreationComponent} from './domain/settings/providers/creation/provider-creation.component';
 import {ProviderComponent} from './domain/settings/providers/provider/provider.component';
@@ -48,6 +49,7 @@ import {LogoutComponent} from './logout/logout.component';
 import {DomainsResolver} from './resolvers/domains.resolver';
 import {DomainResolver} from './resolvers/domain.resolver';
 import {DomainEntrypointResolver} from './resolvers/domain-entrypoint.resolver';
+import {DomainFlowsResolver} from './resolvers/domain-flows.resolver';
 import {DomainPermissionsResolver} from './resolvers/domain-permissions.resolver';
 import {ProvidersResolver} from './resolvers/providers.resolver';
 import {ProviderResolver} from './resolvers/provider.resolver';
@@ -167,7 +169,9 @@ import {ApplicationResourcePolicyResolver} from './resolvers/application-resourc
 import {DomainSettingsEntrypointsComponent} from './domain/settings/entrypoints/entrypoints.component';
 import {DomainSettingsWebAuthnComponent} from './domain/settings/webauthn/webauthn.component';
 import {ApplicationLoginSettingsComponent} from './domain/applications/application/advanced/login/login.component';
-import {IdentitiesResolver} from "./resolvers/identities.resolver";
+import {IdentitiesResolver} from './resolvers/identities.resolver';
+import {PluginPoliciesResolver} from './resolvers/plugin-policies.resolver';
+import {PlatformFlowSchemaResolver} from './resolvers/platform-flow-schema.resolver';
 
 const routes: Routes = [
   {
@@ -992,6 +996,24 @@ const routes: Routes = [
               }
             }
           },
+          { path: 'flows',
+            component: DomainSettingsFlowsComponent,
+            canActivate: [AuthGuard],
+            resolve: {
+              flows: DomainFlowsResolver,
+              policies: PluginPoliciesResolver,
+              flowSettingsForm: PlatformFlowSchemaResolver
+            },
+            data: {
+              menu: {
+                label: 'Flows',
+                section: 'Design'
+              },
+              perms: {
+                only: ['domain_flow_list']
+              }
+            }
+          },
           { path: 'providers',
             component: DomainSettingsProvidersComponent,
             canActivate: [AuthGuard],
diff --git a/gravitee-am-ui/src/app/app.module.ts b/gravitee-am-ui/src/app/app.module.ts
index 418d27a798..3c8fa7db21 100644
--- a/gravitee-am-ui/src/app/app.module.ts
+++ b/gravitee-am-ui/src/app/app.module.ts
@@ -16,7 +16,7 @@
 import 'polyfills';
 import { BrowserModule } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { NgModule } from '@angular/core';
+import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
 import {
@@ -77,6 +77,7 @@ import { LogoutCallbackComponent } from './logout/callback/callback.component';
 import { DomainsResolver } from './resolvers/domains.resolver';
 import { DomainResolver } from './resolvers/domain.resolver';
 import { DomainEntrypointResolver } from './resolvers/domain-entrypoint.resolver';
+import { DomainFlowsResolver } from './resolvers/domain-flows.resolver';
 import { DomainDashboardComponent } from './domain/dashboard/dashboard.component';
 import { DomainSettingsComponent } from './domain/settings/settings.component';
 import { DomainSettingsGeneralComponent } from './domain/settings/general/general.component';
@@ -104,6 +105,7 @@ import {
 } from './domain/settings/memberships/memberships.component';
 import { DomainSettingsFactorsComponent } from './domain/settings/factors/factors.component';
 import { DomainSettingsWebAuthnComponent } from './domain/settings/webauthn/webauthn.component';
+import { DomainSettingsFlowsComponent } from './domain/settings/flows/flows.component';
 import { ProvidersResolver } from './resolvers/providers.resolver';
 import { ProviderResolver } from './resolvers/provider.resolver';
 import { ProviderSettingsComponent } from './domain/settings/providers/provider/settings/settings.component';
@@ -288,7 +290,9 @@ import { ApplicationResourcePolicyComponent } from './domain/applications/applic
 import { ApplicationResourcePolicyResolver } from './resolvers/application-resource-policy.resolver';
 import { LoginSettingsComponent } from './domain/components/login/login-settings.component';
 import { ApplicationLoginSettingsComponent } from './domain/applications/application/advanced/login/login.component';
-import {IdentitiesResolver} from "./resolvers/identities.resolver";
+import { IdentitiesResolver } from './resolvers/identities.resolver';
+import { PluginPoliciesResolver } from './resolvers/plugin-policies.resolver';
+import { PlatformFlowSchemaResolver } from './resolvers/platform-flow-schema.resolver';
 
 @NgModule({
   declarations: [
@@ -324,6 +328,7 @@ import {IdentitiesResolver} from "./resolvers/identities.resolver";
     DomainSettingsMembershipsComponent,
     DomainSettingsFactorsComponent,
     DomainSettingsWebAuthnComponent,
+    DomainSettingsFlowsComponent,
     ConfirmComponent,
     EmptystateComponent,
     DomainCreationComponent,
@@ -500,6 +505,7 @@ import {IdentitiesResolver} from "./resolvers/identities.resolver";
     DomainsResolver,
     DomainResolver,
     DomainEntrypointResolver,
+    DomainFlowsResolver,
     ProvidersResolver,
     ProviderResolver,
     CertificatesResolver,
@@ -557,6 +563,8 @@ import {IdentitiesResolver} from "./resolvers/identities.resolver";
     ApplicationResourceResolver,
     ApplicationResourcePolicyResolver,
     IdentitiesResolver,
+    PluginPoliciesResolver,
+    PlatformFlowSchemaResolver,
     {
       provide: HTTP_INTERCEPTORS,
       useClass: HttpRequestInterceptor,
@@ -583,6 +591,7 @@ import {IdentitiesResolver} from "./resolvers/identities.resolver";
     ApplicationMembershipsDialog,
     UsersSearchInfoDialog
   ],
+  schemas: [CUSTOM_ELEMENTS_SCHEMA],
   bootstrap: [AppComponent]
 })
 export class AppModule { }
diff --git a/gravitee-am-ui/src/app/domain/settings/flows/flows.component.html b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.html
new file mode 100644
index 0000000000..6f5960a926
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.html
@@ -0,0 +1,23 @@
+<!--
+
+    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.
+
+-->
+<div class="gv-page-container">
+  <gv-policy-studio [policies]="policies"
+                    [flowSettingsForm]="flowSettingsForm"
+                    [definition]="definition">
+  </gv-policy-studio>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/settings/flows/flows.component.scss b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gravitee-am-ui/src/app/domain/settings/flows/flows.component.spec.ts b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.spec.ts
new file mode 100644
index 0000000000..3123350a1c
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DomainSettingsFlowsComponent } from './flows.component';
+
+describe('DomainSettingsFlowsComponent', () => {
+  let component: DomainSettingsFlowsComponent;
+  let fixture: ComponentFixture<DomainSettingsFlowsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ DomainSettingsFlowsComponent ]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DomainSettingsFlowsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
+
diff --git a/gravitee-am-ui/src/app/domain/settings/flows/flows.component.ts b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.ts
new file mode 100644
index 0000000000..ba4d5d9b41
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/settings/flows/flows.component.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import '@gravitee/ui-components/wc/gv-policy-studio';
+
+@Component({
+  selector: 'app-domain-flows',
+  templateUrl: './flows.component.html',
+  styleUrls: ['./flows.component.scss']
+})
+export class DomainSettingsFlowsComponent implements OnInit {
+  private domainId: string;
+  policies: any[];
+  definition: any = {};
+  flowSettingsForm: string;
+
+  constructor(private route: ActivatedRoute) {}
+
+  ngOnInit(): void {
+    this.domainId = this.route.snapshot.parent.parent.params['domainId'];
+    this.policies = this.route.snapshot.data['policies'] || [];
+    this.flowSettingsForm = this.route.snapshot.data['flowSettingsForm'];
+    this.definition.flows = this.route.snapshot.data['flows'] || [];
+  }
+}
+
diff --git a/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.spec.ts
new file mode 100644
index 0000000000..3f4d459e14
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.spec.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+import {inject, TestBed} from '@angular/core/testing';
+import {DomainFlowsResolver} from './domain-flows.resolver';
+
+describe('DomainFlowsResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [DomainFlowsResolver]
+    });
+  });
+
+  it('should ...', inject([DomainFlowsResolver], (service: DomainFlowsResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.ts b/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.ts
new file mode 100644
index 0000000000..667ce537bf
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/domain-flows.resolver.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
+import {Observable} from 'rxjs';
+import {DomainService} from '../services/domain.service';
+
+@Injectable()
+export class DomainFlowsResolver implements Resolve<any> {
+
+  constructor(private domainService: DomainService) {
+  }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
+    const domainId = route.parent.parent.paramMap.get('domainId');
+    return this.domainService.flows(domainId);
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.spec.ts
new file mode 100644
index 0000000000..260ea5ae34
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { PlatformFlowSchemaResolver } from './platform-flow-schema.resolver';
+
+describe('PlatformFlowSchemaResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [PlatformFlowSchemaResolver]
+    });
+  });
+
+  it('should ...', inject([PlatformFlowSchemaResolver], (service: PlatformFlowSchemaResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.ts b/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.ts
new file mode 100644
index 0000000000..2de9ca56b8
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/platform-flow-schema.resolver.ts
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
+import {OrganizationService} from '../services/organization.service';
+import {Observable} from 'rxjs';
+
+@Injectable()
+export class PlatformFlowSchemaResolver implements Resolve<any> {
+
+  constructor(private organizationService: OrganizationService) {
+  }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
+    return this.organizationService.flowSchema();
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.spec.ts
new file mode 100644
index 0000000000..6680c353c2
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { PluginPoliciesResolver } from './plugin-policies.resolver';
+
+describe('PluginPoliciesResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [PluginPoliciesResolver]
+    });
+  });
+
+  it('should ...', inject([PluginPoliciesResolver], (service: PluginPoliciesResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.ts b/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.ts
new file mode 100644
index 0000000000..0b410404c5
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/plugin-policies.resolver.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
+import { OrganizationService } from '../services/organization.service';
+import { Observable } from 'rxjs';
+
+@Injectable()
+export class PluginPoliciesResolver implements Resolve<any> {
+
+  constructor(private organizationService: OrganizationService) { }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
+    return this.organizationService.policies();
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/services/domain.service.ts b/gravitee-am-ui/src/app/services/domain.service.ts
index 58f8f7e758..1e90ebea29 100644
--- a/gravitee-am-ui/src/app/services/domain.service.ts
+++ b/gravitee-am-ui/src/app/services/domain.service.ts
@@ -165,4 +165,8 @@ export class DomainService {
         return perms;
       }));
   }
+
+  flows(id) {
+    return this.http.get<any>(this.domainsURL + id + '/flows');
+  }
 }
diff --git a/gravitee-am-ui/src/app/services/organization.service.ts b/gravitee-am-ui/src/app/services/organization.service.ts
index 5769607ead..22eb1bda04 100644
--- a/gravitee-am-ui/src/app/services/organization.service.ts
+++ b/gravitee-am-ui/src/app/services/organization.service.ts
@@ -306,4 +306,8 @@ export class OrganizationService {
   factorSchema(id): Observable<any> {
     return this.http.get<any>(this.organizationURL + '/plugins/factors/' + id + '/schema');
   }
+
+  flowSchema(): Observable<any> {
+    return this.http.get<any>(this.platformURL + '/configuration/flow/schema');
+  }
 }
diff --git a/gravitee-am-ui/src/index.html b/gravitee-am-ui/src/index.html
index 31757f0dd2..f5c8027a0f 100644
--- a/gravitee-am-ui/src/index.html
+++ b/gravitee-am-ui/src/index.html
@@ -25,6 +25,7 @@
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <link rel="stylesheet" href="assets/theme/gravitee.css">
+  <script src="webcomponents/webcomponents-loader.js"></script>
 </head>
 <body>
   <!--[if lt IE 10]>
diff --git a/pom.xml b/pom.xml
index 55ebbe50f0..656965f24f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,10 +98,10 @@
         <sonar.skip>true</sonar.skip>
 
         <!-- External plugins versions -->
-        <gravitee-policy-callout-http.version>1.8.0</gravitee-policy-callout-http.version>
+        <gravitee-policy-callout-http.version>1.11.0</gravitee-policy-callout-http.version>
         <gravitee-policy-groovy.version>1.11.1</gravitee-policy-groovy.version>
-        <gravitee-policy-ipfiltering.version>1.3.0</gravitee-policy-ipfiltering.version>
-        <gravitee-policy-request-validation.version>1.7.1</gravitee-policy-request-validation.version>
+        <gravitee-policy-ipfiltering.version>1.5.0</gravitee-policy-ipfiltering.version>
+        <gravitee-policy-request-validation.version>1.10.0</gravitee-policy-request-validation.version>
     </properties>
 
     <dependencyManagement>


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/PolicyChainHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/PolicyChainHandlerTest.java
index 85a9e30a97..961089d21a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/PolicyChainHandlerTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-common/src/test/java/io/gravitee/am/gateway/handler/common/vertx/web/handler/PolicyChainHandlerTest.java
@@ -17,7 +17,7 @@ package io.gravitee.am.gateway.handler.common.vertx.web.handler;
 
 import io.gravitee.am.common.policy.ExtensionPoint;
 import io.gravitee.am.gateway.core.processor.Processor;
-import io.gravitee.am.gateway.handler.common.policy.PolicyManager;
+import io.gravitee.am.gateway.handler.common.flow.FlowManager;
 import io.gravitee.am.gateway.handler.common.vertx.web.handler.impl.internal.PolicyChainHandlerImpl;
 import io.gravitee.am.gateway.handler.context.ExecutionContextFactory;
 import io.gravitee.am.gateway.policy.Policy;
@@ -45,7 +45,7 @@ import static org.mockito.Mockito.*;
 public class PolicyChainHandlerTest {
 
     @Mock
-    private PolicyManager policyManager;
+    private FlowManager flowManager;
 
     @Mock
     private PolicyChainProcessorFactory policyChainProcessorFactory;
@@ -73,20 +73,20 @@ public class PolicyChainHandlerTest {
 
     @Test
     public void shouldNotInvoke_noPolicies() {
-        when(policyManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Collections.emptyList()));
+        when(flowManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Collections.emptyList()));
 
-        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(policyManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
+        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(flowManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
 
         policyChainHandler.handle(routingContext);
 
-        verify(policyManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
+        verify(flowManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
         verify(policyChainProcessorFactory, never()).create(any(), any());
         verify(executionContextFactory, never()).create(any());
     }
 
     @Test
     public void shouldInvoke_onePolicy() {
-        when(policyManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Collections.singletonList(policy)));
+        when(flowManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Collections.singletonList(policy)));
         when(delegateRequest.method()).thenReturn(HttpMethod.GET);
         when(request.getDelegate()).thenReturn(delegateRequest);
         when(routingContext.request()).thenReturn(request);
@@ -96,18 +96,18 @@ public class PolicyChainHandlerTest {
         when(processor.errorHandler(any())).thenReturn(processor);
         when(policyChainProcessorFactory.create(Collections.singletonList(policy), executionContext)).thenReturn(processor);
 
-        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(policyManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
+        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(flowManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
 
         policyChainHandler.handle(routingContext);
 
-        verify(policyManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
+        verify(flowManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
         verify(policyChainProcessorFactory, times(1)).create(any(), any());
         verify(executionContextFactory, times(1)).create(any());
     }
 
     @Test
     public void shouldInvoke_manyPolicies() {
-        when(policyManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Arrays.asList(policy, policy)));
+        when(flowManager.findByExtensionPoint(ExtensionPoint.PRE_CONSENT)).thenReturn(Single.just(Arrays.asList(policy, policy)));
         when(delegateRequest.method()).thenReturn(HttpMethod.GET);
         when(request.getDelegate()).thenReturn(delegateRequest);
         when(routingContext.request()).thenReturn(request);
@@ -117,12 +117,12 @@ public class PolicyChainHandlerTest {
         when(processor.errorHandler(any())).thenReturn(processor);
         when(policyChainProcessorFactory.create(Arrays.asList(policy, policy), executionContext)).thenReturn(processor);
 
-        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(policyManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
+        PolicyChainHandlerImpl policyChainHandler = new PolicyChainHandlerImpl(flowManager, policyChainProcessorFactory, executionContextFactory, ExtensionPoint.PRE_CONSENT);
 
         policyChainHandler.handle(routingContext);
 
         // should be only call once
-        verify(policyManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
+        verify(flowManager, times(1)).findByExtensionPoint(ExtensionPoint.PRE_CONSENT);
         verify(policyChainProcessorFactory, times(1)).create(any(), any());
         verify(executionContextFactory, times(1)).create(any());
     }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
index 79afe51a22..6d9d9233ea 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
@@ -163,6 +163,9 @@ public abstract class JerseySpringTest {
     @Autowired
     protected CredentialService credentialService;
 
+    @Autowired
+    protected FlowService flowService;
+
     @Before
     public void init() {
         when(permissionService.hasPermission(any(User.class), any(PermissionAcls.class))).thenReturn(Single.just(true));
@@ -336,6 +339,11 @@ public abstract class JerseySpringTest {
         public CredentialService credentialService() {
             return mock(CredentialService.class);
         }
+
+        @Bean
+        public FlowService flowService() {
+            return mock(FlowService.class);
+        }
     }
 
     private JerseyTest _jerseyTest;
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowResourceTest.java
new file mode 100644
index 0000000000..a03a4f91c2
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowResourceTest.java
@@ -0,0 +1,113 @@
+/**
+ * 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.management.handlers.management.api.resources;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.service.exception.FlowNotFoundException;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowResourceTest extends JerseySpringTest {
+
+    public static final String DOMAIN_ID = "domain#1";
+    public static final String FLOW_ID = "flow#1";
+
+    @Test
+    public void shouldGetFlow() {
+        final Flow mockFlow = new Flow();
+        mockFlow.setId(FLOW_ID);
+        mockFlow.setName("name");
+
+        doReturn(Maybe.just(mockFlow)).when(flowService).findById(ReferenceType.DOMAIN, DOMAIN_ID, FLOW_ID);
+
+        final Response response = target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .path(FLOW_ID)
+                .request()
+                .get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Flow flow = readEntity(response, Flow.class);
+        assertEquals(mockFlow.getId(), flow.getId());
+        assertEquals(mockFlow.getName(), flow.getName());
+    }
+
+    @Test
+    public void shouldNotGetFlow_notFound() {
+        doReturn(Maybe.empty()).when(flowService).findById(ReferenceType.DOMAIN, DOMAIN_ID, FLOW_ID);
+
+        final Response response = target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .path(FLOW_ID)
+                .request()
+                .get();
+
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+
+    @Test
+    public void shouldUpdateFlow() {
+
+        Flow updateFlow = new Flow();
+        updateFlow.setName("updatedName");
+
+        doReturn(Single.just(updateFlow)).when(flowService).update(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), eq(FLOW_ID), any(Flow.class), any(User.class));
+
+        final Response response = put(target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .path(FLOW_ID), updateFlow);
+
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Flow flow = readEntity(response, Flow.class);
+        assertEquals(updateFlow.getName(), flow.getName());
+    }
+
+    @Test
+    public void shouldNotUpdateEntrypoint_notFound() {
+
+        Flow updateFlow = new Flow();
+        updateFlow.setName("updatedName");
+
+        doReturn(Single.error(new FlowNotFoundException(FLOW_ID))).when(flowService).update(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), eq(FLOW_ID), any(Flow.class), any(User.class));
+
+        final Response response = put(target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .path(FLOW_ID), updateFlow);
+
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowsResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowsResourceTest.java
new file mode 100644
index 0000000000..fc294f968b
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/FlowsResourceTest.java
@@ -0,0 +1,68 @@
+/**
+ * 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.management.handlers.management.api.resources;
+
+import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FlowsResourceTest extends JerseySpringTest {
+
+    public static final String DOMAIN_ID = "domain-1";
+
+    @Test
+    public void shouldGetFlows() {
+        doReturn(Single.just(Arrays.asList(new Flow(), new Flow()))).when(flowService).findAll(ReferenceType.DOMAIN, DOMAIN_ID);
+
+        final Response response = target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .request()
+                .get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final List<Entrypoint> responseEntity = readEntity(response, List.class);
+        assertEquals(2, responseEntity.size());
+    }
+
+    @Test
+    public void shouldGetFlows_technicalManagementException() {
+        doReturn(Single.error(new TechnicalManagementException("error occurs"))).when(flowService).findAll(ReferenceType.DOMAIN, DOMAIN_ID);
+
+        final Response response = target("domains")
+                .path(DOMAIN_ID)
+                .path("flows")
+                .request()
+                .get();
+        assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR_500, response.getStatus());
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepositoryTest.java
new file mode 100644
index 0000000000..cab2337eb8
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoFlowRepositoryTest.java
@@ -0,0 +1,105 @@
+/**
+ * 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.repository.mongodb.management;
+
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.repository.management.api.FlowRepository;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class MongoFlowRepositoryTest extends AbstractManagementRepositoryTest {
+
+    @Autowired
+    private FlowRepository flowRepository;
+
+    @Override
+    public String collectionName() {
+        return "flows";
+    }
+
+    @Test
+    public void testFindById() {
+        Flow flow = new Flow();
+        flow.setName("ROOT");
+
+        Flow flowCreated = flowRepository.create(flow).blockingGet();
+
+        TestObserver<Flow> obs = flowRepository.findById(flowCreated.getId()).test();
+        obs.awaitTerminalEvent();
+
+        obs.assertComplete();
+        obs.assertNoErrors();
+        obs.assertValue(o -> o.getId().equals(flowCreated.getId()));
+    }
+
+    @Test
+    public void testNotFoundById() {
+        flowRepository.findById("test").test().assertEmpty();
+    }
+
+    @Test
+    public void testCreate() {
+        Flow flow = new Flow();
+        flow.setName("ROOT");
+
+        TestObserver<Flow> obs = flowRepository.create(flow).test();
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+        obs.assertNoErrors();
+        obs.assertValue(o -> o.getName().equals(flow.getName()) && o.getId() != null);
+    }
+
+    @Test
+    public void testUpdate() {
+        Flow flow = new Flow();
+        flow.setName("ROOT");
+        Flow flowCreated = flowRepository.create(flow).blockingGet();
+
+        Flow flowUpdated = new Flow();
+        flowUpdated.setId(flowCreated.getId());
+        flowUpdated.setName("testNameUpdated");
+
+        TestObserver<Flow> obs = flowRepository.update(flowUpdated).test();
+        obs.awaitTerminalEvent();
+
+        obs.assertComplete();
+        obs.assertNoErrors();
+        obs.assertValue(o -> o.getName().equals(flowUpdated.getName()) && o.getId().equals(flowCreated.getId()));
+    }
+
+    @Test
+    public void testDelete() {
+        Flow flow = new Flow();
+        flow.setName("ROOT");
+        Flow flowCreated = flowRepository.create(flow).blockingGet();
+
+        assertNotNull(flowRepository.findById(flowCreated.getId()).blockingGet());
+
+        TestObserver<Void> obs = flowRepository.delete(flowCreated.getId()).test();
+        obs.awaitTerminalEvent();
+        obs.assertNoValues();
+
+        assertNull(flowRepository.findById(flowCreated.getId()).blockingGet());
+    }
+}
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/FlowServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/FlowServiceTest.java
new file mode 100644
index 0000000000..da5b9a10bd
--- /dev/null
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/FlowServiceTest.java
@@ -0,0 +1,201 @@
+/**
+ * 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.service;
+
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.common.event.Event;
+import io.gravitee.am.model.flow.Flow;
+import io.gravitee.am.repository.exceptions.TechnicalException;
+import io.gravitee.am.repository.management.api.FlowRepository;
+import io.gravitee.am.service.exception.FlowNotFoundException;
+import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.impl.FlowServiceImpl;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FlowServiceTest {
+
+    @InjectMocks
+    private FlowService flowService = new FlowServiceImpl();
+
+    @Mock
+    private EventService eventService;
+
+    @Mock
+    private FlowRepository flowRepository;
+
+    @Mock
+    private AuditService auditService;
+
+    private final static String DOMAIN = "domain1";
+
+    @Test
+    public void shouldFindAll() {
+        when(flowRepository.findAll(ReferenceType.DOMAIN, DOMAIN)).thenReturn(Single.just(Collections.singletonList(new Flow())));
+        TestObserver testObserver = flowService.findAll(ReferenceType.DOMAIN, DOMAIN).test();
+
+        testObserver.awaitTerminalEvent();
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValueCount(1);
+    }
+
+    @Test
+    public void shouldNotFindAll_technicalException() {
+        when(flowRepository.findAll(ReferenceType.DOMAIN, DOMAIN)).thenReturn(Single.error(TechnicalException::new));
+        TestObserver testObserver = new TestObserver();
+        flowService.findAll(ReferenceType.DOMAIN, DOMAIN).subscribe(testObserver);
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+    }
+
+    @Test
+    public void shouldCreate() {
+        Flow newFlow = mock(Flow.class);
+        when(flowRepository.create(any(Flow.class))).thenReturn(Single.just(new Flow()));
+        when(eventService.create(any())).thenReturn(Single.just(new Event()));
+
+        TestObserver testObserver = flowService.create(ReferenceType.DOMAIN, DOMAIN, newFlow).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+
+        verify(flowRepository, times(1)).create(any(Flow.class));
+        verify(eventService, times(1)).create(any());
+    }
+
+    @Test
+    public void shouldCreate_technicalException() {
+        Flow newFlow = mock(Flow.class);
+        when(flowRepository.create(any(Flow.class))).thenReturn(Single.error(TechnicalException::new));
+
+        TestObserver testObserver = flowService.create(ReferenceType.DOMAIN, DOMAIN, newFlow).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+
+        verify(eventService, never()).create(any());
+    }
+
+    @Test
+    public void shouldUpdate() {
+        Flow updateFlow = Mockito.mock(Flow.class);
+        when(flowRepository.findById(ReferenceType.DOMAIN, DOMAIN, "my-flow")).thenReturn(Maybe.just(new Flow()));
+        when(flowRepository.update(any(Flow.class))).thenReturn(Single.just(new Flow()));
+        when(eventService.create(any())).thenReturn(Single.just(new Event()));
+
+        TestObserver testObserver = flowService.update(ReferenceType.DOMAIN, DOMAIN, "my-flow", updateFlow).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+
+        verify(flowRepository, times(1)).findById(ReferenceType.DOMAIN, DOMAIN, "my-flow");
+        verify(flowRepository, times(1)).update(any(Flow.class));
+        verify(eventService, times(1)).create(any());
+    }
+
+    @Test
+    public void shouldUpdate_technicalException() {
+        Flow updateFlow = Mockito.mock(Flow.class);
+        when(flowRepository.findById(ReferenceType.DOMAIN, DOMAIN, "my-flow")).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver testObserver = new TestObserver();
+        flowService.update(ReferenceType.DOMAIN, DOMAIN, "my-flow", updateFlow).subscribe(testObserver);
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+
+        verify(flowRepository, never()).update(any(Flow.class));
+        verify(eventService, never()).create(any());
+    }
+
+    @Test
+    public void shouldUpdate_flowNotFound() {
+        when(flowRepository.findById(ReferenceType.DOMAIN, DOMAIN,"my-flow")).thenReturn(Maybe.empty());
+
+        TestObserver testObserver = new TestObserver();
+        flowService.update(ReferenceType.DOMAIN, DOMAIN, "my-flow", new Flow()).subscribe(testObserver);
+
+        testObserver.assertError(FlowNotFoundException.class);
+        testObserver.assertNotComplete();
+
+        verify(flowRepository, never()).update(any(Flow.class));
+        verify(eventService, never()).create(any());
+    }
+
+    @Test
+    public void shouldDelete_flowNotFound() {
+        when(flowRepository.findById("my-flow")).thenReturn(Maybe.empty());
+
+        TestObserver testObserver = flowService.delete("my-flow").test();
+
+        testObserver.assertError(FlowNotFoundException.class);
+        testObserver.assertNotComplete();
+
+        verify(flowRepository, never()).delete(anyString());
+        verify(eventService, never()).create(any());
+    }
+
+    @Test
+    public void shouldDelete_technicalException() {
+        when(flowRepository.findById("my-flow")).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver testObserver = flowService.delete("my-flow").test();
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+
+        verify(flowRepository, never()).delete(anyString());
+        verify(eventService, never()).create(any());
+    }
+
+    @Test
+    public void shouldDelete() {
+        when(flowRepository.findById("my-flow")).thenReturn(Maybe.just(new Flow()));
+        when(flowRepository.delete("my-flow")).thenReturn(Completable.complete());
+        when(eventService.create(any())).thenReturn(Single.just(new Event()));
+
+        TestObserver testObserver = flowService.delete("my-flow").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+
+        verify(flowRepository, times(1)).delete("my-flow");
+        verify(eventService, times(1)).create(any());
+    }
+}