You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2022/03/15 08:38:06 UTC

[syncope] branch master updated: [SYNCOPE-1667] Adding PropagationPolicy (#325)

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new a039899  [SYNCOPE-1667] Adding PropagationPolicy (#325)
a039899 is described below

commit a03989916e1d86e6a8412de6f4b9ecdb2269e4c5
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Mar 15 09:36:13 2022 +0100

    [SYNCOPE-1667] Adding PropagationPolicy (#325)
---
 .../console/commons/IdMPolicyTabProvider.java      |  11 ++
 .../policies/PropagationPolicyDirectoryPanel.java  |  51 ++++++
 .../wizards/resources/ResourceSecurityPanel.java   |  33 +++-
 .../wizards/resources/ResourceSecurityPanel.html   |   3 +
 .../resources/ResourceSecurityPanel.properties     |   1 +
 .../ResourceSecurityPanel_fr_CA.properties         |   1 +
 .../resources/ResourceSecurityPanel_it.properties  |   1 +
 .../resources/ResourceSecurityPanel_ja.properties  |   1 +
 .../ResourceSecurityPanel_pt_BR.properties         |   1 +
 .../resources/ResourceSecurityPanel_ru.properties  |   1 +
 .../console/policies/PolicyModalPanelBuilder.java  | 126 ++++++++++++++
 .../client/console/pages/Policies.properties       |   1 +
 .../client/console/pages/Policies_it.properties    |   1 +
 .../client/console/pages/Policies_ja.properties    |   1 +
 .../client/console/pages/Policies_pt_BR.properties |   1 +
 .../client/console/pages/Policies_ru.properties    |   1 +
 .../policies/PolicyDirectoryPanel.properties       |   2 +
 .../policies/PolicyDirectoryPanel_fr_CA.properties |   2 +
 .../policies/PolicyDirectoryPanel_it.properties    |   2 +
 .../policies/PolicyDirectoryPanel_ja.properties    |   2 +
 .../policies/PolicyDirectoryPanel_pt_BR.properties |   2 +
 .../policies/PolicyDirectoryPanel_ru.properties    |   2 +
 .../common/lib/policy/PropagationPolicyTO.java     |  69 ++++++++
 .../apache/syncope/common/lib/to/ResourceTO.java   |  12 ++
 .../{PolicyType.java => BackOffStrategy.java}      |  41 ++---
 .../syncope/common/lib/types/PolicyType.java       |   4 +
 .../api/entity/policy/PropagationPolicy.java       |  48 ++----
 .../api/entity/resource/ExternalResource.java      |   5 +
 .../src/test/resources/domains/MasterContent.xml   |   5 +-
 .../jpa/dao/JPAExternalResourceDAO.java            |   3 +
 .../core/persistence/jpa/dao/JPAPolicyDAO.java     |   7 +
 .../core/persistence/jpa/dao/JPARealmDAO.java      |  13 +-
 .../jpa/dao/JPARelationshipTypeDAO.java            |   2 +-
 .../core/persistence/jpa/dao/JPAUserDAO.java       |   6 +-
 .../persistence/jpa/entity/JPAEntityFactory.java   |  24 +--
 .../jpa/entity/policy/JPAPolicyUtils.java          |  10 +-
 .../jpa/entity/policy/JPAPolicyUtilsFactory.java   |   6 +
 .../jpa/entity/policy/JPAPropagationPolicy.java    |  76 +++++++++
 .../jpa/entity/resource/JPAExternalResource.java   |  16 ++
 .../core/persistence/jpa/inner/PolicyTest.java     | 121 ++++++++++++--
 .../core/persistence/jpa/outer/GroupTest.java      |   4 +-
 .../core/persistence/jpa/outer/ResourceTest.java   |  17 +-
 .../src/test/resources/domains/MasterContent.xml   |   7 +-
 .../api/propagation/PropagationTaskExecutor.java   |  10 +-
 core/provisioning-java/pom.xml                     |   4 +
 .../provisioning/java/ProvisioningContext.java     |  70 +++++---
 .../java/data/PolicyDataBinderImpl.java            |  25 ++-
 .../java/data/ResourceDataBinderImpl.java          |  19 ++-
 .../AbstractPropagationTaskExecutor.java           | 183 ++++++++++++++++-----
 .../PriorityPropagationTaskExecutor.java           | 111 +++++++------
 .../apache/syncope/fit/console/PoliciesITCase.java |   4 +-
 .../org/apache/syncope/fit/core/PolicyITCase.java  |  38 ++++-
 .../syncope/fit/core/PropagationTaskITCase.java    |  40 +++++
 .../concepts/authenticationmodules.adoc            |  24 +--
 .../concepts/clientapplications.adoc               |   2 +-
 .../reference-guide/concepts/policies.adoc         |  20 ++-
 .../asciidoc/reference-guide/concepts/tasks.adoc   |   3 +
 57 files changed, 1027 insertions(+), 269 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
index cc63b83..0f2b920 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.commons;
 
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.syncope.client.console.policies.PropagationPolicyDirectoryPanel;
 import org.apache.syncope.client.console.policies.PullPolicyDirectoryPanel;
 import org.apache.syncope.client.console.policies.PushPolicyDirectoryPanel;
 import org.apache.wicket.PageReference;
@@ -41,6 +42,16 @@ public class IdMPolicyTabProvider implements PolicyTabProvider {
     public List<ITab> buildTabList(final PageReference pageRef) {
         List<ITab> tabs = new ArrayList<>();
 
+        tabs.add(new AbstractTab(new ResourceModel("policy.propagation")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new PropagationPolicyDirectoryPanel(panelId, pageRef);
+            }
+        });
+
         tabs.add(new AbstractTab(new ResourceModel("policy.pull")) {
 
             private static final long serialVersionUID = -6815067322125799251L;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
new file mode 100644
index 0000000..8a9a793
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.syncope.client.console.policies;
+
+import java.util.List;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.StringResourceModel;
+
+public class PropagationPolicyDirectoryPanel extends PolicyDirectoryPanel<PropagationPolicyTO> {
+
+    private static final long serialVersionUID = 25188602686577L;
+
+    public PropagationPolicyDirectoryPanel(final String id, final PageReference pageRef) {
+        super(id, PolicyType.PROPAGATION, pageRef);
+
+        PropagationPolicyTO defaultItem = new PropagationPolicyTO();
+
+        this.addNewItemPanelBuilder(
+                new PolicyModalPanelBuilder<>(PolicyType.PROPAGATION, defaultItem, modal, pageRef), true);
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, IdRepoEntitlement.POLICY_CREATE);
+
+        initResultTable();
+    }
+
+    @Override
+    protected void addCustomColumnFields(final List<IColumn<PropagationPolicyTO, String>> columns) {
+        columns.add(new PropertyColumn<>(new StringResourceModel("maxAttempts", this), "maxAttempts", "maxAttempts"));
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
index e339bba..f23862e 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
@@ -46,7 +46,7 @@ public class ResourceSecurityPanel extends WizardStep {
         @Override
         protected Map<String, String> load() {
             return PolicyRestClient.list(PolicyType.PASSWORD).stream().
-                collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
         }
     };
 
@@ -57,7 +57,18 @@ public class ResourceSecurityPanel extends WizardStep {
         @Override
         protected Map<String, String> load() {
             return PolicyRestClient.list(PolicyType.ACCOUNT).stream().
-                collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+        }
+    };
+
+    private final IModel<Map<String, String>> propagationPolicies = new LoadableDetachableModel<>() {
+
+        private static final long serialVersionUID = -2012833443695917883L;
+
+        @Override
+        protected Map<String, String> load() {
+            return PolicyRestClient.list(PolicyType.PROPAGATION).stream().
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
         }
     };
 
@@ -68,7 +79,7 @@ public class ResourceSecurityPanel extends WizardStep {
         @Override
         protected Map<String, String> load() {
             return PolicyRestClient.list(PolicyType.PULL).stream().
-                collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
         }
     };
 
@@ -79,7 +90,7 @@ public class ResourceSecurityPanel extends WizardStep {
         @Override
         protected Map<String, String> load() {
             return PolicyRestClient.list(PolicyType.PUSH).stream().
-                collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
         }
     };
 
@@ -121,6 +132,20 @@ public class ResourceSecurityPanel extends WizardStep {
         // -------------------------------
 
         // -------------------------------
+        // Propagation policy selection
+        // -------------------------------
+        AjaxDropDownChoicePanel<String> propagationPolicy = new AjaxDropDownChoicePanel<>(
+                "propagationPolicy",
+                new ResourceModel("propagationPolicy", "propagationPolicy").getObject(),
+                new PropertyModel<>(resourceTO, "propagationPolicy"),
+                false);
+        propagationPolicy.setChoiceRenderer(new PolicyRenderer(propagationPolicies));
+        propagationPolicy.setChoices(new ArrayList<>(propagationPolicies.getObject().keySet()));
+        ((DropDownChoice<?>) propagationPolicy.getField()).setNullValid(true);
+        container.add(propagationPolicy);
+        // -------------------------------
+
+        // -------------------------------
         // Pull policy selection
         // -------------------------------
         AjaxDropDownChoicePanel<String> pullPolicy = new AjaxDropDownChoicePanel<>(
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
index fea39ff..00b4882 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
@@ -25,6 +25,9 @@ under the License.
       <span wicket:id="passwordPolicy">
         [panel for dynamic input type markup]
       </span>
+      <span wicket:id="propagationPolicy">
+        [panel for dynamic input type markup]
+      </span>
       <span wicket:id="pullPolicy">
         [panel for dynamic input type markup]
       </span>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
index e4a70ca..c2b3da7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
 accountPolicy=Account Policy
 pullPolicy=Pull Policy
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
index 8a2c40f..2e20b1a 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
@@ -18,3 +18,4 @@ passwordPolicy=Politique sur les mots de passe
 accountPolicy=Politique sur les comptes
 pullPolicy=Politique Pull
 pushPolicy=Politique Push
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
index e4a70ca..c2b3da7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
 accountPolicy=Account Policy
 pullPolicy=Pull Policy
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
index ca6bc82..0df90b3 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
@@ -18,3 +18,4 @@ passwordPolicy=\u30d1\u30b9\u30ef\u30fc\u30c9\u30dd\u30ea\u30b7\u30fc
 accountPolicy=\u30a2\u30ab\u30a6\u30f3\u30c8\u30dd\u30ea\u30b7\u30fc
 pullPolicy=\u30d7\u30eb\u30dd\u30ea\u30b7\u30fc
 pushPolicy=\u30d7\u30c3\u30b7\u30e5\u30dd\u30ea\u30b7\u30fc
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
index 7300d71..2b8b4b7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
@@ -18,3 +18,4 @@ passwordPolicy=Pol\u00edtica de Senha
 accountPolicy=Pol\u00edtica de Conta
 pullPolicy=Pol\u00edtica de Pull
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
index 7d70763..70bf684 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
@@ -19,3 +19,4 @@ passwordPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0430\u
 accountPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439
 pullPolicy=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
index 82fdaa2..7778556 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
@@ -21,14 +21,17 @@ package org.apache.syncope.client.console.policies;
 import java.io.Serializable;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.console.panels.AbstractModalPanel;
 import org.apache.syncope.client.console.rest.PolicyRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
@@ -39,6 +42,7 @@ import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
 import org.apache.syncope.common.lib.policy.PolicyTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.wicket.Application;
@@ -59,6 +63,42 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
 
     private static final long serialVersionUID = 5945391813567245081L;
 
+    private static class BackOffParamsModel<N extends Number> implements IModel<N> {
+
+        private static final long serialVersionUID = 28839546672164L;
+
+        private final PropertyModel<String> backOffParamsModel;
+
+        private final int index;
+
+        BackOffParamsModel(final PropertyModel<String> backOffParamsModel, final int index) {
+            this.backOffParamsModel = backOffParamsModel;
+            this.index = index;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public N getObject() {
+            String[] split = backOffParamsModel.getObject().split(";");
+            if (index >= split.length) {
+                return null;
+            }
+
+            return index == 2
+                    ? (N) Double.valueOf(backOffParamsModel.getObject().split(";")[index])
+                    : (N) Long.valueOf(backOffParamsModel.getObject().split(";")[index]);
+        }
+
+        @Override
+        public void setObject(final N object) {
+            String[] split = backOffParamsModel.getObject().split(";");
+            if (index < split.length) {
+                split[index] = object.toString();
+                backOffParamsModel.setObject(Arrays.stream(split).collect(Collectors.joining(";")));
+            }
+        }
+    }
+
     private final BaseModal<T> modal;
 
     private final PolicyType type;
@@ -137,6 +177,63 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
                             false));
                     break;
 
+                case PROPAGATION:
+                    fields.add(new AjaxSpinnerFieldPanel.Builder<Integer>().build(
+                            "field",
+                            "maxAttempts",
+                            Integer.class,
+                            new PropertyModel<>(policyTO, "maxAttempts")));
+                    AjaxDropDownChoicePanel<Serializable> backOffStrategy = new AjaxDropDownChoicePanel<>(
+                            "field",
+                            "backOffStrategy",
+                            new PropertyModel<>(policyTO, "backOffStrategy")).
+                            setChoices(List.of((Serializable[]) BackOffStrategy.values()));
+                    fields.add(backOffStrategy);
+
+                    PropertyModel<String> backOffParamsModel = new PropertyModel<>(policyTO, "backOffParams");
+
+                    AjaxSpinnerFieldPanel<Long> initialInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+                            min(1L).build(
+                            "field",
+                            "initialInterval",
+                            Long.class,
+                            new BackOffParamsModel<>(backOffParamsModel, 0));
+                    fields.add(initialInterval.setOutputMarkupPlaceholderTag(true));
+                    AjaxSpinnerFieldPanel<Long> maxInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+                            min(1L).build(
+                            "field",
+                            "maxInterval",
+                            Long.class,
+                            new BackOffParamsModel<>(backOffParamsModel, 1));
+                    fields.add(maxInterval.setOutputMarkupPlaceholderTag(true).setVisible(false));
+                    AjaxSpinnerFieldPanel<Double> multiplier = new AjaxSpinnerFieldPanel.Builder<Double>().
+                            min(1D).build(
+                            "field",
+                            "multiplier",
+                            Double.class,
+                            new BackOffParamsModel<>(backOffParamsModel, 2));
+                    fields.add(multiplier.setOutputMarkupPlaceholderTag(true).setVisible(false));
+
+                    showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+                    backOffStrategy.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                        private static final long serialVersionUID = -1107858522700306810L;
+
+                        @Override
+                        protected void onUpdate(final AjaxRequestTarget target) {
+                            BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+                            backOffParamsModel.setObject(strategy.getDefaultBackOffParams());
+
+                            showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+                            target.add(initialInterval);
+                            target.add(maxInterval);
+                            target.add(multiplier);
+                        }
+                    });
+                    break;
+
                 case PULL:
                 case PUSH:
                     fields.add(new AjaxDropDownChoicePanel<>(
@@ -228,6 +325,35 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
             });
         }
 
+        private void showHide(
+                final AjaxDropDownChoicePanel<Serializable> backOffStrategy,
+                final AjaxSpinnerFieldPanel<Long> initialInterval,
+                final AjaxSpinnerFieldPanel<Long> maxInterval,
+                final AjaxSpinnerFieldPanel<Double> multiplier) {
+
+            BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+
+            switch (strategy) {
+                case EXPONENTIAL:
+                    initialInterval.addLabel("initialInterval");
+                    maxInterval.setVisible(true);
+                    multiplier.setVisible(true);
+                    break;
+
+                case RANDOM:
+                    initialInterval.addLabel("initialInterval");
+                    maxInterval.setVisible(true);
+                    multiplier.setVisible(true);
+                    break;
+
+                case FIXED:
+                default:
+                    initialInterval.addLabel("period");
+                    maxInterval.setVisible(false);
+                    multiplier.setVisible(false);
+            }
+        }
+
         @Override
         public void onSubmit(final AjaxRequestTarget target) {
             try {
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
index 8f9914e..b44aeda 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
@@ -21,3 +21,4 @@ policy.push=Push
 policy.access=Access
 policy.attrRelease=Attribute Release
 policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
index 1d36635..693aa35 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
@@ -21,3 +21,4 @@ policy.push=Push
 policy.access=Accesso
 policy.attrRelease=Rilascio Attributi
 policy.auth=Autenticazione
+policy.propagation=Propagazione
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
index c998c1a..5ed564c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
@@ -21,3 +21,4 @@ policy.push=\u30d7\u30c3\u30b7\u30e5
 policy.access=Access
 policy.attrRelease=Attribute Release
 policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
index 8f9914e..b44aeda 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
@@ -21,3 +21,4 @@ policy.push=Push
 policy.access=Access
 policy.attrRelease=Attribute Release
 policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
index 6d42c42..992307b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
@@ -22,3 +22,4 @@ policy.push=Push
 policy.access=Access
 policy.attrRelease=Attribute Release
 policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
index 4345946..1f1d3dc 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
 order=Order
 rejectedAttrs.title=Rejected Attributes
 unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
index 1be912b..a7858b0 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
 order=Order
 rejectedAttrs.title=Rejected Attributes
 unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
index 1c041ba..690c704 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
 order=Ordinamento
 rejectedAttrs.title=Attributi Rifiutati
 unauthorizedRedirectUrl=URL di Ridirezione Per Mancata Autorizzazione
+maxAttempts=Tentativi Massimi
+backOffStrategy=Strategia di BackOff
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
index 14c4ef4..ebefed5 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
 order=Order
 rejectedAttrs.title=Rejected Attributes
 unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
index 0635b3a..29e278e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
 order=Order
 rejectedAttrs.title=Rejected Attributes
 unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
index c4091fc..4212773 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
@@ -52,3 +52,5 @@ caseInsensitive=Case Insensitive
 order=Order
 rejectedAttrs.title=Rejected Attributes
 unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
new file mode 100644
index 0000000..90bc7cd
--- /dev/null
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.syncope.common.lib.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+@Schema(allOf = { PolicyTO.class }, discriminatorProperty = "_class")
+public class PropagationPolicyTO extends PolicyTO {
+
+    private static final long serialVersionUID = 10604950933449L;
+
+    private BackOffStrategy backOffStrategy = BackOffStrategy.FIXED;
+
+    private String backOffParams = BackOffStrategy.FIXED.getDefaultBackOffParams();
+
+    private int maxAttempts = 3;
+
+    @JacksonXmlProperty(localName = "_class", isAttribute = true)
+    @JsonProperty("_class")
+    @Schema(name = "_class", required = true, example = "org.apache.syncope.common.lib.policy.PropagationPolicyTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    public BackOffStrategy getBackOffStrategy() {
+        return backOffStrategy;
+    }
+
+    public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+        this.backOffStrategy = backOffStrategy;
+        this.backOffParams = backOffStrategy.getDefaultBackOffParams();
+    }
+
+    public String getBackOffParams() {
+        return backOffParams;
+    }
+
+    public void setBackOffParams(final String backOffParams) {
+        this.backOffParams = backOffParams;
+    }
+
+    public int getMaxAttempts() {
+        return maxAttempts;
+    }
+
+    public void setMaxAttempts(final int maxAttempts) {
+        this.maxAttempts = maxAttempts;
+    }
+}
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 344b0a6..59233a3 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -69,6 +69,8 @@ public class ResourceTO implements EntityTO {
 
     private String accountPolicy;
 
+    private String propagationPolicy;
+
     private String pullPolicy;
 
     private String pushPolicy;
@@ -178,6 +180,14 @@ public class ResourceTO implements EntityTO {
         this.accountPolicy = accountPolicy;
     }
 
+    public String getPropagationPolicy() {
+        return propagationPolicy;
+    }
+
+    public void setPropagationPolicy(final String propagationPolicy) {
+        this.propagationPolicy = propagationPolicy;
+    }
+
     public String getPullPolicy() {
         return pullPolicy;
     }
@@ -293,6 +303,7 @@ public class ResourceTO implements EntityTO {
                 append(provisioningTraceLevel, other.provisioningTraceLevel).
                 append(passwordPolicy, other.passwordPolicy).
                 append(accountPolicy, other.accountPolicy).
+                append(propagationPolicy, other.propagationPolicy).
                 append(pullPolicy, other.pullPolicy).
                 append(pushPolicy, other.pushPolicy).
                 append(authPolicy, other.authPolicy).
@@ -321,6 +332,7 @@ public class ResourceTO implements EntityTO {
                 append(provisioningTraceLevel).
                 append(passwordPolicy).
                 append(accountPolicy).
+                append(propagationPolicy).
                 append(pullPolicy).
                 append(pushPolicy).
                 append(authPolicy).
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
similarity index 59%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
index 3272e79..314a8f7 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
@@ -18,35 +18,18 @@
  */
 package org.apache.syncope.common.lib.types;
 
-public enum PolicyType {
+public enum BackOffStrategy {
+    FIXED("1000"),
+    EXPONENTIAL("100;30000;2"),
+    RANDOM("100;30000;2");
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * How authentication policies should look like.
-     */
-    AUTH,
-    /**
-     * How attribute release policies should look like.
-     */
-    ATTR_RELEASE,
-    /**
-     * How access policies should be defined.
-     */
-    ACCESS,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    private final String defaultBackOffParams;
 
+    BackOffStrategy(final String defaultBackOffParams) {
+        this.defaultBackOffParams = defaultBackOffParams;
+    }
+
+    public String getDefaultBackOffParams() {
+        return defaultBackOffParams;
+    }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index 3272e79..e9a71bd 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -41,6 +41,10 @@ public enum PolicyType {
      */
     ACCESS,
     /**
+     * For handling propagation behavior.
+     */
+    PROPAGATION,
+    /**
      * For handling conflicts resolution during pull.
      */
     PULL,
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
similarity index 56%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
index 3272e79..d4e8172 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
@@ -16,37 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
-
-public enum PolicyType {
-
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * How authentication policies should look like.
-     */
-    AUTH,
-    /**
-     * How attribute release policies should look like.
-     */
-    ATTR_RELEASE,
-    /**
-     * How access policies should be defined.
-     */
-    ACCESS,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+package org.apache.syncope.core.persistence.api.entity.policy;
 
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+public interface PropagationPolicy extends Policy {
+
+    BackOffStrategy getBackOffStrategy();
+
+    void setBackOffStrategy(BackOffStrategy backOffStrategy);
+
+    String getBackOffParams();
+
+    void setBackOffParams(String backOffParams);
+
+    int getMaxAttempts();
+
+    void setMaxAttempts(int maxAttempts);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
index 8ad9c40..86fb038 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -58,6 +59,10 @@ public interface ExternalResource extends ProvidedKeyEntity {
 
     void setPasswordPolicy(PasswordPolicy passwordPolicy);
 
+    PropagationPolicy getPropagationPolicy();
+
+    void setPropagationPolicy(PropagationPolicy propagationPolicy);
+
     PullPolicy getPullPolicy();
 
     void setPullPolicy(PullPolicy pullPolicy);
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 54ecf96..c3c079e 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -43,6 +43,8 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+  <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
+                     maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
   
   <!-- Authentication policies -->
   <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf"
@@ -704,7 +706,8 @@ under the License.
 
   <ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
                     randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
-                    enforceMandatoryCondition="0" overrideCapabilities="0"/>
+                    enforceMandatoryCondition="0" overrideCapabilities="0"
+                    propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
 
   <ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
                     createTraceLevel="ALL" updateTraceLevel="ALL"  deleteTraceLevel="ALL" provisioningTraceLevel="ALL" 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
index b304b8b..769ca55 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
@@ -42,6 +42,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -64,6 +65,8 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
             query.append("accountPolicy");
         } else if (PasswordPolicy.class.isAssignableFrom(policyClass)) {
             query.append("passwordPolicy");
+        } else if (PropagationPolicy.class.isAssignableFrom(policyClass)) {
+            query.append("propagationPolicy");
         } else if (PullPolicy.class.isAssignableFrom(policyClass)) {
             query.append("pullPolicy");
         } else if (PushPolicy.class.isAssignableFrom(policyClass)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
index fe65201..2f349fe 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
@@ -39,6 +40,7 @@ import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
@@ -51,6 +53,8 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
                 ? JPAAccountPolicy.class
                 : PasswordPolicy.class.isAssignableFrom(reference)
                 ? JPAPasswordPolicy.class
+                : PropagationPolicy.class.isAssignableFrom(reference)
+                ? JPAPropagationPolicy.class
                 : PullPolicy.class.isAssignableFrom(reference)
                 ? JPAPullPolicy.class
                 : PushPolicy.class.isAssignableFrom(reference)
@@ -168,11 +172,14 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
         if (!(policy instanceof AuthPolicy)
                 && !(policy instanceof AttrReleasePolicy)
                 && !(policy instanceof AccessPolicy)) {
+
             resourceDAO.findByPolicy(policy).forEach(resource -> {
                 if (policy instanceof AccountPolicy) {
                     resource.setAccountPolicy(null);
                 } else if (policy instanceof PasswordPolicy) {
                     resource.setPasswordPolicy(null);
+                } else if (policy instanceof PropagationPolicy) {
+                    resource.setPropagationPolicy(null);
                 } else if (policy instanceof PullPolicy) {
                     resource.setPullPolicy(null);
                 } else if (policy instanceof PushPolicy) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index b36b473..9fb6c1d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.core.persistence.jpa.dao;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import javax.persistence.NoResultException;
@@ -41,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
 import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 
 public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
 
@@ -145,9 +145,10 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
 
     @Override
     public <T extends Policy> List<Realm> findByPolicy(final T policy) {
-        if (ProvisioningPolicy.class.isAssignableFrom(policy.getClass())) {
-            return Collections.<Realm>emptyList();
+        if (policy instanceof PropagationPolicy || policy instanceof ProvisioningPolicy) {
+            return List.of();
         }
+
         String policyColumn = null;
         if (policy instanceof AccountPolicy) {
             policyColumn = "accountPolicy";
@@ -167,10 +168,10 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
         query.setParameter("policy", policy);
 
         List<Realm> result = new ArrayList<>();
-        query.getResultList().stream().map(realm -> {
+        query.getResultList().forEach(realm -> {
             result.add(realm);
-            return realm;
-        }).forEachOrdered(realm -> result.addAll(findSamePolicyChildren(realm, policy)));
+            result.addAll(findSamePolicyChildren(realm, policy));
+        });
 
         return result;
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
index 2a29d9b..f4595c9 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
@@ -88,7 +88,7 @@ public class JPARelationshipTypeDAO extends AbstractDAO<RelationshipType> implem
             }
             relationship.setLeftEnd(null);
             return relationship;
-        }).forEachOrdered(relationship -> entityManager().remove(relationship));
+        }).forEach(entityManager()::remove);
 
         entityManager().remove(type);
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index 7ecb27a..84be74f 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -317,13 +317,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         findAllResources(user).stream().
                 map(ExternalResource::getAccountPolicy).
                 filter(Objects::nonNull).
-                forEachOrdered(policies::add);
+                forEach(policies::add);
 
         // add realm policies
         realmDAO.findAncestors(user.getRealm()).stream().
                 map(Realm::getAccountPolicy).
                 filter(Objects::nonNull).
-                forEachOrdered(policies::add);
+                forEach(policies::add);
 
         return policies;
     }
@@ -533,7 +533,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(roleKey -> {
+                forEach(roleKey -> {
                     Role role = roleDAO.find(roleKey.toString());
                     if (role == null) {
                         LOG.error("Could not find role {}, even though returned by the native query", roleKey);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 0b1b369..7b04ca6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -25,6 +25,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.AuditConf;
 import org.apache.syncope.core.persistence.api.entity.Batch;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
@@ -46,6 +47,7 @@ import org.apache.syncope.core.persistence.api.entity.Report;
 import org.apache.syncope.core.persistence.api.entity.ReportExec;
 import org.apache.syncope.core.persistence.api.entity.ReportTemplate;
 import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.SRARoute;
 import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
@@ -58,7 +60,12 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
 import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
 import org.apache.syncope.core.persistence.api.entity.auth.AuthProfile;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSPClientApp;
 import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRPClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPEntity;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPEntity;
 import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrUniqueValue;
@@ -70,6 +77,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -109,10 +117,13 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrUni
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrValue;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthProfile;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPACASSPClientApp;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCJWKS;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCRPClientApp;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPEntity;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPClientApp;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPEntity;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPAWAConfigEntry;
@@ -126,6 +137,7 @@ import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
@@ -158,16 +170,6 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrValue;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.entity.SRARoute;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPEntity;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPClientApp;
-import org.apache.syncope.core.persistence.api.entity.auth.CASSPClientApp;
-import org.apache.syncope.core.persistence.api.entity.auth.OIDCRPClientApp;
-import org.apache.syncope.core.persistence.api.entity.AuditConf;
 
 public class JPAEntityFactory implements EntityFactory {
 
@@ -188,6 +190,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAAccountPolicy();
         } else if (reference.equals(PasswordPolicy.class)) {
             result = (E) new JPAPasswordPolicy();
+        } else if (reference.equals(PropagationPolicy.class)) {
+            result = (E) new JPAPropagationPolicy();
         } else if (reference.equals(PushPolicy.class)) {
             result = (E) new JPAPushPolicy();
         } else if (reference.equals(PullPolicy.class)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
index c694eca..c30361b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
@@ -28,6 +28,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 
 public class JPAPolicyUtils implements PolicyUtils {
 
@@ -51,9 +52,6 @@ public class JPAPolicyUtils implements PolicyUtils {
             case PASSWORD:
                 return PasswordPolicy.class;
 
-            case PULL:
-                return PullPolicy.class;
-
             case AUTH:
                 return AuthPolicy.class;
 
@@ -63,6 +61,12 @@ public class JPAPolicyUtils implements PolicyUtils {
             case ACCESS:
                 return AccessPolicy.class;
 
+            case PROPAGATION:
+                return PropagationPolicy.class;
+
+            case PULL:
+                return PullPolicy.class;
+
             case PUSH:
             default:
                 return PushPolicy.class;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
index ec0378e..f13302a 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
 import org.apache.syncope.common.lib.policy.AuthPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
 import org.apache.syncope.common.lib.policy.PolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
 import org.apache.syncope.common.lib.types.PolicyType;
@@ -35,6 +36,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 
@@ -52,6 +54,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.ACCOUNT;
         } else if (policy instanceof PasswordPolicy) {
             type = PolicyType.PASSWORD;
+        } else if (policy instanceof PropagationPolicy) {
+            type = PolicyType.PROPAGATION;
         } else if (policy instanceof PullPolicy) {
             type = PolicyType.PULL;
         } else if (policy instanceof PushPolicy) {
@@ -76,6 +80,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.ACCOUNT;
         } else if (policyClass == PasswordPolicyTO.class) {
             type = PolicyType.PASSWORD;
+        } else if (policyClass == PropagationPolicyTO.class) {
+            type = PolicyType.PROPAGATION;
         } else if (policyClass == PullPolicyTO.class) {
             type = PolicyType.PULL;
         } else if (policyClass == PushPolicyTO.class) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
new file mode 100644
index 0000000..207886f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.syncope.core.persistence.jpa.entity.policy;
+
+import java.util.Optional;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
+
+@Entity
+@Table(name = JPAPropagationPolicy.TABLE)
+public class JPAPropagationPolicy extends AbstractPolicy implements PropagationPolicy {
+
+    private static final long serialVersionUID = 17400846199535L;
+
+    public static final String TABLE = "PropagationPolicy";
+
+    @Enumerated(EnumType.STRING)
+    @NotNull
+    private BackOffStrategy backOffStrategy;
+
+    private String backOffParams;
+
+    @NotNull
+    private Integer maxAttempts = 3;
+
+    @Override
+    public BackOffStrategy getBackOffStrategy() {
+        return backOffStrategy;
+    }
+
+    @Override
+    public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+        this.backOffStrategy = backOffStrategy;
+    }
+
+    @Override
+    public String getBackOffParams() {
+        return backOffParams;
+    }
+
+    @Override
+    public void setBackOffParams(final String backOffParams) {
+        this.backOffParams = backOffParams;
+    }
+
+    @Override
+    public int getMaxAttempts() {
+        return Optional.ofNullable(maxAttempts).orElse(3);
+    }
+
+    @Override
+    public void setMaxAttempts(final int maxAttempts) {
+        this.maxAttempts = maxAttempts;
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
index ef97aa6..d9efbda 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
@@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.jpa.validation.entity.ExternalResourc
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
@@ -64,6 +65,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity;
 import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 
@@ -131,6 +133,9 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     private JPAAccountPolicy accountPolicy;
 
     @ManyToOne(fetch = FetchType.EAGER)
+    private JPAPropagationPolicy propagationPolicy;
+
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAPullPolicy pullPolicy;
 
     @ManyToOne(fetch = FetchType.EAGER)
@@ -311,6 +316,17 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     }
 
     @Override
+    public PropagationPolicy getPropagationPolicy() {
+        return propagationPolicy;
+    }
+
+    @Override
+    public void setPropagationPolicy(final PropagationPolicy propagationPolicy) {
+        checkType(propagationPolicy, JPAPropagationPolicy.class);
+        this.propagationPolicy = (JPAPropagationPolicy) propagationPolicy;
+    }
+
+    @Override
     public PullPolicy getPullPolicy() {
         return pullPolicy;
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
index 785b79f..1607f0d 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
@@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPushCorrelationRuleConf;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
@@ -41,12 +42,14 @@ import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -78,6 +81,12 @@ public class PolicyTest extends AbstractTest {
 
     @Test
     public void findByKey() {
+        PropagationPolicy propagationPolicy = policyDAO.find("89d322db-9878-420c-b49c-67be13df9a12");
+        assertNotNull(propagationPolicy);
+        assertEquals(BackOffStrategy.FIXED, propagationPolicy.getBackOffStrategy());
+        assertEquals("10000", propagationPolicy.getBackOffParams());
+        assertEquals(5, propagationPolicy.getMaxAttempts());
+
         PullPolicy pullPolicy = policyDAO.find("880f8553-069b-4aed-9930-2cd53873f544");
         assertNotNull(pullPolicy);
 
@@ -125,6 +134,10 @@ public class PolicyTest extends AbstractTest {
 
     @Test
     public void findByType() {
+        List<PropagationPolicy> propagationPolicies = policyDAO.find(PropagationPolicy.class);
+        assertNotNull(propagationPolicies);
+        assertFalse(propagationPolicies.isEmpty());
+
         List<PullPolicy> pullPolicies = policyDAO.find(PullPolicy.class);
         assertNotNull(pullPolicies);
         assertFalse(pullPolicies.isEmpty());
@@ -143,10 +156,30 @@ public class PolicyTest extends AbstractTest {
     }
 
     @Test
-    public void create() {
-        PullPolicy policy = entityFactory.newEntity(PullPolicy.class);
-        policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
-        policy.setName("Pull policy");
+    public void createPropagation() {
+        int beforeCount = policyDAO.findAll().size();
+
+        PropagationPolicy propagationPolicy = entityFactory.newEntity(PropagationPolicy.class);
+        propagationPolicy.setName("Propagation policy");
+        propagationPolicy.setMaxAttempts(5);
+        propagationPolicy.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+        propagationPolicy.setBackOffParams(propagationPolicy.getBackOffStrategy().getDefaultBackOffParams());
+
+        propagationPolicy = policyDAO.save(propagationPolicy);
+        assertNotNull(propagationPolicy);
+        assertEquals(5, propagationPolicy.getMaxAttempts());
+        assertEquals(BackOffStrategy.EXPONENTIAL, propagationPolicy.getBackOffStrategy());
+        assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicy.getBackOffParams());
+
+        int afterCount = policyDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+    }
+
+    @Test
+    public void createPull() {
+        PullPolicy pullPolicy = entityFactory.newEntity(PullPolicy.class);
+        pullPolicy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+        pullPolicy.setName("Pull policy");
 
         final String pullURuleName = "net.tirasa.pull.correlation.TirasaURule";
         final String pullGRuleName = "net.tirasa.pull.correlation.TirasaGRule";
@@ -160,9 +193,9 @@ public class PolicyTest extends AbstractTest {
 
         PullCorrelationRuleEntity rule1 = entityFactory.newEntity(PullCorrelationRuleEntity.class);
         rule1.setAnyType(anyTypeDAO.findUser());
-        rule1.setPullPolicy(policy);
+        rule1.setPullPolicy(pullPolicy);
         rule1.setImplementation(impl1);
-        policy.add(rule1);
+        pullPolicy.add(rule1);
 
         Implementation impl2 = entityFactory.newEntity(Implementation.class);
         impl2.setKey(pullGRuleName);
@@ -173,19 +206,67 @@ public class PolicyTest extends AbstractTest {
 
         PullCorrelationRuleEntity rule2 = entityFactory.newEntity(PullCorrelationRuleEntity.class);
         rule2.setAnyType(anyTypeDAO.findGroup());
-        rule2.setPullPolicy(policy);
+        rule2.setPullPolicy(pullPolicy);
         rule2.setImplementation(impl2);
-        policy.add(rule2);
+        pullPolicy.add(rule2);
 
-        policy = policyDAO.save(policy);
+        pullPolicy = policyDAO.save(pullPolicy);
 
-        assertNotNull(policy);
+        assertNotNull(pullPolicy);
         assertEquals(pullURuleName,
-                policy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+                pullPolicy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
         assertEquals(pullGRuleName,
-                policy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+                pullPolicy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+    }
+
+    @Test
+    public void createPush() {
+        PushPolicy pushPolicy = entityFactory.newEntity(PushPolicy.class);
+        pushPolicy.setName("Push policy");
+        pushPolicy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+
+        final String pushURuleName = "net.tirasa.push.correlation.TirasaURule";
+        final String pushGRuleName = "net.tirasa.push.correlation.TirasaGRule";
+
+        Implementation impl1 = entityFactory.newEntity(Implementation.class);
+        impl1.setKey(pushURuleName);
+        impl1.setEngine(ImplementationEngine.JAVA);
+        impl1.setType(IdMImplementationType.PUSH_CORRELATION_RULE);
+        impl1.setBody(PushCorrelationRule.class.getName());
+        impl1 = implementationDAO.save(impl1);
+
+        PushCorrelationRuleEntity rule1 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+        rule1.setAnyType(anyTypeDAO.findUser());
+        rule1.setPushPolicy(pushPolicy);
+        rule1.setImplementation(impl1);
+        pushPolicy.add(rule1);
+
+        Implementation impl2 = entityFactory.newEntity(Implementation.class);
+        impl2.setKey(pushGRuleName);
+        impl2.setEngine(ImplementationEngine.JAVA);
+        impl2.setType(IdMImplementationType.PUSH_CORRELATION_RULE);
+        impl2.setBody(PushCorrelationRule.class.getName());
+        impl2 = implementationDAO.save(impl2);
+
+        PushCorrelationRuleEntity rule2 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+        rule2.setAnyType(anyTypeDAO.findGroup());
+        rule2.setPushPolicy(pushPolicy);
+        rule2.setImplementation(impl2);
+        pushPolicy.add(rule2);
+
+        pushPolicy = policyDAO.save(pushPolicy);
+
+        assertNotNull(pushPolicy);
+        assertEquals(pushURuleName,
+                pushPolicy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+        assertEquals(pushGRuleName,
+                pushPolicy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+    }
 
+    @Test
+    public void createAccess() {
         int beforeCount = policyDAO.findAll().size();
+
         AccessPolicy accessPolicy = entityFactory.newEntity(AccessPolicy.class);
         accessPolicy.setName("AttrReleasePolicyAllowEverything");
 
@@ -200,8 +281,12 @@ public class PolicyTest extends AbstractTest {
 
         int afterCount = policyDAO.findAll().size();
         assertEquals(afterCount, beforeCount + 1);
+    }
+
+    @Test
+    public void createAuth() {
+        int beforeCount = policyDAO.findAll().size();
 
-        beforeCount = policyDAO.findAll().size();
         AuthPolicy authPolicy = entityFactory.newEntity(AuthPolicy.class);
         authPolicy.setName("AuthPolicyTest");
 
@@ -215,10 +300,14 @@ public class PolicyTest extends AbstractTest {
         assertNotNull(authPolicy);
         assertNotNull(authPolicy.getKey());
 
-        afterCount = policyDAO.findAll().size();
+        int afterCount = policyDAO.findAll().size();
         assertEquals(afterCount, beforeCount + 1);
+    }
+
+    @Test
+    public void createAttrRelease() {
+        int beforeCount = policyDAO.findAll().size();
 
-        beforeCount = policyDAO.findAll().size();
         AttrReleasePolicy attrReleasePolicy = entityFactory.newEntity(AttrReleasePolicy.class);
         attrReleasePolicy.setName("AttrReleasePolicyAllowEverything");
         attrReleasePolicy.setStatus(Boolean.TRUE);
@@ -235,7 +324,7 @@ public class PolicyTest extends AbstractTest {
         assertNotNull(attrReleasePolicy.getStatus());
         assertNotNull(((DefaultAttrReleasePolicyConf) attrReleasePolicy.getConf()).getAllowedAttrs());
 
-        afterCount = policyDAO.findAll().size();
+        int afterCount = policyDAO.findAll().size();
         assertEquals(afterCount, beforeCount + 1);
     }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
index c857676..05ab3f0 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
@@ -181,7 +181,7 @@ public class GroupTest extends AbstractTest {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(actualKey -> {
+                forEach(actualKey -> {
                     Group group = groupDAO.find(actualKey.toString());
                     if (group == null) {
                     } else if (!result.contains(group)) {
@@ -282,7 +282,7 @@ public class GroupTest extends AbstractTest {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(actualKey -> {
+                forEach(actualKey -> {
                     Group group = groupDAO.find(actualKey.toString());
                     if (group == null) {
                     } else if (!result.contains(group)) {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
index 32bcd63..cde0f36 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
@@ -37,7 +37,6 @@ import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
-import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
@@ -210,7 +209,7 @@ public class ResourceTest extends AbstractTest {
         List<User> users = userDAO.findByResource(resource);
         assertNotNull(users);
 
-        Set<String> userKeys = users.stream().map(Entity::getKey).collect(Collectors.toSet());
+        Set<String> userKeys = users.stream().map(User::getKey).collect(Collectors.toSet());
         // -------------------------------------
 
         // Get tasks
@@ -229,13 +228,11 @@ public class ResourceTest extends AbstractTest {
         assertNull(actual);
 
         // resource must be not referenced any more from users
-        userKeys.stream().
-                map(key -> userDAO.find(key)).
-                map(actualUser -> {
-                    assertNotNull(actualUser);
-                    return actualUser;
-                }).forEachOrdered((actualUser) -> userDAO.findAllResources(actualUser).
-                        forEach(res -> assertFalse(res.getKey().equalsIgnoreCase(resource.getKey()))));
+        userKeys.stream().map(userDAO::find).forEach(user -> {
+            assertNotNull(user);
+            userDAO.findAllResources(user).
+                    forEach(r -> assertFalse(r.getKey().equalsIgnoreCase(resource.getKey())));
+        });
 
         // resource must be not referenced any more from the connector
         ConnInstance actualConnector = connInstanceDAO.find(connector.getKey());
@@ -260,7 +257,7 @@ public class ResourceTest extends AbstractTest {
         List<? extends MappingItem> items = ldap.getProvision(anyTypeDAO.findGroup()).get().getMapping().getItems();
         assertNotNull(items);
         assertFalse(items.isEmpty());
-        List<String> itemKeys = items.stream().map(Entity::getKey).collect(Collectors.toList());
+        List<String> itemKeys = items.stream().map(MappingItem::getKey).collect(Collectors.toList());
 
         Provision groupProvision = ldap.getProvision(anyTypeDAO.findGroup()).get();
         virSchemaDAO.findByProvision(groupProvision).
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index d4684d3..817ce37 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -43,7 +43,9 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
-  
+  <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
+                     maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
+
   <!-- Authentication policies -->
   <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf"
               jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
@@ -791,7 +793,8 @@ under the License.
 
   <ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
                     randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
-                    enforceMandatoryCondition="0" overrideCapabilities="0"/>
+                    enforceMandatoryCondition="0" overrideCapabilities="0"
+                    propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
 
   <ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
                     createTraceLevel="ALL" updateTraceLevel="ALL"  deleteTraceLevel="ALL" provisioningTraceLevel="ALL" 
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
index cd20967..470ba42 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -43,6 +43,13 @@ public interface PropagationTaskExecutor {
     String MANDATORY_NULL_OR_EMPTY_ATTR_NAME = "__MANDATORY_NULL_OR_EMPTY__";
 
     /**
+     * Remove any RetryTemplate defined for the given External Resource from local cache.
+     *
+     * @param resource External Resource name
+     */
+    void expireRetryTemplate(String resource);
+
+    /**
      * Execute the given task and returns the generated {@link TaskExec}.
      *
      * @param taskInfo to be executed
@@ -62,6 +69,5 @@ public interface PropagationTaskExecutor {
      * @param executor the executor of this task
      * @return reporter to report propagation execution status
      */
-    PropagationReporter execute(
-            Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync, String executor);
+    PropagationReporter execute(Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync, String executor);
 }
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index d850ded..aaff252 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -49,6 +49,10 @@ under the License.
       <artifactId>spring-context-support</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.springframework.retry</groupId>
+      <artifactId>spring-retry</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
     </dependency>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 7581dbb..03613fb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -219,8 +219,9 @@ public class ProvisioningContext {
 
     /**
      * Annotated as {@code @Primary} because it will be used by {@code @Async} in {@link AsyncConnectorFacade}.
+     *
      * @param provisioningProperties configuration properties
-     * 
+     *
      * @return executor
      */
     @Bean
@@ -238,8 +239,10 @@ public class ProvisioningContext {
 
     @Bean
     public AsyncConfigurer asyncConfigurer(@Qualifier("asyncConnectorFacadeExecutor")
-                                           final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor) {
+            final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor) {
+
         return new AsyncConfigurer() {
+
             @Override
             public Executor getAsyncExecutor() {
                 return asyncConnectorFacadeExecutor;
@@ -255,7 +258,7 @@ public class ProvisioningContext {
      */
     @Bean
     public ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor(
-        final ProvisioningProperties provisioningProperties) {
+            final ProvisioningProperties provisioningProperties) {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
         executor.setCorePoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getCorePoolSize());
         executor.setMaxPoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getMaxPoolSize());
@@ -285,7 +288,7 @@ public class ProvisioningContext {
     @Lazy(false)
     @Bean
     public SchedulerFactoryBean scheduler(final ApplicationContext ctx,
-                                          final ProvisioningProperties provisioningProperties) {
+            final ProvisioningProperties provisioningProperties) {
         SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
         scheduler.setAutoStartup(true);
         scheduler.setApplicationContext(ctx);
@@ -347,7 +350,7 @@ public class ProvisioningContext {
     @ConditionalOnMissingBean
     @Bean
     public JavaMailSender mailSender(final ProvisioningProperties provisioningProperties)
-        throws IllegalArgumentException, IOException {
+            throws IllegalArgumentException, IOException {
         JavaMailSenderImpl mailSender = new JavaMailSenderImpl() {
 
             @Override
@@ -582,7 +585,7 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-        public IntAttrNameParser intAttrNameParser(
+    public IntAttrNameParser intAttrNameParser(
             final AnyUtilsFactory anyUtilsFactory,
             final PlainSchemaDAO plainSchemaDAO,
             final DerSchemaDAO derSchemaDAO,
@@ -764,16 +767,21 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public NotificationJob notificationJob(final NotificationJobDelegate delegate,
-                                           final DomainHolder domainHolder,
-                                           final SecurityProperties securityProperties) {
+    public NotificationJob notificationJob(
+            final NotificationJobDelegate delegate,
+            final DomainHolder domainHolder,
+            final SecurityProperties securityProperties) {
+
         return new NotificationJob(securityProperties, domainHolder, delegate);
     }
 
     @ConditionalOnMissingBean
     @Bean
-    public ReportJobDelegate reportJobDelegate(final ReportDAO reportDAO, final ReportExecDAO reportExecDAO,
-                                               final EntityFactory entityFactory) {
+    public ReportJobDelegate reportJobDelegate(
+            final ReportDAO reportDAO,
+            final ReportExecDAO reportExecDAO,
+            final EntityFactory entityFactory) {
+
         return new DefaultReportJobDelegate(reportDAO, reportExecDAO, entityFactory);
     }
 
@@ -870,8 +878,10 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ApplicationDataBinder applicationDataBinder(final ApplicationDAO applicationDAO,
-                                                       final EntityFactory entityFactory) {
+    public ApplicationDataBinder applicationDataBinder(
+            final ApplicationDAO applicationDAO,
+            final EntityFactory entityFactory) {
+
         return new ApplicationDataBinderImpl(applicationDAO, entityFactory);
     }
 
@@ -895,8 +905,10 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ClientAppDataBinder clientAppDataBinder(final PolicyDAO policyDAO,
-                                                   final EntityFactory entityFactory) {
+    public ClientAppDataBinder clientAppDataBinder(
+            final PolicyDAO policyDAO,
+            final EntityFactory entityFactory) {
+
         return new ClientAppDataBinderImpl(policyDAO, entityFactory);
     }
 
@@ -913,16 +925,22 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public DelegationDataBinder delegationDataBinder(final UserDAO userDAO, final RoleDAO roleDAO,
-                                                     final EntityFactory entityFactory) {
+    public DelegationDataBinder delegationDataBinder(
+            final UserDAO userDAO,
+            final RoleDAO roleDAO,
+            final EntityFactory entityFactory) {
+
         return new DelegationDataBinderImpl(userDAO, roleDAO, entityFactory);
     }
 
     @ConditionalOnMissingBean
     @Bean
-    public DynRealmDataBinder dynRealmDataBinder(final AnyTypeDAO anyTypeDAO, final DynRealmDAO dynRealmDAO,
-                                                 final SearchCondVisitor searchCondVisitor,
-                                                 final EntityFactory entityFactory) {
+    public DynRealmDataBinder dynRealmDataBinder(
+            final AnyTypeDAO anyTypeDAO,
+            final DynRealmDAO dynRealmDAO,
+            final SearchCondVisitor searchCondVisitor,
+            final EntityFactory entityFactory) {
+
         return new DynRealmDataBinderImpl(anyTypeDAO, dynRealmDAO, entityFactory, searchCondVisitor);
     }
 
@@ -1066,7 +1084,8 @@ public class ProvisioningContext {
             final AnyTypeClassDAO anyTypeClassDAO,
             final ImplementationDAO implementationDAO,
             final PlainSchemaDAO plainSchemaDAO,
-            final IntAttrNameParser intAttrNameParser) {
+            final IntAttrNameParser intAttrNameParser,
+            final PropagationTaskExecutor propagationTaskExecutor) {
 
         return new ResourceDataBinderImpl(
                 anyTypeDAO,
@@ -1077,7 +1096,8 @@ public class ProvisioningContext {
                 implementationDAO,
                 plainSchemaDAO,
                 entityFactory,
-                intAttrNameParser);
+                intAttrNameParser,
+                propagationTaskExecutor);
     }
 
     @ConditionalOnMissingBean
@@ -1224,8 +1244,10 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public WAConfigDataBinder waConfigDataBinder(final WAConfigDAO waConfigDAO,
-                                                 final EntityFactory entityFactory) {
+    public WAConfigDataBinder waConfigDataBinder(
+            final WAConfigDAO waConfigDAO,
+            final EntityFactory entityFactory) {
+
         return new WAConfigDataBinderImpl(waConfigDAO, entityFactory);
     }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
index b42ac2d..afbe9ca 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
@@ -27,6 +27,7 @@ import org.apache.syncope.common.lib.policy.PushPolicyTO;
 import org.apache.syncope.common.lib.policy.AccessPolicyTO;
 import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
 import org.apache.syncope.common.lib.policy.AuthPolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
@@ -43,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -138,6 +140,17 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
                     accountPolicy.add(resource);
                 }
             });
+        } else if (policyTO instanceof PropagationPolicyTO) {
+            if (result == null) {
+                result = (T) entityFactory.newEntity(PropagationPolicy.class);
+            }
+
+            PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(result);
+            PropagationPolicyTO propagationPolicyTO = PropagationPolicyTO.class.cast(policyTO);
+
+            propagationPolicy.setBackOffStrategy(propagationPolicyTO.getBackOffStrategy());
+            propagationPolicy.setBackOffParams(propagationPolicyTO.getBackOffParams());
+            propagationPolicy.setMaxAttempts(propagationPolicyTO.getMaxAttempts());
         } else if (policyTO instanceof PullPolicyTO) {
             if (result == null) {
                 result = (T) entityFactory.newEntity(PullPolicy.class);
@@ -169,8 +182,8 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
                 }
             });
             // remove all rules not contained in the TO
-            pullPolicy.getCorrelationRules().removeIf(anyFilter
-                    -> !pullPolicyTO.getCorrelationRules().containsKey(anyFilter.getAnyType().getKey()));
+            pullPolicy.getCorrelationRules().removeIf(anyFilter -> !pullPolicyTO.getCorrelationRules().
+                    containsKey(anyFilter.getAnyType().getKey()));
         } else if (policyTO instanceof PushPolicyTO) {
             if (result == null) {
                 result = (T) entityFactory.newEntity(PushPolicy.class);
@@ -289,6 +302,14 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
 
             accountPolicyTO.getPassthroughResources().addAll(
                     accountPolicy.getResources().stream().map(Entity::getKey).collect(Collectors.toList()));
+        } else if (policy instanceof PropagationPolicy) {
+            PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(policy);
+            PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+            policyTO = (T) propagationPolicyTO;
+
+            propagationPolicyTO.setBackOffStrategy(propagationPolicy.getBackOffStrategy());
+            propagationPolicyTO.setBackOffParams(propagationPolicy.getBackOffParams());
+            propagationPolicyTO.setMaxAttempts(propagationPolicy.getMaxAttempts());
         } else if (policy instanceof PullPolicy) {
             PullPolicy pullPolicy = PullPolicy.class.cast(policy);
             PullPolicyTO pullPolicyTO = new PullPolicyTO();
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 5cf60a9..6cbf8cd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -60,6 +60,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
@@ -69,6 +70,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -95,6 +97,8 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
 
     protected final IntAttrNameParser intAttrNameParser;
 
+    protected final PropagationTaskExecutor propagationTaskExecutor;
+
     public ResourceDataBinderImpl(
             final AnyTypeDAO anyTypeDAO,
             final ConnInstanceDAO connInstanceDAO,
@@ -104,7 +108,8 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
             final ImplementationDAO implementationDAO,
             final PlainSchemaDAO plainSchemaDAO,
             final EntityFactory entityFactory,
-            final IntAttrNameParser intAttrNameParser) {
+            final IntAttrNameParser intAttrNameParser,
+            final PropagationTaskExecutor propagationTaskExecutor) {
 
         this.anyTypeDAO = anyTypeDAO;
         this.connInstanceDAO = connInstanceDAO;
@@ -115,6 +120,7 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         this.plainSchemaDAO = plainSchemaDAO;
         this.entityFactory = entityFactory;
         this.intAttrNameParser = intAttrNameParser;
+        this.propagationTaskExecutor = propagationTaskExecutor;
     }
 
     @Override
@@ -358,6 +364,14 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
                 ? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
 
+        if (resource.getPropagationPolicy() != null
+                && !resource.getPropagationPolicy().getKey().equals(resourceTO.getPropagationPolicy())) {
+
+            propagationTaskExecutor.expireRetryTemplate(resource.getKey());
+        }
+        resource.setPropagationPolicy(resourceTO.getPropagationPolicy() == null
+                ? null : (PropagationPolicy) policyDAO.find(resourceTO.getPropagationPolicy()));
+
         resource.setPullPolicy(resourceTO.getPullPolicy() == null
                 ? null : (PullPolicy) policyDAO.find(resourceTO.getPullPolicy()));
 
@@ -688,6 +702,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resourceTO.setAccountPolicy(resource.getAccountPolicy() == null
                 ? null : resource.getAccountPolicy().getKey());
 
+        resourceTO.setPropagationPolicy(resource.getPropagationPolicy() == null
+                ? null : resource.getPropagationPolicy().getKey());
+
         resourceTO.setPullPolicy(resource.getPullPolicy() == null
                 ? null : resource.getPullPolicy().getKey());
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 682070d..e6daff8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -20,7 +20,8 @@ package org.apache.syncope.core.provisioning.java.propagation;
 
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -61,7 +62,6 @@ import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnectorManager;
 import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
@@ -79,6 +79,12 @@ import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.retry.RetryException;
+import org.springframework.retry.backoff.ExponentialBackOffPolicy;
+import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
 @Transactional(rollbackFor = { Throwable.class })
@@ -86,6 +92,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
     protected static final Logger LOG = LoggerFactory.getLogger(PropagationTaskExecutor.class);
 
+    protected final Map<String, RetryTemplate> retryTemplates = Collections.synchronizedMap(new HashMap<>());
+
     protected final ConnectorManager connectorManager;
 
     protected final ConnObjectUtils connObjectUtils;
@@ -146,6 +154,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         this.outboundMatcher = outboundMatcher;
     }
 
+    @Override
+    public void expireRetryTemplate(final String resource) {
+        retryTemplates.remove(resource);
+    }
+
     protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
         List<PropagationActions> result = new ArrayList<>();
 
@@ -298,6 +311,94 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         return task;
     }
 
+    protected Optional<RetryTemplate> retryTemplate(final ExternalResource resource) {
+        RetryTemplate retryTemplate = null;
+
+        if (resource.getPropagationPolicy() != null) {
+            retryTemplate = retryTemplates.get(resource.getKey());
+            if (retryTemplate == null) {
+                retryTemplate = new RetryTemplate();
+
+                SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+                retryPolicy.setMaxAttempts(resource.getPropagationPolicy().getMaxAttempts());
+                retryTemplate.setRetryPolicy(retryPolicy);
+
+                String[] params = resource.getPropagationPolicy().getBackOffParams().split(";");
+
+                switch (resource.getPropagationPolicy().getBackOffStrategy()) {
+                    case EXPONENTIAL:
+                        ExponentialBackOffPolicy eBackOffPolicy = new ExponentialBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                eBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+                        }
+                        if (params.length > 1) {
+                            try {
+                                eBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[1], e);
+                            }
+                        }
+                        if (params.length > 2) {
+                            try {
+                                eBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to double: {}", params[2], e);
+                            }
+                        }
+                        retryTemplate.setBackOffPolicy(eBackOffPolicy);
+                        break;
+
+                    case RANDOM:
+                        ExponentialRandomBackOffPolicy erBackOffPolicy = new ExponentialRandomBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                erBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+                        }
+                        if (params.length > 1) {
+                            try {
+                                erBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[1], e);
+                            }
+                        }
+                        if (params.length > 2) {
+                            try {
+                                erBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to double: {}", params[2], e);
+                            }
+                        }
+                        retryTemplate.setBackOffPolicy(erBackOffPolicy);
+                        break;
+
+                    case FIXED:
+                    default:
+                        FixedBackOffPolicy fBackOffPolicy = new FixedBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                fBackOffPolicy.setBackOffPeriod(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+
+                        }
+                        retryTemplate.setBackOffPolicy(fBackOffPolicy);
+                }
+
+                retryTemplates.put(resource.getKey(), retryTemplate);
+            }
+        }
+
+        return Optional.ofNullable(retryTemplate);
+    }
+
     @Override
     public TaskExec execute(
             final PropagationTaskInfo taskInfo,
@@ -306,6 +407,25 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
         PropagationTask task = buildTask(taskInfo);
 
+        return retryTemplate(task.getResource()).map(rt -> rt.execute(context -> {
+            LOG.debug("#{} Propagation attempt", context.getRetryCount());
+
+            TaskExec exec = doExecute(taskInfo, task, reporter, executor);
+            if (context.getRetryCount() < task.getResource().getPropagationPolicy().getMaxAttempts() - 1
+                    && !ExecStatus.SUCCESS.name().equals(exec.getStatus())) {
+
+                throw new RetryException("Attempt #" + context.getRetryCount() + " failed");
+            }
+            return exec;
+        })).orElseGet(() -> doExecute(taskInfo, task, reporter, executor));
+    }
+
+    protected TaskExec doExecute(
+            final PropagationTaskInfo taskInfo,
+            final PropagationTask task,
+            final PropagationReporter reporter,
+            final String executor) {
+
         Connector connector = taskInfo.getConnector() == null
                 ? connectorManager.getConnector(task.getResource())
                 : taskInfo.getConnector();
@@ -314,9 +434,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
         OffsetDateTime start = OffsetDateTime.now();
 
-        TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setStatus(ExecStatus.CREATED.name());
-        execution.setExecutor(executor);
+        TaskExec exec = entityFactory.newEntity(TaskExec.class);
+        exec.setStatus(ExecStatus.CREATED.name());
+        exec.setExecutor(executor);
 
         String taskExecutionMessage = null;
         String failureReason = null;
@@ -363,7 +483,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 default:
             }
 
-            execution.setStatus(propagationAttempted.get()
+            exec.setStatus(propagationAttempted.get()
                     ? ExecStatus.SUCCESS.name()
                     : ExecStatus.NOT_ATTEMPTED.name());
 
@@ -390,14 +510,14 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             }
 
             try {
-                execution.setStatus(ExecStatus.FAILURE.name());
+                exec.setStatus(ExecStatus.FAILURE.name());
             } catch (Exception wft) {
-                LOG.error("While executing KO action on {}", execution, wft);
+                LOG.error("While executing KO action on {}", exec, wft);
             }
 
             propagationAttempted.set(true);
 
-            actions.forEach(action -> action.onError(task, execution, e));
+            actions.forEach(action -> action.onError(task, exec, e));
         } finally {
             // Try to read remote object AFTER any actual operation
             if (connector != null) {
@@ -424,17 +544,17 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                         build();
             }
 
-            execution.setStart(start);
-            execution.setMessage(taskExecutionMessage);
-            execution.setEnd(OffsetDateTime.now());
+            exec.setStart(start);
+            exec.setMessage(taskExecutionMessage);
+            exec.setEnd(OffsetDateTime.now());
 
-            LOG.debug("Execution finished: {}", execution);
+            LOG.debug("Execution finished: {}", exec);
 
-            if (hasToBeregistered(task, execution)) {
-                LOG.debug("Execution to be stored: {}", execution);
+            if (hasToBeregistered(task, exec)) {
+                LOG.debug("Execution to be stored: {}", exec);
 
-                execution.setTask(task);
-                task.add(execution);
+                exec.setTask(task);
+                task.add(exec);
 
                 taskDAO.save(task);
             }
@@ -447,7 +567,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                                     ? outboundMatcher.getFIQL(beforeObj, provision)
                                     : null;
             reporter.onSuccessOrNonPriorityResourceFailures(taskInfo,
-                    ExecStatus.valueOf(execution.getStatus()),
+                    ExecStatus.valueOf(exec.getStatus()),
                     failureReason,
                     fiql,
                     beforeObj,
@@ -455,7 +575,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         }
 
         for (PropagationActions action : actions) {
-            action.after(task, execution, afterObj);
+            action.after(task, exec, afterObj);
         }
         // SYNCOPE-1136
         String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase();
@@ -470,7 +590,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 operation);
 
         if (notificationsAvailable || auditRequested) {
-            ExecTO execTO = taskDataBinder.getExecTO(execution);
+            ExecTO execTO = taskDataBinder.getExecTO(exec);
             notificationManager.createTasks(
                     AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.PROPAGATION,
@@ -494,13 +614,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     taskInfo);
         }
 
-        return execution;
+        return exec;
     }
 
-    protected abstract void doExecute(
-            Collection<PropagationTaskInfo> taskInfos, PropagationReporter reporter, boolean nullPriorityAsync,
-            String executor);
-
     protected TaskExec rejected(
             final PropagationTaskInfo taskInfo,
             final String rejectReason,
@@ -536,23 +652,6 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         return execution;
     }
 
-    @Override
-    public PropagationReporter execute(
-            final Collection<PropagationTaskInfo> taskInfos,
-            final boolean nullPriorityAsync,
-            final String executor) {
-
-        PropagationReporter reporter = new DefaultPropagationReporter();
-        try {
-            doExecute(taskInfos, reporter, nullPriorityAsync, executor);
-        } catch (PropagationException e) {
-            LOG.error("Error propagation priority resource", e);
-            reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
-        }
-
-        return reporter;
-    }
-
     /**
      * Check whether an execution has to be stored, for a given task.
      *
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index c5a1454..f1d1218 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -120,72 +120,79 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
     }
 
     @Override
-    protected void doExecute(
+    public PropagationReporter execute(
             final Collection<PropagationTaskInfo> taskInfos,
-            final PropagationReporter reporter,
             final boolean nullPriorityAsync,
             final String executor) {
 
-        List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
-                filter(task -> task.getExternalResource().getPropagationPriority() != null).
-                sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
-                collect(Collectors.toList());
-        LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
-
-        List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
-                filter(task -> !prioritizedTasks.contains(task)).
-                collect(Collectors.toList());
-        LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
-
-        // first process priority resources sequentially and fail as soon as any propagation failure is reported
-        prioritizedTasks.forEach(task -> {
-            TaskExec execution = null;
-            ExecStatus execStatus;
-            String errorMessage = null;
-            try {
-                execution = newPropagationTaskCallable(task, reporter, executor).call();
-                execStatus = ExecStatus.valueOf(execution.getStatus());
-            } catch (Exception e) {
-                LOG.error("Unexpected exception", e);
-                execStatus = ExecStatus.FAILURE;
-                errorMessage = e.getMessage();
-            }
-            if (execStatus != ExecStatus.SUCCESS) {
-                throw new PropagationException(
-                        task.getResource(),
-                        Optional.ofNullable(execution).map(Exec::getMessage).orElse(errorMessage));
-            }
-        });
-
-        // then process non-priority resources concurrently...
-        if (!concurrentTasks.isEmpty()) {
-            CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(taskExecutor);
-            List<Future<TaskExec>> futures = new ArrayList<>();
-
-            concurrentTasks.forEach(taskInfo -> {
+        PropagationReporter reporter = new DefaultPropagationReporter();
+        try {
+            List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
+                    filter(task -> task.getExternalResource().getPropagationPriority() != null).
+                    sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
+                    collect(Collectors.toList());
+            LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
+
+            List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
+                    filter(task -> !prioritizedTasks.contains(task)).
+                    collect(Collectors.toList());
+            LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
+
+            // first process priority resources sequentially and fail as soon as any propagation failure is reported
+            prioritizedTasks.forEach(task -> {
+                TaskExec exec = null;
+                ExecStatus execStatus;
+                String errorMessage = null;
                 try {
-                    futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter, executor)));
-
-                    if (nullPriorityAsync) {
-                        reporter.onSuccessOrNonPriorityResourceFailures(
-                                taskInfo, ExecStatus.CREATED, null, null, null, null);
-                    }
+                    exec = newPropagationTaskCallable(task, reporter, executor).call();
+                    execStatus = ExecStatus.valueOf(exec.getStatus());
                 } catch (Exception e) {
-                    LOG.error("While submitting task for async execution: {}", taskInfo, e);
-                    rejected(taskInfo, e.getMessage(), reporter, executor);
+                    LOG.error("Unexpected exception", e);
+                    execStatus = ExecStatus.FAILURE;
+                    errorMessage = e.getMessage();
+                }
+                if (execStatus != ExecStatus.SUCCESS) {
+                    throw new PropagationException(
+                            task.getResource(),
+                            Optional.ofNullable(exec).map(Exec::getMessage).orElse(errorMessage));
                 }
             });
 
-            // ...waiting for all callables to complete, if async processing was not required
-            if (!nullPriorityAsync) {
-                futures.forEach(future -> {
+            // then process non-priority resources concurrently...
+            if (!concurrentTasks.isEmpty()) {
+                CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(taskExecutor);
+                List<Future<TaskExec>> futures = new ArrayList<>();
+
+                concurrentTasks.forEach(taskInfo -> {
                     try {
-                        future.get();
+                        futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter, executor)));
+
+                        if (nullPriorityAsync) {
+                            reporter.onSuccessOrNonPriorityResourceFailures(
+                                    taskInfo, ExecStatus.CREATED, null, null, null, null);
+                        }
                     } catch (Exception e) {
-                        LOG.error("Unexpected exception", e);
+                        LOG.error("While submitting task for async execution: {}", taskInfo, e);
+                        rejected(taskInfo, e.getMessage(), reporter, executor);
                     }
                 });
+
+                // ...waiting for all callables to complete, if async processing was not required
+                if (!nullPriorityAsync) {
+                    futures.forEach(future -> {
+                        try {
+                            future.get();
+                        } catch (Exception e) {
+                            LOG.error("Unexpected exception", e);
+                        }
+                    });
+                }
             }
+        } catch (PropagationException e) {
+            LOG.error("Error propagation priority resource", e);
+            reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
         }
+
+        return reporter;
     }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
index 7e37ba2..9cbe4bf 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
@@ -149,7 +149,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
     }
 
     private static void createPullPolicy(String name) {
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:5:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:6:link");
         TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
         TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
 
@@ -244,7 +244,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
     }
 
     private static void deletePullPolicy(String name) {
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:5:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:6:link");
         Component component = findComponentByProp("name",
                 "body:content:tabbedPanel:panel:container:content:"
                 + "searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", name);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index 8693080..7f250a1 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -58,6 +58,8 @@ import java.util.Set;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.policy.AuthPolicyTO;
 import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 
 public class PolicyITCase extends AbstractITCase {
 
@@ -117,10 +119,13 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void listByType() {
-        List<PullPolicyTO> policyTOs = policyService.list(PolicyType.PULL);
+        List<PropagationPolicyTO> propagationPolicies = policyService.list(PolicyType.PROPAGATION);
+        assertNotNull(propagationPolicies);
+        assertFalse(propagationPolicies.isEmpty());
 
-        assertNotNull(policyTOs);
-        assertFalse(policyTOs.isEmpty());
+        List<PullPolicyTO> pullPolicies = policyService.list(PolicyType.PULL);
+        assertNotNull(pullPolicies);
+        assertFalse(pullPolicies.isEmpty());
     }
 
     @Test
@@ -142,6 +147,16 @@ public class PolicyITCase extends AbstractITCase {
     }
 
     @Test
+    public void getPropagationPolicy() {
+        PropagationPolicyTO policyTO =
+                policyService.read(PolicyType.PROPAGATION, "89d322db-9878-420c-b49c-67be13df9a12");
+
+        assertNotNull(policyTO);
+        assertTrue(policyTO.getUsedByResources().contains(RESOURCE_NAME_DBSCRIPTED));
+        assertTrue(policyTO.getUsedByRealms().isEmpty());
+    }
+
+    @Test
     public void getPullPolicy() {
         PullPolicyTO policyTO = policyService.read(PolicyType.PULL, "66691e96-285f-4464-bc19-e68384ea4c85");
 
@@ -151,8 +166,7 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void getAuthPolicy() {
-        AuthPolicyTO policyTO =
-                policyService.read(PolicyType.AUTH, "659b9906-4b6e-4bc0-aca0-6809dff346d4");
+        AuthPolicyTO policyTO = policyService.read(PolicyType.AUTH, "659b9906-4b6e-4bc0-aca0-6809dff346d4");
 
         assertNotNull(policyTO);
         assertTrue(policyTO.getUsedByRealms().isEmpty());
@@ -160,8 +174,7 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void getAccessPolicy() {
-        AccessPolicyTO policyTO =
-                policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
+        AccessPolicyTO policyTO = policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
 
         assertNotNull(policyTO);
         assertTrue(policyTO.getUsedByRealms().isEmpty());
@@ -178,6 +191,14 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void create() throws IOException {
+        PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+        propagationPolicyTO.setName("propagation policy name");
+        propagationPolicyTO.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+        propagationPolicyTO = createPolicy(PolicyType.PROPAGATION, propagationPolicyTO);
+        assertNotNull(propagationPolicyTO);
+        assertEquals(3, propagationPolicyTO.getMaxAttempts());
+        assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicyTO.getBackOffParams());
+
         PullPolicyTO pullPolicyTO = createPolicy(PolicyType.PULL, buildPullPolicyTO());
         assertNotNull(pullPolicyTO);
         assertEquals("TestPullRule", pullPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
@@ -186,8 +207,7 @@ public class PolicyITCase extends AbstractITCase {
         assertNotNull(pushPolicyTO);
         assertEquals("TestPushRule", pushPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
 
-        AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH,
-                buildAuthPolicyTO("LdapAuthentication1"));
+        AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH, buildAuthPolicyTO("LdapAuthentication1"));
         assertNotNull(authPolicyTO);
         assertEquals("Test Authentication policy", authPolicyTO.getName());
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index 3b36ed7..6c46c8d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.fit.core;
 
+import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -42,12 +43,14 @@ import org.apache.syncope.common.lib.request.AttrPatch;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.request.UserUR;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.ws.rs.core.GenericType;
 import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.TaskTO;
@@ -96,6 +99,8 @@ import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.BeforeAll;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
 
 public class PropagationTaskITCase extends AbstractTaskITCase {
 
@@ -311,6 +316,41 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
     }
 
     @Test
+    public void propagationPolicy() throws InterruptedException {
+        SyncopeClient.nullPriorityAsync(anyObjectService, true);
+
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        jdbcTemplate.execute("ALTER TABLE TESTPRINTER ADD COLUMN MAND_VALUE VARCHAR(1)");
+        jdbcTemplate.execute("UPDATE TESTPRINTER SET MAND_VALUE='C'");
+        jdbcTemplate.execute("ALTER TABLE TESTPRINTER ALTER COLUMN MAND_VALUE VARCHAR(1) NOT NULL");
+        try {
+            String entityKey = createAnyObject(AnyObjectITCase.getSample("propagationPolicy")).getEntity().getKey();
+
+            Thread.sleep(1000);
+            jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+
+            PagedResult<PropagationTaskTO> propagations = await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).until(
+                    () -> taskService.search(
+                            new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_DBSCRIPTED).
+                                    anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(entityKey).build()),
+                    p -> p.getTotalCount() > 0);
+
+            propagations.getResult().get(0).getExecutions().stream().
+                    anyMatch(e -> ExecStatus.FAILURE.name().equals(e.getStatus()));
+            propagations.getResult().get(0).getExecutions().stream().
+                    anyMatch(e -> ExecStatus.SUCCESS.name().equals(e.getStatus()));
+        } finally {
+            SyncopeClient.nullPriorityAsync(anyObjectService, false);
+
+            try {
+                jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+            } catch (DataAccessException e) {
+                // ignore
+            }
+        }
+    }
+
+    @Test
     public void issueSYNCOPE741() {
         for (int i = 0; i < 3; i++) {
             taskService.execute(new ExecSpecs.Builder().
diff --git a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
index cda13af..951dcc9 100644
--- a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
@@ -24,18 +24,18 @@ technology or repository, in the context of a certain <<policies-authentication,
 Several authentication modules are provided:
 
 * Principal Authentication:
-    ** https://apereo.github.io/cas/6.4.x/authentication/Database-Authentication.html[Database^]
-    ** https://apereo.github.io/cas/6.4.x/authentication/JAAS-Authentication.html[JAAS^]
-    ** https://apereo.github.io/cas/6.4.x/authentication/LDAP-Authentication.html[LDAP^]
-    ** https://apereo.github.io/cas/6.4.x/integration/Delegate-Authentication.html[OpenID Connect^]
-    ** https://apereo.github.io/cas/6.4.x/mfa/RADIUS-Authentication.html[Radius^]
-    ** https://apereo.github.io/cas/6.4.x/authentication/Syncope-Authentication.html[Static^]
-    ** https://apereo.github.io/cas/6.4.x/authentication/Syncope-Authentication.html[Syncope^]
-    ** https://apereo.github.io/cas/6.4.x/integration/Delegate-Authentication.html[SAML^]
+    ** https://apereo.github.io/cas/6.5.x/authentication/Database-Authentication.html[Database^]
+    ** https://apereo.github.io/cas/6.5.x/authentication/JAAS-Authentication.html[JAAS^]
+    ** https://apereo.github.io/cas/6.5.x/authentication/LDAP-Authentication.html[LDAP^]
+    ** https://apereo.github.io/cas/6.5.x/integration/Delegate-Authentication.html[OpenID Connect^]
+    ** https://apereo.github.io/cas/6.5.x/mfa/RADIUS-Authentication.html[Radius^]
+    ** https://apereo.github.io/cas/6.5.x/authentication/Syncope-Authentication.html[Static^]
+    ** https://apereo.github.io/cas/6.5.x/authentication/Syncope-Authentication.html[Syncope^]
+    ** https://apereo.github.io/cas/6.5.x/integration/Delegate-Authentication.html[SAML^]
 * MFA:
-    ** https://apereo.github.io/cas/6.4.x/mfa/DuoSecurity-Authentication.html[Duo Security^]
-    ** https://apereo.github.io/cas/6.4.x/mfa/FIDO-U2F-Authentication.html[Fido U2F^]
-    ** https://apereo.github.io/cas/6.4.x/mfa/GoogleAuthenticator-Authentication.html[Google Authenticator^]
+    ** https://apereo.github.io/cas/6.5.x/mfa/DuoSecurity-Authentication.html[Duo Security^]
+    ** https://apereo.github.io/cas/6.5.x/mfa/FIDO-U2F-Authentication.html[Fido U2F^]
+    ** https://apereo.github.io/cas/6.5.x/mfa/GoogleAuthenticator-Authentication.html[Google Authenticator^]
 
 [TIP]
 ====
@@ -58,4 +58,4 @@ class.
 
 [NOTE]
 Authentication Modules are dynamically translated into
-https://apereo.github.io/cas/6.4.x/authentication/Configuring-Authentication-Components.html#authentication-handlers[CAS Authentication Handlers^].
+https://apereo.github.io/cas/6.5.x/authentication/Configuring-Authentication-Components.html#authentication-handlers[CAS Authentication Handlers^].
diff --git a/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc b/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
index 4148456..97d8437 100644
--- a/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
@@ -41,4 +41,4 @@ More parameters are required to be specified depending on the actual client appl
 
 [NOTE]
 Client Applications are dynamically translated into
-https://apereo.github.io/cas/6.4.x/services/Service-Management.html[CAS Services^].
+https://apereo.github.io/cas/6.5.x/services/Service-Management.html[CAS Services^].
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index 5b4dd19..576d838 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -286,7 +286,7 @@ authentication request from the application arrives.
 
 [NOTE]
 Access Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/services/Configuring-Service-Access-Strategy.html#configure-service-access-strategy[CAS Service Access Strategy^].
+https://apereo.github.io/cas/6.5.x/services/Configuring-Service-Access-Strategy.html#configure-service-access-strategy[CAS Service Access Strategy^].
 
 [[policies-attribute-release]]
 ==== Attribute Release
@@ -298,7 +298,7 @@ values.
 
 [NOTE]
 Attribute Release Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/integration/Attribute-Release-Policies.html#attribute-release-policies[CAS Attribute Release Policy^].
+https://apereo.github.io/cas/6.5.x/integration/Attribute-Release-Policies.html#attribute-release-policies[CAS Attribute Release Policy^].
 
 [[policies-authentication]]
 ==== Authentication
@@ -325,7 +325,21 @@ be considered successful.
 
 [NOTE]
 Authentication Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/authentication/Configuring-Authentication-Policy.html#authentication-policy[CAS Authentication Policy^].
+https://apereo.github.io/cas/6.5.x/authentication/Configuring-Authentication-Policy.html#authentication-policy[CAS Authentication Policy^].
+
+[[policies-propagation]]
+==== Propagation
+
+Propagation policies are evaluated during the execution of <<tasks-propagation,propagation tasks>> and are meant to
+retry the configured operations in case of propagation failures.
+
+When defining a propagation policy, the following information must be provided:
+
+* max number of attempts
+* back-off strategy
+** `FIXED` - pauses for a fixed period of time before continuing
+** `EXPONENTIAL` - increases the back off period for each retry attempt in a given set up to a limit
+** `RANDOM` - chooses a random multiple of the interval that would come from a simple deterministic exponential
 
 [[policies-pull]]
 ==== Pull
diff --git a/src/main/asciidoc/reference-guide/concepts/tasks.adoc b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
index 21e01ac..26c29cf 100644
--- a/src/main/asciidoc/reference-guide/concepts/tasks.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
@@ -62,6 +62,9 @@ endif::[]
 during the <<propagation,propagation>> process, and are permanently saved - for later re-execution or for examining
 the execution details - depending on the trace levels set on the related
 <<external-resource-details,external resource>>.
+
+Automatic retry in case of failure can be configured by mean of a <<policies-propagation,propagation policy>>, for the
+related external resource.
 ====
 
 [[tasks-pull]]