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 2017/08/03 15:55:42 UTC

[1/4] syncope git commit: [SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to

Repository: syncope
Updated Branches:
  refs/heads/2_0_X 48707e6b2 -> 1cf0d2a02
  refs/heads/master 7eb33dcc8 -> f1c004bfc


http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
index 2277c14..888e870 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
@@ -22,6 +22,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.SAML2EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -36,6 +37,8 @@ public class JPASAML2EntityFactory implements SAML2EntityFactory {
             result = (E) new JPASAML2IdP();
         } else if (reference.equals(SAML2IdPItem.class)) {
             result = (E) new JPASAML2IdPItem();
+        } else if (reference.equals(SAML2UserTemplate.class)) {
+            result = (E) new JPASAML2UserTemplate();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
index cf1e3a8..d6df04d 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
@@ -19,15 +19,21 @@
 package org.apache.syncope.core.persistence.jpa.entity;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import javax.persistence.Basic;
 import javax.persistence.Cacheable;
 import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
 import javax.persistence.Column;
+import javax.persistence.ElementCollection;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
 import javax.persistence.Lob;
 import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
@@ -37,6 +43,7 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.apache.syncope.core.persistence.jpa.validation.entity.SAML2IdPCheck;
 
 @Entity
@@ -65,11 +72,31 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     @Min(0)
     @Max(1)
     @Column(nullable = false)
+    private Integer createUnmatching;
+
+    @Min(0)
+    @Max(1)
+    @Column(nullable = false)
+    private Integer updateMatching;
+
+    @Min(0)
+    @Max(1)
+    @Column(nullable = false)
     private Integer useDeflateEncoding;
 
     @Column(nullable = false)
     private SAML2BindingType bindingType;
 
+    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "idp")
+    private JPASAML2UserTemplate userTemplate;
+
+    @ElementCollection(fetch = FetchType.EAGER)
+    @Column(name = "actionClassName")
+    @CollectionTable(name = TABLE + "_actionsClassNames",
+            joinColumns =
+            @JoinColumn(name = "saml2IdP_id", referencedColumnName = "id"))
+    private Set<String> actionsClassNames = new HashSet<>();
+
     @Override
     public String getEntityID() {
         return entityID;
@@ -101,6 +128,26 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     }
 
     @Override
+    public boolean isCreateUnmatching() {
+        return isBooleanAsInteger(createUnmatching);
+    }
+
+    @Override
+    public void setCreateUnmatching(final boolean createUnmatching) {
+        this.createUnmatching = getBooleanAsInteger(createUnmatching);
+    }
+
+    @Override
+    public boolean isUpdateMatching() {
+        return isBooleanAsInteger(updateMatching);
+    }
+
+    @Override
+    public void setUpdateMatching(final boolean updateMatching) {
+        this.updateMatching = getBooleanAsInteger(updateMatching);
+    }
+
+    @Override
     public boolean isUseDeflateEncoding() {
         return isBooleanAsInteger(useDeflateEncoding);
     }
@@ -121,6 +168,17 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     }
 
     @Override
+    public SAML2UserTemplate getUserTemplate() {
+        return userTemplate;
+    }
+
+    @Override
+    public void setUserTemplate(final SAML2UserTemplate userTemplate) {
+        checkType(userTemplate, JPASAML2UserTemplate.class);
+        this.userTemplate = (JPASAML2UserTemplate) userTemplate;
+    }
+
+    @Override
     public boolean add(final SAML2IdPItem item) {
         checkType(item, JPASAML2IdPItem.class);
         return items.contains((JPASAML2IdPItem) item) || items.add((JPASAML2IdPItem) item);
@@ -148,4 +206,8 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
         this.add(item);
     }
 
+    @Override
+    public Set<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
new file mode 100644
index 0000000..538fa94
--- /dev/null
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
+import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractAnyTemplate;
+
+@Entity
+@Table(name = JPASAML2UserTemplate.TABLE, uniqueConstraints =
+        @UniqueConstraint(columnNames = { "idp_id" }))
+public class JPASAML2UserTemplate extends AbstractAnyTemplate implements SAML2UserTemplate {
+
+    private static final long serialVersionUID = -4575039890434426856L;
+
+    public static final String TABLE = "SAML2UserTemplate";
+
+    @ManyToOne
+    private JPASAML2IdP idp;
+
+    @Override
+    public SAML2IdP getIdP() {
+        return idp;
+    }
+
+    @Override
+    public void setIdP(final SAML2IdP idp) {
+        checkType(idp, JPASAML2IdP.class);
+        this.idp = (JPASAML2IdP) idp;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
new file mode 100644
index 0000000..6f23804
--- /dev/null
+++ b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
@@ -0,0 +1,34 @@
+/*
+ * 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.provisioning.api;
+
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+
+public interface SAML2IdPActions {
+
+    UserTO beforeCreate(UserTO input, SAML2LoginResponseTO loginResponse);
+
+    UserTO afterCreate(UserTO input, SAML2LoginResponseTO loginResponse);
+
+    UserPatch beforeUpdate(UserPatch input, SAML2LoginResponseTO loginResponse);
+
+    UserTO afterUpdate(UserTO input, SAML2LoginResponseTO loginResponse);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
new file mode 100644
index 0000000..ba5fc40
--- /dev/null
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
@@ -0,0 +1,48 @@
+/*
+ * 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.provisioning.java;
+
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+
+public class DefaultSAML2IdPActions implements SAML2IdPActions {
+
+    @Override
+    public UserTO beforeCreate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserTO afterCreate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserPatch beforeUpdate(final UserPatch input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserTO afterUpdate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
index b2668c5..2f2f9d0 100644
--- a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -36,6 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.SAML2EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder;
@@ -168,9 +170,24 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         idp.setEntityID(idpTO.getEntityID());
         idp.setName(idpTO.getName());
         idp.setMetadata(Base64.decode(idpTO.getMetadata()));
+        idp.setCreateUnmatching(idpTO.isCreateUnmatching());
+        idp.setUpdateMatching(idpTO.isUpdateMatching());
         idp.setUseDeflateEncoding(idpTO.isUseDeflateEncoding());
         idp.setBindingType(idpTO.getBindingType());
 
+        if (idpTO.getUserTemplate() == null) {
+            idp.setUserTemplate(null);
+        } else {
+            SAML2UserTemplate userTemplate = idp.getUserTemplate();
+            if (userTemplate == null) {
+                userTemplate = entityFactory.newEntity(SAML2UserTemplate.class);
+                userTemplate.setAnyType(anyTypeDAO.findUser());
+                userTemplate.setIdP(idp);
+                idp.setUserTemplate(userTemplate);
+            }
+            userTemplate.set(idpTO.getUserTemplate());
+        }
+
         idp.getItems().clear();
         AnyTypeClassTO allowedSchemas = new AnyTypeClassTO();
         for (AnyTypeClass anyTypeClass : anyTypeDAO.findUser().getClasses()) {
@@ -186,6 +203,9 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         }
         populateItems(idpTO, idp, allowedSchemas);
 
+        idp.getActionsClassNames().clear();
+        idp.getActionsClassNames().addAll(idpTO.getActionsClassNames());
+
         return saml2IdPDAO.save(idp);
     }
 
@@ -213,10 +233,18 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         idpTO.setName(idp.getName());
         idpTO.setUseDeflateEncoding(idp.isUseDeflateEncoding());
         idpTO.setBindingType(idp.getBindingType());
+        idpTO.setCreateUnmatching(idp.isCreateUnmatching());
+        idpTO.setUpdateMatching(idp.isUpdateMatching());
         idpTO.setMetadata(Base64.encode(idp.getMetadata()));
 
+        if (idp.getUserTemplate() != null) {
+            idpTO.setUserTemplate((UserTO) idp.getUserTemplate().get());
+        }
+
         populateItems(idp, idpTO);
 
+        idpTO.getActionsClassNames().addAll(idp.getActionsClassNames());
+
         return idpTO;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java b/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
index 5761c01..0d528c3 100644
--- a/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
+++ b/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
@@ -20,6 +20,7 @@ package org.apache.syncope.common.rest.api.service;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -40,6 +41,16 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 public interface SAML2IdPService extends JAXRSService {
 
     /**
+     * Returns the list of available SAML2IdPActions implementations.
+     *
+     * @return the list of available SAML2IdPActions implementations
+     */
+    @GET
+    @Path("actionsClasses")
+    @Produces({ MediaType.APPLICATION_JSON })
+    Set<String> getActionsClasses();
+
+    /**
      * Returns a list of all defined SAML 2.0 Identity Providers.
      *
      * @return list of all defined SAML 2.0 Identity Providers

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java b/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
index 1f2790f..ebda30d 100644
--- a/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
+++ b/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.rest.cxf.service;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -35,6 +36,11 @@ public class SAML2IdPServiceImpl extends AbstractServiceImpl implements SAML2IdP
     private SAML2IdPLogic logic;
 
     @Override
+    public Set<String> getActionsClasses() {
+        return logic.getActionsClasses();
+    }
+
+    @Override
     public List<SAML2IdPTO> list() {
         return logic.list();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
index e3cb7e0..6967e73 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -49,6 +50,7 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
 import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO;
 import org.apache.syncope.common.lib.to.SAML2RequestTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.SAML2SPService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.SAML2SPDetector;
@@ -173,13 +175,20 @@ public class SAML2ITCase extends AbstractITCase {
             }
         });
         assertNotNull(ssoCircle);
+        assertFalse(ssoCircle.isCreateUnmatching());
+        assertNull(ssoCircle.getUserTemplate());
         assertFalse(ssoCircle.getItems().isEmpty());
         assertNotNull(ssoCircle.getConnObjectKeyItem());
         assertNotEquals("email", ssoCircle.getConnObjectKeyItem().getIntAttrName());
         assertNotEquals("EmailAddress", ssoCircle.getConnObjectKeyItem().getExtAttrName());
 
-        ssoCircle.getItems().clear();
+        ssoCircle.setCreateUnmatching(true);
+
+        UserTO userTemplate = new UserTO();
+        userTemplate.setRealm("'/'");
+        ssoCircle.setUserTemplate(userTemplate);
 
+        ssoCircle.getItems().clear();
         ItemTO keyMapping = new ItemTO();
         keyMapping.setIntAttrName("email");
         keyMapping.setExtAttrName("EmailAddress");
@@ -188,6 +197,8 @@ public class SAML2ITCase extends AbstractITCase {
         saml2IdPService.update(ssoCircle);
 
         ssoCircle = saml2IdPService.read(ssoCircle.getKey());
+        assertTrue(ssoCircle.isCreateUnmatching());
+        assertEquals(userTemplate, ssoCircle.getUserTemplate());
         assertEquals("email", ssoCircle.getConnObjectKeyItem().getIntAttrName());
         assertEquals("EmailAddress", ssoCircle.getConnObjectKeyItem().getExtAttrName());
     }


[2/4] syncope git commit: [SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to

Posted by il...@apache.org.
[SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to provide the actual user data


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/1cf0d2a0
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/1cf0d2a0
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/1cf0d2a0

Branch: refs/heads/2_0_X
Commit: 1cf0d2a02873926d6a2b41f1cb11d2cc0aa5ab83
Parents: 48707e6
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Aug 3 17:53:38 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Aug 3 17:53:38 2017 +0200

----------------------------------------------------------------------
 .../console/tasks/SchedTaskDirectoryPanel.java  |   5 +-
 .../markup/html/bootstrap/dialog/BaseModal.java |   4 +-
 .../wizards/any/UserTemplateWizardBuilder.java  |  17 ++
 .../console/panels/RealmDetails_it.properties   |   2 +-
 .../jpa/entity/JPAAnyTemplateRealm.java         |   4 +-
 .../provisioning/java/utils/MappingUtils.java   |   6 +-
 .../provisioning/java/utils/TemplateUtils.java  |  74 ++---
 .../saml2lsp/agent/AbstractSAML2SPServlet.java  |   1 -
 .../console/panels/SAML2IdPsDirectoryPanel.java | 110 ++++++-
 .../console/rest/SAML2IdPsRestClient.java       |   5 +
 .../console/wizards/SAML2IdPWizardBuilder.java  |  36 ++-
 .../panels/SAML2IdPsDirectoryPanel.properties   |   4 +
 .../SAML2IdPsDirectoryPanel_it.properties       |   4 +
 .../SAML2IdPsDirectoryPanel_pt_BR.properties    |   4 +
 .../SAML2IdPsDirectoryPanel_ru.properties       |   4 +
 .../syncope/common/lib/to/SAML2IdPTO.java       |  41 +++
 .../common/lib/to/SAML2LoginResponseTO.java     |  14 +
 .../common/lib/to/SAML2ReceivedResponseTO.java  |  10 -
 .../syncope/core/logic/SAML2IdPLogic.java       |  16 +-
 .../apache/syncope/core/logic/SAML2SPLogic.java | 140 +++------
 ...AML2SPClassPathScanImplementationLookup.java |  78 +++++
 .../syncope/core/logic/saml2/SAML2IdPCache.java |  16 +-
 .../core/logic/saml2/SAML2IdPEntity.java        |  51 ++--
 .../core/logic/saml2/SAML2ReaderWriter.java     |   1 -
 .../core/logic/saml2/SAML2UserManager.java      | 292 +++++++++++++++++++
 .../core/persistence/api/entity/SAML2IdP.java   |  15 +
 .../api/entity/SAML2UserTemplate.java           |  26 ++
 .../jpa/entity/JPASAML2EntityFactory.java       |   3 +
 .../persistence/jpa/entity/JPASAML2IdP.java     |  62 ++++
 .../jpa/entity/JPASAML2UserTemplate.java        |  52 ++++
 .../core/provisioning/api/SAML2IdPActions.java  |  34 +++
 .../java/DefaultSAML2IdPActions.java            |  48 +++
 .../java/data/SAML2IdPDataBinderImpl.java       |  28 ++
 .../rest/api/service/SAML2IdPService.java       |  11 +
 .../rest/cxf/service/SAML2IdPServiceImpl.java   |   6 +
 .../apache/syncope/fit/core/SAML2ITCase.java    |  13 +-
 36 files changed, 1026 insertions(+), 211 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index f09c0dc..7663235 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -72,8 +72,8 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
 
     private static final long serialVersionUID = 4984337552918213290L;
 
-    private static final String GROUP_MEMBER_PROVISION_TASKJOB
-            = "org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate";
+    private static final String GROUP_MEMBER_PROVISION_TASKJOB =
+            "org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate";
 
     protected final Class<T> reference;
 
@@ -116,6 +116,7 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
             @Override
             protected Serializable onApplyInternal(
                     final TemplatableTO targetObject, final String type, final AnyTO anyTO) {
+
                 targetObject.getTemplates().put(type, anyTO);
                 new TaskRestClient().update(SchedTaskTO.class.cast(targetObject));
                 return targetObject;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
index a5716e6..1df1e6c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
@@ -164,8 +164,8 @@ public class BaseModal<T extends Serializable> extends Modal<T> {
 
     private BaseModal<T> setInternalContent(final Panel component) {
         if (!component.getId().equals(getContentId())) {
-            throw new WicketRuntimeException(
-                    "Modal content id is wrong. Component ID:" + component.getId() + "; content ID: " + getContentId());
+            throw new WicketRuntimeException("Modal content id is wrong. "
+                    + "Component ID: " + component.getId() + "; content ID: " + getContentId());
         }
 
         content.replaceWith(component);

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
index 8d0dc5b..3c8111c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
@@ -34,6 +34,23 @@ public class UserTemplateWizardBuilder extends UserWizardBuilder implements Temp
     private final TemplatableTO templatable;
 
     public UserTemplateWizardBuilder(
+            final UserTO template,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageRef) {
+
+        super(anyTypeClasses, formLayoutInfo, pageRef);
+        templatable = null;
+
+        if (template == null) {
+            setItem(new UserWrapper(new UserTO()));
+        } else {
+            setItem(new UserWrapper(template));
+        }
+
+    }
+
+    public UserTemplateWizardBuilder(
             final TemplatableTO templatable,
             final List<String> anyTypeClasses,
             final UserFormLayoutInfo formLayoutInfo,

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
index 05d20c2..80a008a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
@@ -17,5 +17,5 @@
 fullPath=Percorso
 accountPolicy=Account Policy
 passwordPolicy=Password Policy
-actionsClassNames=Actions
+actionsClassNames=Azioni
 resources=Risorse assegnate

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
index 46949ee..05ecf6b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
@@ -31,10 +31,10 @@ import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractAnyTempla
         @UniqueConstraint(columnNames = { "realm_id", "anyType_id" }))
 public class JPAAnyTemplateRealm extends AbstractAnyTemplate implements AnyTemplateRealm {
 
-    public static final String TABLE = "AnyTemplateRealm";
-
     private static final long serialVersionUID = 1863029633568957907L;
 
+    public static final String TABLE = "AnyTemplateRealm";
+
     @ManyToOne
     private JPARealm realm;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
index 2c71f37..ce794af 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -189,7 +189,7 @@ public final class MappingUtils {
         return evaluateNAME(evalConnObjectLink, connObjectKey);
     }
 
-    private static List<ItemTransformer> getMappingItemTransformers(
+    private static List<ItemTransformer> getItemTransformers(
             final String propagationJEXLTransformer,
             final String pullJEXLTransformer,
             final List<String> mappingItemTransformerClassNames) {
@@ -224,14 +224,14 @@ public final class MappingUtils {
     }
 
     public static List<ItemTransformer> getItemTransformers(final ItemTO item) {
-        return getMappingItemTransformers(
+        return getItemTransformers(
                 item.getPropagationJEXLTransformer(),
                 item.getPullJEXLTransformer(),
                 item.getTransformerClassNames());
     }
 
     public static List<ItemTransformer> getItemTransformers(final Item item) {
-        return getMappingItemTransformers(
+        return getItemTransformers(
                 item.getPropagationJEXLTransformer(),
                 item.getPullJEXLTransformer(),
                 item.getTransformerClassNames());

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
index d56f651..ef47ef1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
@@ -126,48 +126,52 @@ public class TemplateUtils {
     @Transactional(readOnly = true)
     public <T extends AnyTO> void apply(final T anyTO, final AnyTemplate anyTemplate) {
         if (anyTemplate != null) {
-            AnyTO template = anyTemplate.get();
-            fill(anyTO, template);
-
-            if (template instanceof AnyObjectTO) {
-                fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-                fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-            } else if (template instanceof UserTO) {
-                if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
-                    String evaluated = JexlUtils.evaluate(((UserTO) template).getUsername(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((UserTO) anyTO).setUsername(evaluated);
-                    }
+            apply(anyTO, anyTemplate.get());
+        }
+    }
+
+    @Transactional(readOnly = true)
+    public <T extends AnyTO> void apply(final T anyTO, final AnyTO template) {
+        fill(anyTO, template);
+
+        if (template instanceof AnyObjectTO) {
+            fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+            fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+        } else if (template instanceof UserTO) {
+            if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
+                String evaluated = JexlUtils.evaluate(((UserTO) template).getUsername(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((UserTO) anyTO).setUsername(evaluated);
                 }
+            }
 
-                if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
-                    String evaluated = JexlUtils.evaluate(((UserTO) template).getPassword(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((UserTO) anyTO).setPassword(evaluated);
-                    }
+            if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
+                String evaluated = JexlUtils.evaluate(((UserTO) template).getPassword(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((UserTO) anyTO).setPassword(evaluated);
                 }
+            }
 
-                fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-                fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-            } else if (template instanceof GroupTO) {
-                if (StringUtils.isNotBlank(((GroupTO) template).getName())) {
-                    String evaluated = JexlUtils.evaluate(((GroupTO) template).getName(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((GroupTO) anyTO).setName(evaluated);
-                    }
+            fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+            fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+        } else if (template instanceof GroupTO) {
+            if (StringUtils.isNotBlank(((GroupTO) template).getName())) {
+                String evaluated = JexlUtils.evaluate(((GroupTO) template).getName(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((GroupTO) anyTO).setName(evaluated);
                 }
+            }
 
-                if (((GroupTO) template).getUserOwner() != null) {
-                    final User userOwner = userDAO.find(((GroupTO) template).getUserOwner());
-                    if (userOwner != null) {
-                        ((GroupTO) anyTO).setUserOwner(userOwner.getKey());
-                    }
+            if (((GroupTO) template).getUserOwner() != null) {
+                final User userOwner = userDAO.find(((GroupTO) template).getUserOwner());
+                if (userOwner != null) {
+                    ((GroupTO) anyTO).setUserOwner(userOwner.getKey());
                 }
-                if (((GroupTO) template).getGroupOwner() != null) {
-                    final Group groupOwner = groupDAO.find(((GroupTO) template).getGroupOwner());
-                    if (groupOwner != null) {
-                        ((GroupTO) anyTO).setGroupOwner(groupOwner.getKey());
-                    }
+            }
+            if (((GroupTO) template).getGroupOwner() != null) {
+                final Group groupOwner = groupDAO.find(((GroupTO) template).getGroupOwner());
+                if (groupOwner != null) {
+                    ((GroupTO) anyTO).setGroupOwner(groupOwner.getKey());
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
index ecc14fe..f2bbabf 100644
--- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
+++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
@@ -97,7 +97,6 @@ public abstract class AbstractSAML2SPServlet extends HttpServlet {
         SAML2ReceivedResponseTO receivedResponseTO = new SAML2ReceivedResponseTO();
         receivedResponseTO.setSpEntityID(spEntityID);
         receivedResponseTO.setUrlContext(urlContext);
-        receivedResponseTO.setClientAddress(clientAddress);
         receivedResponseTO.setSamlResponse(samlResponse);
         receivedResponseTO.setRelayState(relayState);
         return receivedResponseTO;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
index 2ab6d8d..2874a84 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.console.panels;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -30,8 +31,10 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel.SAML2IdPsProvider;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.SAML2IdPsRestClient;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
@@ -42,13 +45,18 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizard;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.any.AnyWrapper;
+import org.apache.syncope.client.console.wizards.any.UserTemplateWizardBuilder;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
@@ -58,6 +66,7 @@ import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.util.crypt.Base64;
 
 public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
@@ -69,6 +78,8 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
 
     private final BaseModal<String> metadataModal = new BaseModal<>("outer");
 
+    private final BaseModal<Serializable> templateModal;
+
     public SAML2IdPsDirectoryPanel(final String id, final PageReference pageRef) {
         super(id, new Builder<SAML2IdPTO, SAML2IdPTO, SAML2IdPsRestClient>(new SAML2IdPsRestClient(), pageRef) {
 
@@ -117,6 +128,29 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         setWindowClosedReloadCallback(metadataModal);
         metadataModal.size(Modal.Size.Large);
 
+        templateModal = new BaseModal<Serializable>("outer") {
+
+            private static final long serialVersionUID = 5787433530654262016L;
+
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setFooterVisible(false);
+            }
+        };
+        templateModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+
+            private static final long serialVersionUID = 8804221891699487139L;
+
+            @Override
+            public void onClose(final AjaxRequestTarget target) {
+//                target.add(content);
+                templateModal.show(false);
+            }
+        });
+        templateModal.size(Modal.Size.Large);
+        addOuterObject(templateModal);
+
         initResultTable();
 
         final ImportMetadata importMetadata = new ImportMetadata("importMetadata", container, pageRef);
@@ -172,17 +206,6 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
 
         panel.add(new ActionLink<SAML2IdPTO>() {
 
-            private static final long serialVersionUID = -3722207913631435501L;
-
-            @Override
-            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
-                SAML2IdPTO object = restClient.read(model.getObject().getKey());
-                send(SAML2IdPsDirectoryPanel.this, Broadcast.EXACT,
-                        new AjaxWizard.EditItemActionEvent<>(object, target));
-            }
-        }, ActionLink.ActionType.EDIT, SAML2SPEntitlement.IDP_UPDATE);
-        panel.add(new ActionLink<SAML2IdPTO>() {
-
             private static final long serialVersionUID = -7978723352517770645L;
 
             @Override
@@ -200,6 +223,49 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         }, ActionLink.ActionType.HTML, SAML2SPEntitlement.IDP_READ);
         panel.add(new ActionLink<SAML2IdPTO>() {
 
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
+                SAML2IdPTO object = restClient.read(model.getObject().getKey());
+                send(SAML2IdPsDirectoryPanel.this, Broadcast.EXACT,
+                        new AjaxWizard.EditItemActionEvent<>(object, target));
+            }
+        }, ActionLink.ActionType.EDIT, SAML2SPEntitlement.IDP_UPDATE);
+        panel.add(new ActionLink<SAML2IdPTO>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
+                final SAML2IdPTO object = restClient.read(model.getObject().getKey());
+
+                UserTemplateWizardBuilder builder = new UserTemplateWizardBuilder(
+                        object.getUserTemplate(),
+                        new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses(),
+                        new UserFormLayoutInfo(),
+                        pageRef) {
+
+                    private static final long serialVersionUID = -7978723352517770634L;
+
+                    @Override
+                    protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
+                        object.setUserTemplate(modelObject.getInnerObject());
+                        restClient.update(object);
+
+                        return modelObject;
+                    }
+                };
+                templateModal.header(Model.of(StringUtils.capitalize(
+                        new StringResourceModel("template.title", SAML2IdPsDirectoryPanel.this).getString())));
+                templateModal.setContent(builder.build(BaseModal.CONTENT_ID));
+                templateModal.show(true);
+                target.add(templateModal);
+
+            }
+        }, ActionLink.ActionType.TEMPLATE, SAML2SPEntitlement.IDP_UPDATE);
+        panel.add(new ActionLink<SAML2IdPTO>() {
+
             private static final long serialVersionUID = -5467832321897812767L;
 
             @Override
@@ -220,6 +286,28 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         return panel;
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+
+        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
+            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+            WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
+
+            if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
+                final IModel<Serializable> model = new CompoundPropertyModel<>(modalPanel.getItem());
+                templateModal.setFormModel(model);
+                templateModal.header(newItemEvent.getResourceModel());
+                newItemEvent.getTarget().add(templateModal.setContent(modalPanel));
+                templateModal.show(true);
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            }
+        }
+    }
+
     protected final class SAML2IdPsProvider extends DirectoryDataProvider<SAML2IdPTO> {
 
         private static final long serialVersionUID = -185944053385660794L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
index ba67f57..c7e6e0c 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.rest;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
@@ -33,6 +34,10 @@ public class SAML2IdPsRestClient extends BaseRestClient {
         return getService(SAML2IdPService.class).list();
     }
 
+    public Set<String> getActionsClasses() {
+        return getService(SAML2IdPService.class).getActionsClasses();
+    }
+
     public void importIdPs(final InputStream input) {
         SyncopeConsoleSession.get().
                 getService(MediaType.APPLICATION_XML_TYPE, SAML2IdPService.class).importFromMetadata(input);

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
index 8567dd2..0fc7b22 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
@@ -29,6 +29,7 @@ import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel;
 import org.apache.syncope.client.console.rest.SAML2IdPsRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
 import org.apache.syncope.client.console.wizards.resources.JEXLTransformersTogglePanel;
@@ -42,9 +43,12 @@ import org.apache.wicket.extensions.wizard.WizardModel;
 import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.util.ListModel;
 
 public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
 
@@ -54,6 +58,16 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
 
     private final SAML2IdPsDirectoryPanel directoryPanel;
 
+    private final IModel<List<String>> actionsClasses = new LoadableDetachableModel<List<String>>() {
+
+        private static final long serialVersionUID = 5275935387613157437L;
+
+        @Override
+        protected List<String> load() {
+            return new ArrayList<>(restClient.getActionsClasses());
+        }
+    };
+
     public SAML2IdPWizardBuilder(
             final SAML2IdPsDirectoryPanel directoryPanel, final SAML2IdPTO idpTO, final PageReference pageRef) {
 
@@ -68,8 +82,7 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
         Mapping mapping = new Mapping(modelObject);
         mapping.setOutputMarkupId(true);
 
-        ItemTransformersTogglePanel mapItemTransformers =
-                new ItemTransformersTogglePanel(mapping, pageRef);
+        ItemTransformersTogglePanel mapItemTransformers = new ItemTransformersTogglePanel(mapping, pageRef);
         addOuterObject(mapItemTransformers);
         JEXLTransformersTogglePanel jexlTransformers = new JEXLTransformersTogglePanel(mapping, pageRef);
         addOuterObject(jexlTransformers);
@@ -80,7 +93,7 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
         return wizardModel;
     }
 
-    private static final class IdP extends WizardStep {
+    private final class IdP extends WizardStep {
 
         private static final long serialVersionUID = 854012593185195024L;
 
@@ -94,6 +107,14 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
             name.setRequired(true);
             fields.add(name);
 
+            AjaxCheckBoxPanel createUnmatching = new AjaxCheckBoxPanel(
+                    "field", "createUnmatching", new PropertyModel<Boolean>(idpTO, "createUnmatching"), false);
+            fields.add(createUnmatching);
+
+            AjaxCheckBoxPanel updateMatching = new AjaxCheckBoxPanel(
+                    "field", "updateMatching", new PropertyModel<Boolean>(idpTO, "updateMatching"), false);
+            fields.add(updateMatching);
+
             AjaxCheckBoxPanel useDeflateEncoding = new AjaxCheckBoxPanel(
                     "field", "useDeflateEncoding", new PropertyModel<Boolean>(idpTO, "useDeflateEncoding"), false);
             fields.add(useDeflateEncoding);
@@ -104,6 +125,15 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
             bindingType.setChoices(Arrays.asList(SAML2BindingType.values()));
             fields.add(bindingType);
 
+            AjaxPalettePanel<String> actionsClassNames = new AjaxPalettePanel.Builder<String>().
+                    setAllowMoveAll(true).setAllowOrder(true).
+                    setName(new StringResourceModel("actionsClassNames", directoryPanel).getString()).
+                    build("field",
+                            new PropertyModel<List<String>>(idpTO, "actionsClassNames"),
+                            new ListModel<>(actionsClasses.getObject()));
+            actionsClassNames.setOutputMarkupId(true);
+            fields.add(actionsClassNames);
+
             add(new ListView<Component>("fields", fields) {
 
                 private static final long serialVersionUID = -9180479401817023838L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
index f9c6403..97d8690 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
@@ -21,3 +21,7 @@ any.edit=Edit ${entityID}
 connObjectKeyValidation=There must be exactly one Remote Key
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Actions
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
index 312da5d..76464c0 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
@@ -21,3 +21,7 @@ any.edit=Modifica ${entityID}
 connObjectKeyValidation=Deve essere definito esattamente una Chiave remota
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Azioni
+template.title=template utenti

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
index 4e053f7..d81f0a8 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
@@ -21,3 +21,7 @@ any.edit=Alterar ${entityID}
 connObjectKeyValidation=Precisa ser exatamente um Remote Key
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Actions
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
index 8bedf47..8712eeb 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
@@ -21,3 +21,7 @@ any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${entityID}
 connObjectKeyValidation=\u0422\u0430\u043c \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0434\u0438\u0441\u0442\u0430\u043d\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
index ffda86d..022267d 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
@@ -20,7 +20,9 @@ package org.apache.syncope.common.lib.to;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.PathParam;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
@@ -45,14 +47,22 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
 
     private String metadata;
 
+    private boolean createUnmatching;
+
+    private boolean updateMatching;
+
     private boolean useDeflateEncoding;
 
     private SAML2BindingType bindingType;
 
     private boolean logoutSupported;
 
+    private UserTO userTemplate;
+
     private final List<ItemTO> items = new ArrayList<>();
 
+    private final Set<String> actionsClassNames = new HashSet<>();
+
     @Override
     public String getKey() {
         return key;
@@ -88,6 +98,22 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         this.metadata = metadata;
     }
 
+    public boolean isCreateUnmatching() {
+        return createUnmatching;
+    }
+
+    public void setCreateUnmatching(final boolean createUnmatching) {
+        this.createUnmatching = createUnmatching;
+    }
+
+    public boolean isUpdateMatching() {
+        return updateMatching;
+    }
+
+    public void setUpdateMatching(final boolean updateMatching) {
+        this.updateMatching = updateMatching;
+    }
+
     public boolean isUseDeflateEncoding() {
         return useDeflateEncoding;
     }
@@ -112,6 +138,14 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         this.logoutSupported = logoutSupported;
     }
 
+    public UserTO getUserTemplate() {
+        return userTemplate;
+    }
+
+    public void setUserTemplate(final UserTO userTemplate) {
+        this.userTemplate = userTemplate;
+    }
+
     @Override
     public ItemTO getConnObjectKeyItem() {
         return IterableUtils.find(getItems(), new Predicate<ItemTO>() {
@@ -154,4 +188,11 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         return this.items.remove(item);
     }
 
+    @XmlElementWrapper(name = "actionsClassNames")
+    @XmlElement(name = "actionsClassName")
+    @JsonProperty("actionsClassNames")
+    public Set<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
index f905035..2b02eb4 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.common.lib.to;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Date;
 import java.util.HashSet;
@@ -26,6 +27,8 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 
 @XmlRootElement(name = "saml2LoginResponse")
@@ -124,6 +127,17 @@ public class SAML2LoginResponseTO extends AbstractBaseBean {
         this.username = username;
     }
 
+    @JsonIgnore
+    public AttrTO getAttr(final String schema) {
+        return IterableUtils.find(attrs, new Predicate<AttrTO>() {
+
+            @Override
+            public boolean evaluate(final AttrTO object) {
+                return object.getSchema().equals(schema);
+            }
+        });
+    }
+
     @XmlElementWrapper(name = "attrs")
     @XmlElement(name = "attr")
     @JsonProperty("attrs")

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
index 4ea2dec..556be9b 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
@@ -33,8 +33,6 @@ public class SAML2ReceivedResponseTO extends AbstractBaseBean {
 
     private String urlContext;
 
-    private String clientAddress;
-
     private String samlResponse;
 
     private String relayState;
@@ -55,14 +53,6 @@ public class SAML2ReceivedResponseTO extends AbstractBaseBean {
         this.urlContext = urlContext;
     }
 
-    public String getClientAddress() {
-        return clientAddress;
-    }
-
-    public void setClientAddress(final String clientAddress) {
-        this.clientAddress = clientAddress;
-    }
-
     public String getSamlResponse() {
         return samlResponse;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
index d7337b3..92dda48 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
@@ -25,6 +25,7 @@ import java.io.OutputStreamWriter;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Transformer;
@@ -35,6 +36,7 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
+import org.apache.syncope.core.logic.init.SAML2SPClassPathScanImplementationLookup;
 import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter;
 import org.apache.syncope.core.logic.saml2.SAML2IdPCache;
 import org.apache.syncope.core.logic.saml2.SAML2IdPEntity;
@@ -68,8 +70,16 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
     private SAML2IdPDAO idpDAO;
 
     @Autowired
+    private SAML2SPClassPathScanImplementationLookup implLookup;
+
+    @Autowired
     private SAML2ReaderWriter saml2rw;
 
+    @PreAuthorize("isAuthenticated()")
+    public Set<String> getActionsClasses() {
+        return implLookup.getActionsClasses();
+    }
+
     private SAML2IdPTO complete(final SAML2IdP idp, final SAML2IdPTO idpTO) {
         SAML2IdPEntity idpEntity = cache.get(idpTO.getEntityID());
         if (idpEntity == null) {
@@ -159,7 +169,7 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
             connObjectKeyItem.setExtAttrName("NameID");
             idpTO.setConnObjectKeyItem(connObjectKeyItem);
 
-            SAML2IdPEntity idp = cache.put(idpEntityDescriptor, connObjectKeyItem, false, SAML2BindingType.POST);
+            SAML2IdPEntity idp = cache.put(idpEntityDescriptor, idpTO);
             if (idp.getSSOLocation(SAML2BindingType.POST) != null) {
                 idpTO.setBindingType(SAML2BindingType.POST);
             } else if (idp.getSSOLocation(SAML2BindingType.REDIRECT) != null) {
@@ -225,9 +235,7 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
 
         saml2Idp = idpDAO.save(binder.update(saml2Idp, saml2IdpTO));
 
-        idpEntity.setUseDeflateEncoding(saml2Idp.isUseDeflateEncoding());
-        idpEntity.setBindingType(saml2Idp.getBindingType());
-        idpEntity.setConnObjectKeyItem(binder.getIdPTO(saml2Idp).getConnObjectKeyItem());
+        idpEntity.setIdpTO(binder.getIdPTO(saml2Idp));
     }
 
     @PreAuthorize("hasRole('" + SAML2SPEntitlement.IDP_DELETE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
index a9b63ed..f6953e6 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.logic;
 
+import org.apache.syncope.core.logic.saml2.SAML2UserManager;
 import com.fasterxml.uuid.Generators;
 import com.fasterxml.uuid.impl.RandomBasedGenerator;
 import java.io.OutputStream;
@@ -25,14 +26,12 @@ import java.io.OutputStreamWriter;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Resource;
-import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
@@ -43,9 +42,7 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.SAML2RequestTO;
 import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
-import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
@@ -53,23 +50,11 @@ import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter;
 import org.apache.syncope.core.logic.saml2.SAML2IdPCache;
 import org.apache.syncope.core.logic.saml2.SAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.SAML2IdPDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
-import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
-import org.apache.syncope.core.provisioning.api.utils.EntityUtils;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.joda.time.DateTime;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.schema.XSString;
@@ -118,8 +103,8 @@ import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
-import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
 import org.apache.syncope.core.spring.security.Encryptor;
 
@@ -149,27 +134,18 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
     private SAML2IdPCache cache;
 
     @Autowired
-    private UserDAO userDAO;
+    private SAML2UserManager userManager;
 
     @Autowired
     private SAML2IdPDAO saml2IdPDAO;
 
     @Autowired
-    private PlainSchemaDAO plainSchemaDAO;
-
-    @Autowired
     private AccessTokenDAO accessTokenDAO;
 
     @Autowired
-    private IntAttrNameParser intAttrNameParser;
-
-    @Autowired
     private AuthDataAccessor authDataAccessor;
 
     @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
     private SAML2ReaderWriter saml2rw;
 
     @Resource(name = "syncopeJWTSSOProviderDelegate")
@@ -352,73 +328,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         return requestTO;
     }
 
-    private List<String> findMatchingUser(final String keyValue, final ItemTO connObjectKeyItem) {
-        List<String> result = new ArrayList<>();
-
-        String transformed = keyValue;
-        for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
-            List<Object> output = transformer.beforePull(
-                    null,
-                    null,
-                    Collections.<Object>singletonList(transformed));
-            if (output != null && !output.isEmpty()) {
-                transformed = output.get(0).toString();
-            }
-        }
-
-        IntAttrName intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), AnyTypeKind.USER);
-
-        if (intAttrName.getField() != null) {
-            switch (intAttrName.getField()) {
-                case "key":
-                    User byKey = userDAO.find(transformed);
-                    if (byKey != null) {
-                        result.add(byKey.getKey());
-                    }
-                    break;
-
-                case "username":
-                    User byUsername = userDAO.findByUsername(transformed);
-                    if (byUsername != null) {
-                        result.add(byUsername.getKey());
-                    }
-                    break;
-
-                default:
-            }
-        } else if (intAttrName.getSchemaType() != null) {
-            switch (intAttrName.getSchemaType()) {
-                case PLAIN:
-                    PlainAttrValue value = entityFactory.newEntity(UPlainAttrValue.class);
-
-                    PlainSchema schema = plainSchemaDAO.find(intAttrName.getSchemaName());
-                    if (schema == null) {
-                        value.setStringValue(transformed);
-                    } else {
-                        try {
-                            value.parseValue(schema, transformed);
-                        } catch (ParsingValidationException e) {
-                            LOG.error("While parsing provided key value {}", transformed, e);
-                            value.setStringValue(transformed);
-                        }
-                    }
-
-                    CollectionUtils.collect(userDAO.findByAttrValue(intAttrName.getSchemaName(), value),
-                            EntityUtils.keyTransformer(), result);
-                    break;
-
-                case DERIVED:
-                    CollectionUtils.collect(userDAO.findByDerAttrValue(intAttrName.getSchemaName(), transformed),
-                            EntityUtils.keyTransformer(), result);
-                    break;
-
-                default:
-            }
-        }
-
-        return result;
-    }
-
     @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')")
     public SAML2LoginResponseTO validateLoginResponse(final SAML2ReceivedResponseTO response) {
         check();
@@ -454,7 +363,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         }
 
         // 3. validate the SAML response and, if needed, decrypt the provided assertion(s)
-        SAML2IdPEntity idp = getIdP(samlResponse.getIssuer().getValue());
+        final SAML2IdPEntity idp = getIdP(samlResponse.getIssuer().getValue());
         if (idp.getConnObjectKeyItem() == null) {
             throw new IllegalArgumentException("No mapping provided for SAML 2.0 IdP '" + idp.getId() + "'");
         }
@@ -463,7 +372,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
                     samlResponse,
                     idp,
                     getAssertionConsumerURL(response.getSpEntityID(), response.getUrlContext()),
-                    response.getClientAddress(),
                     relayState.getJwtClaims().getSubject(),
                     response.getSpEntityID());
         } catch (Exception e) {
@@ -474,7 +382,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         }
 
         // 4. prepare the result: find matching user (if any) and return the received attributes
-        SAML2LoginResponseTO responseTO = new SAML2LoginResponseTO();
+        final SAML2LoginResponseTO responseTO = new SAML2LoginResponseTO();
         responseTO.setIdp(idp.getId());
         responseTO.setSloSupported(idp.getSLOLocation(idp.getBindingType()) != null);
 
@@ -525,19 +433,49 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         if (nameID == null) {
             throw new IllegalArgumentException("NameID not found");
         }
+        final String nameIDValue = nameID.getValue();
 
-        List<String> matchingUsers = keyValue == null
+        final List<String> matchingUsers = keyValue == null
                 ? Collections.<String>emptyList()
-                : findMatchingUser(keyValue, idp.getConnObjectKeyItem());
+                : userManager.findMatchingUser(keyValue, idp.getConnObjectKeyItem());
         LOG.debug("Found {} matching users for NameID {}", matchingUsers.size(), nameID.getValue());
 
+        String username;
         if (matchingUsers.isEmpty()) {
-            throw new NotFoundException("User matching the provided NameID value " + nameID.getValue());
+            if (idp.isCreateUnmatching()) {
+                LOG.debug("No user matching NameID {}, about to create", nameID.getValue());
+
+                username = AuthContextUtils.execWithAuthContext(
+                        AuthContextUtils.getDomain(), new AuthContextUtils.Executable<String>() {
+
+                    @Override
+                    public String exec() {
+                        return userManager.create(idp, responseTO, nameIDValue);
+                    }
+                });
+            } else {
+                throw new NotFoundException("User matching the provided NameID value " + nameID.getValue());
+            }
         } else if (matchingUsers.size() > 1) {
             throw new IllegalArgumentException("Several users match the provided NameID value " + nameID.getValue());
+        } else {
+            if (idp.isUpdateMatching()) {
+                LOG.debug("About to update {} for NameID {}", matchingUsers.get(0), nameID.getValue());
+
+                username = AuthContextUtils.execWithAuthContext(
+                        AuthContextUtils.getDomain(), new AuthContextUtils.Executable<String>() {
+
+                    @Override
+                    public String exec() {
+                        return userManager.update(matchingUsers.get(0), idp, responseTO);
+                    }
+                });
+            } else {
+                username = matchingUsers.get(0);
+            }
         }
-        responseTO.setUsername(userDAO.find(matchingUsers.get(0)).getUsername());
 
+        responseTO.setUsername(username);
         responseTO.setNameID(nameID.getValue());
 
         // 5. generate JWT for further access

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
new file mode 100644
index 0000000..13b3cab
--- /dev/null
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
@@ -0,0 +1,78 @@
+/*
+ * 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.logic.init;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.SyncopeLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+
+@Component
+public class SAML2SPClassPathScanImplementationLookup implements SyncopeLoader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ImplementationLookup.class);
+
+    private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.core";
+
+    private Set<String> actionsClasses;
+
+    @Override
+    public Integer getPriority() {
+        return Integer.MIN_VALUE;
+    }
+
+    @Override
+    public void load() {
+        actionsClasses = new HashSet<>();
+
+        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+        scanner.addIncludeFilter(new AssignableTypeFilter(SAML2IdPActions.class));
+
+        for (BeanDefinition bd : scanner.findCandidateComponents(DEFAULT_BASE_PACKAGE)) {
+            try {
+                Class<?> clazz = ClassUtils.resolveClassName(
+                        bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
+                boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
+
+                if (SAML2IdPActions.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    actionsClasses.add(clazz.getName());
+                }
+            } catch (Throwable t) {
+                LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
+            }
+        }
+
+        actionsClasses = Collections.unmodifiableSet(actionsClasses);
+    }
+
+    public Set<String> getActionsClasses() {
+        return actionsClasses;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
index ed244d0..e56b4f8 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
@@ -28,8 +28,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import net.shibboleth.utilities.java.support.xml.XMLParserException;
-import org.apache.syncope.common.lib.to.ItemTO;
-import org.apache.syncope.common.lib.types.SAML2BindingType;
+import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.core.logic.init.SAML2SPLoader;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder;
@@ -66,13 +65,10 @@ public class SAML2IdPCache {
 
     public SAML2IdPEntity put(
             final EntityDescriptor entityDescriptor,
-            final ItemTO connObjectKeyItem,
-            final boolean useDeflateEncoding,
-            final SAML2BindingType bindingType)
+            final SAML2IdPTO idpTO)
             throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
 
-        SAML2IdPEntity idp = new SAML2IdPEntity(
-                entityDescriptor, connObjectKeyItem, useDeflateEncoding, bindingType, loader.getKeyPass());
+        SAML2IdPEntity idp = new SAML2IdPEntity(entityDescriptor, idpTO, loader.getKeyPass());
         cache.put(entityDescriptor.getEntityID(), idp);
         return idp;
     }
@@ -85,11 +81,7 @@ public class SAML2IdPCache {
         Element element = OpenSAMLUtil.getParserPool().parse(
                 new InputStreamReader(new ByteArrayInputStream(idp.getMetadata()))).getDocumentElement();
         EntityDescriptor entityDescriptor = (EntityDescriptor) OpenSAMLUtil.fromDom(element);
-        return put(
-                entityDescriptor,
-                binder.getIdPTO(idp).getConnObjectKeyItem(),
-                idp.isUseDeflateEncoding(),
-                idp.getBindingType());
+        return put(entityDescriptor, binder.getIdPTO(idp));
     }
 
     public SAML2IdPEntity remove(final String entityID) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
index b747c32..b937e17 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
@@ -30,8 +30,11 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.opensaml.saml.common.xml.SAMLConstants;
 import org.opensaml.saml.saml2.metadata.Endpoint;
@@ -51,11 +54,7 @@ public class SAML2IdPEntity {
 
     private final String id;
 
-    private boolean useDeflateEncoding;
-
-    private SAML2BindingType bindingType;
-
-    private ItemTO connObjectKeyItem;
+    private SAML2IdPTO idpTO;
 
     private final Map<String, Endpoint> ssoBindings = new HashMap<>();
 
@@ -67,16 +66,12 @@ public class SAML2IdPEntity {
 
     public SAML2IdPEntity(
             final EntityDescriptor entityDescriptor,
-            final ItemTO connObjectKeyItem,
-            final boolean useDeflateEncoding,
-            final SAML2BindingType bindingType,
+            final SAML2IdPTO idpTO,
             final String keyPass)
             throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
 
         this.id = entityDescriptor.getEntityID();
-        this.connObjectKeyItem = connObjectKeyItem;
-        this.useDeflateEncoding = useDeflateEncoding;
-        this.bindingType = bindingType;
+        this.idpTO = idpTO;
 
         IDPSSODescriptor idpdescriptor = entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
 
@@ -121,32 +116,44 @@ public class SAML2IdPEntity {
         }
     }
 
+    public void setIdpTO(final SAML2IdPTO idpTO) {
+        this.idpTO = idpTO;
+    }
+
     public String getId() {
         return id;
     }
 
-    public boolean isUseDeflateEncoding() {
-        return useDeflateEncoding;
+    public boolean isCreateUnmatching() {
+        return idpTO.isCreateUnmatching();
     }
 
-    public void setUseDeflateEncoding(final boolean useDeflateEncoding) {
-        this.useDeflateEncoding = useDeflateEncoding;
+    public boolean isUpdateMatching() {
+        return idpTO.isUpdateMatching();
     }
 
-    public SAML2BindingType getBindingType() {
-        return bindingType;
+    public boolean isUseDeflateEncoding() {
+        return idpTO.isUseDeflateEncoding();
     }
 
-    public void setBindingType(final SAML2BindingType bindingType) {
-        this.bindingType = bindingType;
+    public SAML2BindingType getBindingType() {
+        return idpTO.getBindingType();
     }
 
     public ItemTO getConnObjectKeyItem() {
-        return connObjectKeyItem;
+        return idpTO.getConnObjectKeyItem();
+    }
+
+    public List<ItemTO> getItems() {
+        return idpTO.getItems();
+    }
+
+    public UserTO getUserTemplate() {
+        return idpTO.getUserTemplate();
     }
 
-    public void setConnObjectKeyItem(final ItemTO connObjectKeyItem) {
-        this.connObjectKeyItem = connObjectKeyItem;
+    public Set<String> getActionsClassNames() {
+        return idpTO.getActionsClassNames();
     }
 
     public Endpoint getSSOLocation(final SAML2BindingType bindingType) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
index 17386ea..8e47911 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
@@ -214,7 +214,6 @@ public class SAML2ReaderWriter {
             final Response samlResponse,
             final SAML2IdPEntity idp,
             final String assertionConsumerURL,
-            final String clientAddress,
             final String requestId,
             final String spEntityID)
             throws WSSecurityException {

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
new file mode 100644
index 0000000..a7af5e2
--- /dev/null
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
@@ -0,0 +1,292 @@
+/*
+ * 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.logic.saml2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class SAML2UserManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SAML2UserManager.class);
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private IntAttrNameParser intAttrNameParser;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private TemplateUtils templateUtils;
+
+    @Autowired
+    private UserProvisioningManager provisioningManager;
+
+    @Autowired
+    private UserDataBinder binder;
+
+    @Transactional(readOnly = true)
+    public List<String> findMatchingUser(final String keyValue, final ItemTO connObjectKeyItem) {
+        List<String> result = new ArrayList<>();
+
+        String transformed = keyValue;
+        for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
+            List<Object> output = transformer.beforePull(
+                    null,
+                    null,
+                    Collections.<Object>singletonList(transformed));
+            if (output != null && !output.isEmpty()) {
+                transformed = output.get(0).toString();
+            }
+        }
+
+        IntAttrName intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), AnyTypeKind.USER);
+
+        if (intAttrName.getField() != null) {
+            switch (intAttrName.getField()) {
+                case "key":
+                    User byKey = userDAO.find(transformed);
+                    if (byKey != null) {
+                        result.add(byKey.getUsername());
+                    }
+                    break;
+
+                case "username":
+                    User byUsername = userDAO.findByUsername(transformed);
+                    if (byUsername != null) {
+                        result.add(byUsername.getUsername());
+                    }
+                    break;
+
+                default:
+            }
+        } else if (intAttrName.getSchemaType() != null) {
+            switch (intAttrName.getSchemaType()) {
+                case PLAIN:
+                    PlainAttrValue value = entityFactory.newEntity(UPlainAttrValue.class);
+
+                    PlainSchema schema = plainSchemaDAO.find(intAttrName.getSchemaName());
+                    if (schema == null) {
+                        value.setStringValue(transformed);
+                    } else {
+                        try {
+                            value.parseValue(schema, transformed);
+                        } catch (ParsingValidationException e) {
+                            LOG.error("While parsing provided key value {}", transformed, e);
+                            value.setStringValue(transformed);
+                        }
+                    }
+
+                    CollectionUtils.collect(
+                            userDAO.findByAttrValue(intAttrName.getSchemaName(), value),
+                            new Transformer<User, String>() {
+
+                        @Override
+                        public String transform(final User input) {
+                            return input.getUsername();
+                        }
+                    }, result);
+                    break;
+
+                case DERIVED:
+                    CollectionUtils.collect(
+                            userDAO.findByDerAttrValue(intAttrName.getSchemaName(), transformed),
+                            new Transformer<User, String>() {
+
+                        @Override
+                        public String transform(final User input) {
+                            return input.getUsername();
+                        }
+                    }, result);
+                    break;
+
+                default:
+            }
+        }
+
+        return result;
+    }
+
+    private List<SAML2IdPActions> getActions(final SAML2IdPEntity idp) {
+        List<SAML2IdPActions> actions = new ArrayList<>();
+
+        for (String className : idp.getActionsClassNames()) {
+            try {
+                Class<?> actionsClass = Class.forName(className);
+                SAML2IdPActions idpActions = (SAML2IdPActions) ApplicationContextProvider.getBeanFactory().
+                        createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+
+                actions.add(idpActions);
+            } catch (Exception e) {
+                LOG.warn("Class '{}' not found", className, e);
+            }
+        }
+
+        return actions;
+    }
+
+    private void fill(final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO, final UserTO userTO) {
+        for (ItemTO item : idp.getItems()) {
+            IntAttrName intAttrName = intAttrNameParser.parse(item.getIntAttrName(), AnyTypeKind.USER);
+
+            List<String> values = Collections.emptyList();
+            AttrTO samlAttr = responseTO.getAttr(item.getExtAttrName());
+            if (samlAttr != null && !samlAttr.getValues().isEmpty()) {
+                values = samlAttr.getValues();
+
+                List<Object> transformed = new ArrayList<Object>(values);
+                for (ItemTransformer transformer : MappingUtils.getItemTransformers(item)) {
+                    transformed = transformer.beforePull(null, userTO, transformed);
+                }
+                values.clear();
+                for (Object value : transformed) {
+                    values.add(value.toString());
+                }
+            }
+
+            if (intAttrName.getField() != null) {
+                switch (intAttrName.getField()) {
+                    case "username":
+                        if (!values.isEmpty()) {
+                            userTO.setUsername(values.get(0));
+                        }
+                        break;
+
+                    default:
+                        LOG.warn("Unsupported: {}", intAttrName.getField());
+                }
+            } else if (intAttrName.getSchemaType() != null) {
+                switch (intAttrName.getSchemaType()) {
+                    case PLAIN:
+                        AttrTO attr = userTO.getPlainAttr(intAttrName.getSchemaName());
+                        if (attr == null) {
+                            attr = new AttrTO.Builder().schema(intAttrName.getSchemaName()).build();
+                            userTO.getPlainAttrs().add(attr);
+                        }
+                        attr.getValues().clear();
+                        attr.getValues().addAll(values);
+                        break;
+
+                    default:
+                        LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchemaName());
+                }
+            }
+        }
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public String create(final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO, final String nameID) {
+        UserTO userTO = new UserTO();
+
+        if (idp.getUserTemplate() != null) {
+            templateUtils.apply(userTO, idp.getUserTemplate());
+        }
+
+        List<SAML2IdPActions> actions = getActions(idp);
+        for (SAML2IdPActions action : actions) {
+            userTO = action.beforeCreate(userTO, responseTO);
+        }
+
+        fill(idp, responseTO, userTO);
+
+        if (userTO.getRealm() == null) {
+            userTO.setRealm(SyncopeConstants.ROOT_REALM);
+        }
+        if (userTO.getUsername() == null) {
+            userTO.setUsername(nameID);
+        }
+
+        Pair<String, List<PropagationStatus>> created = provisioningManager.create(userTO, false, false);
+        userTO = binder.getUserTO(created.getKey());
+
+        for (SAML2IdPActions action : actions) {
+            userTO = action.afterCreate(userTO, responseTO);
+        }
+
+        return userTO.getUsername();
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public String update(final String username, final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO) {
+        UserTO userTO = binder.getUserTO(username);
+        UserTO original = SerializationUtils.clone(userTO);
+
+        fill(idp, responseTO, userTO);
+
+        UserPatch userPatch = AnyOperations.diff(userTO, original, true);
+
+        List<SAML2IdPActions> actions = getActions(idp);
+        for (SAML2IdPActions action : actions) {
+            userPatch = action.beforeUpdate(userPatch, responseTO);
+        }
+
+        Pair<String, List<PropagationStatus>> updated = provisioningManager.update(userPatch, false);
+        userTO = binder.getUserTO(updated.getKey());
+
+        for (SAML2IdPActions action : actions) {
+            userTO = action.afterUpdate(userTO, responseTO);
+        }
+
+        return userTO.getUsername();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
index 7d1a5da..5588334 100644
--- a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
+++ b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.entity;
 
 import java.util.List;
+import java.util.Set;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 
 public interface SAML2IdP extends Entity {
@@ -35,6 +36,14 @@ public interface SAML2IdP extends Entity {
 
     void setMetadata(byte[] metadata);
 
+    boolean isCreateUnmatching();
+
+    void setCreateUnmatching(boolean createUnmatching);
+
+    boolean isUpdateMatching();
+
+    void setUpdateMatching(boolean updateMatching);
+
     boolean isUseDeflateEncoding();
 
     void setUseDeflateEncoding(boolean useDeflateEncoding);
@@ -43,6 +52,10 @@ public interface SAML2IdP extends Entity {
 
     void setBindingType(SAML2BindingType bindingType);
 
+    SAML2UserTemplate getUserTemplate();
+
+    void setUserTemplate(SAML2UserTemplate userTemplate);
+
     SAML2IdPItem getConnObjectKeyItem();
 
     void setConnObjectKeyItem(SAML2IdPItem item);
@@ -51,4 +64,6 @@ public interface SAML2IdP extends Entity {
 
     List<? extends SAML2IdPItem> getItems();
 
+    Set<String> getActionsClassNames();
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1cf0d2a0/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
new file mode 100644
index 0000000..a26227b
--- /dev/null
+++ b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
@@ -0,0 +1,26 @@
+/*
+ * 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.api.entity;
+
+public interface SAML2UserTemplate extends AnyTemplate {
+
+    SAML2IdP getIdP();
+
+    void setIdP(SAML2IdP idp);
+}


[4/4] syncope git commit: [SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to

Posted by il...@apache.org.
[SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to provide the actual user data


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/f1c004bf
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/f1c004bf
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/f1c004bf

Branch: refs/heads/master
Commit: f1c004bfcd30c529757e37743e666c935beeeeb5
Parents: 7eb33dc
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Aug 3 17:53:38 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Aug 3 17:55:29 2017 +0200

----------------------------------------------------------------------
 .../console/tasks/SchedTaskDirectoryPanel.java  |   5 +-
 .../markup/html/bootstrap/dialog/BaseModal.java |   4 +-
 .../wizards/any/UserTemplateWizardBuilder.java  |  17 ++
 .../console/panels/RealmDetails_it.properties   |   2 +-
 .../jpa/entity/JPAAnyTemplateRealm.java         |   4 +-
 .../provisioning/java/utils/MappingUtils.java   |   6 +-
 .../provisioning/java/utils/TemplateUtils.java  |  74 ++---
 .../saml2lsp/agent/AbstractSAML2SPServlet.java  |   1 -
 .../console/panels/SAML2IdPsDirectoryPanel.java | 110 ++++++-
 .../console/rest/SAML2IdPsRestClient.java       |   5 +
 .../console/wizards/SAML2IdPWizardBuilder.java  |  36 ++-
 .../panels/SAML2IdPsDirectoryPanel.properties   |   4 +
 .../SAML2IdPsDirectoryPanel_it.properties       |   4 +
 .../SAML2IdPsDirectoryPanel_pt_BR.properties    |   4 +
 .../SAML2IdPsDirectoryPanel_ru.properties       |   4 +
 .../syncope/common/lib/to/SAML2IdPTO.java       |  41 +++
 .../common/lib/to/SAML2LoginResponseTO.java     |  14 +
 .../common/lib/to/SAML2ReceivedResponseTO.java  |  10 -
 .../syncope/core/logic/SAML2IdPLogic.java       |  16 +-
 .../apache/syncope/core/logic/SAML2SPLogic.java | 140 +++------
 ...AML2SPClassPathScanImplementationLookup.java |  78 +++++
 .../syncope/core/logic/saml2/SAML2IdPCache.java |  16 +-
 .../core/logic/saml2/SAML2IdPEntity.java        |  51 ++--
 .../core/logic/saml2/SAML2ReaderWriter.java     |   1 -
 .../core/logic/saml2/SAML2UserManager.java      | 292 +++++++++++++++++++
 .../core/persistence/api/entity/SAML2IdP.java   |  15 +
 .../api/entity/SAML2UserTemplate.java           |  26 ++
 .../jpa/entity/JPASAML2EntityFactory.java       |   3 +
 .../persistence/jpa/entity/JPASAML2IdP.java     |  62 ++++
 .../jpa/entity/JPASAML2UserTemplate.java        |  52 ++++
 .../core/provisioning/api/SAML2IdPActions.java  |  34 +++
 .../java/DefaultSAML2IdPActions.java            |  48 +++
 .../java/data/SAML2IdPDataBinderImpl.java       |  28 ++
 .../rest/api/service/SAML2IdPService.java       |  11 +
 .../rest/cxf/service/SAML2IdPServiceImpl.java   |   6 +
 .../apache/syncope/fit/core/SAML2ITCase.java    |  13 +-
 36 files changed, 1026 insertions(+), 211 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index f09c0dc..7663235 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -72,8 +72,8 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
 
     private static final long serialVersionUID = 4984337552918213290L;
 
-    private static final String GROUP_MEMBER_PROVISION_TASKJOB
-            = "org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate";
+    private static final String GROUP_MEMBER_PROVISION_TASKJOB =
+            "org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate";
 
     protected final Class<T> reference;
 
@@ -116,6 +116,7 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
             @Override
             protected Serializable onApplyInternal(
                     final TemplatableTO targetObject, final String type, final AnyTO anyTO) {
+
                 targetObject.getTemplates().put(type, anyTO);
                 new TaskRestClient().update(SchedTaskTO.class.cast(targetObject));
                 return targetObject;

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
index a5716e6..1df1e6c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
@@ -164,8 +164,8 @@ public class BaseModal<T extends Serializable> extends Modal<T> {
 
     private BaseModal<T> setInternalContent(final Panel component) {
         if (!component.getId().equals(getContentId())) {
-            throw new WicketRuntimeException(
-                    "Modal content id is wrong. Component ID:" + component.getId() + "; content ID: " + getContentId());
+            throw new WicketRuntimeException("Modal content id is wrong. "
+                    + "Component ID: " + component.getId() + "; content ID: " + getContentId());
         }
 
         content.replaceWith(component);

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
index 8d0dc5b..3c8111c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
@@ -34,6 +34,23 @@ public class UserTemplateWizardBuilder extends UserWizardBuilder implements Temp
     private final TemplatableTO templatable;
 
     public UserTemplateWizardBuilder(
+            final UserTO template,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageRef) {
+
+        super(anyTypeClasses, formLayoutInfo, pageRef);
+        templatable = null;
+
+        if (template == null) {
+            setItem(new UserWrapper(new UserTO()));
+        } else {
+            setItem(new UserWrapper(template));
+        }
+
+    }
+
+    public UserTemplateWizardBuilder(
             final TemplatableTO templatable,
             final List<String> anyTypeClasses,
             final UserFormLayoutInfo formLayoutInfo,

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
index 05d20c2..80a008a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/RealmDetails_it.properties
@@ -17,5 +17,5 @@
 fullPath=Percorso
 accountPolicy=Account Policy
 passwordPolicy=Password Policy
-actionsClassNames=Actions
+actionsClassNames=Azioni
 resources=Risorse assegnate

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
index 46949ee..05ecf6b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyTemplateRealm.java
@@ -31,10 +31,10 @@ import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractAnyTempla
         @UniqueConstraint(columnNames = { "realm_id", "anyType_id" }))
 public class JPAAnyTemplateRealm extends AbstractAnyTemplate implements AnyTemplateRealm {
 
-    public static final String TABLE = "AnyTemplateRealm";
-
     private static final long serialVersionUID = 1863029633568957907L;
 
+    public static final String TABLE = "AnyTemplateRealm";
+
     @ManyToOne
     private JPARealm realm;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
index 2c71f37..ce794af 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -189,7 +189,7 @@ public final class MappingUtils {
         return evaluateNAME(evalConnObjectLink, connObjectKey);
     }
 
-    private static List<ItemTransformer> getMappingItemTransformers(
+    private static List<ItemTransformer> getItemTransformers(
             final String propagationJEXLTransformer,
             final String pullJEXLTransformer,
             final List<String> mappingItemTransformerClassNames) {
@@ -224,14 +224,14 @@ public final class MappingUtils {
     }
 
     public static List<ItemTransformer> getItemTransformers(final ItemTO item) {
-        return getMappingItemTransformers(
+        return getItemTransformers(
                 item.getPropagationJEXLTransformer(),
                 item.getPullJEXLTransformer(),
                 item.getTransformerClassNames());
     }
 
     public static List<ItemTransformer> getItemTransformers(final Item item) {
-        return getMappingItemTransformers(
+        return getItemTransformers(
                 item.getPropagationJEXLTransformer(),
                 item.getPullJEXLTransformer(),
                 item.getTransformerClassNames());

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
index d56f651..ef47ef1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
@@ -126,48 +126,52 @@ public class TemplateUtils {
     @Transactional(readOnly = true)
     public <T extends AnyTO> void apply(final T anyTO, final AnyTemplate anyTemplate) {
         if (anyTemplate != null) {
-            AnyTO template = anyTemplate.get();
-            fill(anyTO, template);
-
-            if (template instanceof AnyObjectTO) {
-                fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-                fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-            } else if (template instanceof UserTO) {
-                if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
-                    String evaluated = JexlUtils.evaluate(((UserTO) template).getUsername(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((UserTO) anyTO).setUsername(evaluated);
-                    }
+            apply(anyTO, anyTemplate.get());
+        }
+    }
+
+    @Transactional(readOnly = true)
+    public <T extends AnyTO> void apply(final T anyTO, final AnyTO template) {
+        fill(anyTO, template);
+
+        if (template instanceof AnyObjectTO) {
+            fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+            fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+        } else if (template instanceof UserTO) {
+            if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
+                String evaluated = JexlUtils.evaluate(((UserTO) template).getUsername(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((UserTO) anyTO).setUsername(evaluated);
                 }
+            }
 
-                if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
-                    String evaluated = JexlUtils.evaluate(((UserTO) template).getPassword(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((UserTO) anyTO).setPassword(evaluated);
-                    }
+            if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
+                String evaluated = JexlUtils.evaluate(((UserTO) template).getPassword(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((UserTO) anyTO).setPassword(evaluated);
                 }
+            }
 
-                fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-                fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
-            } else if (template instanceof GroupTO) {
-                if (StringUtils.isNotBlank(((GroupTO) template).getName())) {
-                    String evaluated = JexlUtils.evaluate(((GroupTO) template).getName(), anyTO, new MapContext());
-                    if (StringUtils.isNotBlank(evaluated)) {
-                        ((GroupTO) anyTO).setName(evaluated);
-                    }
+            fillRelationships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+            fillMemberships((GroupableRelatableTO) anyTO, ((GroupableRelatableTO) template));
+        } else if (template instanceof GroupTO) {
+            if (StringUtils.isNotBlank(((GroupTO) template).getName())) {
+                String evaluated = JexlUtils.evaluate(((GroupTO) template).getName(), anyTO, new MapContext());
+                if (StringUtils.isNotBlank(evaluated)) {
+                    ((GroupTO) anyTO).setName(evaluated);
                 }
+            }
 
-                if (((GroupTO) template).getUserOwner() != null) {
-                    final User userOwner = userDAO.find(((GroupTO) template).getUserOwner());
-                    if (userOwner != null) {
-                        ((GroupTO) anyTO).setUserOwner(userOwner.getKey());
-                    }
+            if (((GroupTO) template).getUserOwner() != null) {
+                final User userOwner = userDAO.find(((GroupTO) template).getUserOwner());
+                if (userOwner != null) {
+                    ((GroupTO) anyTO).setUserOwner(userOwner.getKey());
                 }
-                if (((GroupTO) template).getGroupOwner() != null) {
-                    final Group groupOwner = groupDAO.find(((GroupTO) template).getGroupOwner());
-                    if (groupOwner != null) {
-                        ((GroupTO) anyTO).setGroupOwner(groupOwner.getKey());
-                    }
+            }
+            if (((GroupTO) template).getGroupOwner() != null) {
+                final Group groupOwner = groupDAO.find(((GroupTO) template).getGroupOwner());
+                if (groupOwner != null) {
+                    ((GroupTO) anyTO).setGroupOwner(groupOwner.getKey());
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
index ecc14fe..f2bbabf 100644
--- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
+++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java
@@ -97,7 +97,6 @@ public abstract class AbstractSAML2SPServlet extends HttpServlet {
         SAML2ReceivedResponseTO receivedResponseTO = new SAML2ReceivedResponseTO();
         receivedResponseTO.setSpEntityID(spEntityID);
         receivedResponseTO.setUrlContext(urlContext);
-        receivedResponseTO.setClientAddress(clientAddress);
         receivedResponseTO.setSamlResponse(samlResponse);
         receivedResponseTO.setRelayState(relayState);
         return receivedResponseTO;

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
index 2ab6d8d..2874a84 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.console.panels;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -30,8 +31,10 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel.SAML2IdPsProvider;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.SAML2IdPsRestClient;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
@@ -42,13 +45,18 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizard;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.any.AnyWrapper;
+import org.apache.syncope.client.console.wizards.any.UserTemplateWizardBuilder;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
@@ -58,6 +66,7 @@ import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.util.crypt.Base64;
 
 public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
@@ -69,6 +78,8 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
 
     private final BaseModal<String> metadataModal = new BaseModal<>("outer");
 
+    private final BaseModal<Serializable> templateModal;
+
     public SAML2IdPsDirectoryPanel(final String id, final PageReference pageRef) {
         super(id, new Builder<SAML2IdPTO, SAML2IdPTO, SAML2IdPsRestClient>(new SAML2IdPsRestClient(), pageRef) {
 
@@ -117,6 +128,29 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         setWindowClosedReloadCallback(metadataModal);
         metadataModal.size(Modal.Size.Large);
 
+        templateModal = new BaseModal<Serializable>("outer") {
+
+            private static final long serialVersionUID = 5787433530654262016L;
+
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setFooterVisible(false);
+            }
+        };
+        templateModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+
+            private static final long serialVersionUID = 8804221891699487139L;
+
+            @Override
+            public void onClose(final AjaxRequestTarget target) {
+//                target.add(content);
+                templateModal.show(false);
+            }
+        });
+        templateModal.size(Modal.Size.Large);
+        addOuterObject(templateModal);
+
         initResultTable();
 
         final ImportMetadata importMetadata = new ImportMetadata("importMetadata", container, pageRef);
@@ -172,17 +206,6 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
 
         panel.add(new ActionLink<SAML2IdPTO>() {
 
-            private static final long serialVersionUID = -3722207913631435501L;
-
-            @Override
-            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
-                SAML2IdPTO object = restClient.read(model.getObject().getKey());
-                send(SAML2IdPsDirectoryPanel.this, Broadcast.EXACT,
-                        new AjaxWizard.EditItemActionEvent<>(object, target));
-            }
-        }, ActionLink.ActionType.EDIT, SAML2SPEntitlement.IDP_UPDATE);
-        panel.add(new ActionLink<SAML2IdPTO>() {
-
             private static final long serialVersionUID = -7978723352517770645L;
 
             @Override
@@ -200,6 +223,49 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         }, ActionLink.ActionType.HTML, SAML2SPEntitlement.IDP_READ);
         panel.add(new ActionLink<SAML2IdPTO>() {
 
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
+                SAML2IdPTO object = restClient.read(model.getObject().getKey());
+                send(SAML2IdPsDirectoryPanel.this, Broadcast.EXACT,
+                        new AjaxWizard.EditItemActionEvent<>(object, target));
+            }
+        }, ActionLink.ActionType.EDIT, SAML2SPEntitlement.IDP_UPDATE);
+        panel.add(new ActionLink<SAML2IdPTO>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final SAML2IdPTO ignore) {
+                final SAML2IdPTO object = restClient.read(model.getObject().getKey());
+
+                UserTemplateWizardBuilder builder = new UserTemplateWizardBuilder(
+                        object.getUserTemplate(),
+                        new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses(),
+                        new UserFormLayoutInfo(),
+                        pageRef) {
+
+                    private static final long serialVersionUID = -7978723352517770634L;
+
+                    @Override
+                    protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
+                        object.setUserTemplate(modelObject.getInnerObject());
+                        restClient.update(object);
+
+                        return modelObject;
+                    }
+                };
+                templateModal.header(Model.of(StringUtils.capitalize(
+                        new StringResourceModel("template.title", SAML2IdPsDirectoryPanel.this).getString())));
+                templateModal.setContent(builder.build(BaseModal.CONTENT_ID));
+                templateModal.show(true);
+                target.add(templateModal);
+
+            }
+        }, ActionLink.ActionType.TEMPLATE, SAML2SPEntitlement.IDP_UPDATE);
+        panel.add(new ActionLink<SAML2IdPTO>() {
+
             private static final long serialVersionUID = -5467832321897812767L;
 
             @Override
@@ -220,6 +286,28 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
         return panel;
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+
+        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
+            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+            WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
+
+            if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
+                final IModel<Serializable> model = new CompoundPropertyModel<>(modalPanel.getItem());
+                templateModal.setFormModel(model);
+                templateModal.header(newItemEvent.getResourceModel());
+                newItemEvent.getTarget().add(templateModal.setContent(modalPanel));
+                templateModal.show(true);
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
+                templateModal.close(newItemEvent.getTarget());
+            }
+        }
+    }
+
     protected final class SAML2IdPsProvider extends DirectoryDataProvider<SAML2IdPTO> {
 
         private static final long serialVersionUID = -185944053385660794L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
index ba67f57..c7e6e0c 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPsRestClient.java
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.rest;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
@@ -33,6 +34,10 @@ public class SAML2IdPsRestClient extends BaseRestClient {
         return getService(SAML2IdPService.class).list();
     }
 
+    public Set<String> getActionsClasses() {
+        return getService(SAML2IdPService.class).getActionsClasses();
+    }
+
     public void importIdPs(final InputStream input) {
         SyncopeConsoleSession.get().
                 getService(MediaType.APPLICATION_XML_TYPE, SAML2IdPService.class).importFromMetadata(input);

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
index 8567dd2..0fc7b22 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java
@@ -29,6 +29,7 @@ import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel;
 import org.apache.syncope.client.console.rest.SAML2IdPsRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
 import org.apache.syncope.client.console.wizards.resources.JEXLTransformersTogglePanel;
@@ -42,9 +43,12 @@ import org.apache.wicket.extensions.wizard.WizardModel;
 import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.util.ListModel;
 
 public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
 
@@ -54,6 +58,16 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
 
     private final SAML2IdPsDirectoryPanel directoryPanel;
 
+    private final IModel<List<String>> actionsClasses = new LoadableDetachableModel<List<String>>() {
+
+        private static final long serialVersionUID = 5275935387613157437L;
+
+        @Override
+        protected List<String> load() {
+            return new ArrayList<>(restClient.getActionsClasses());
+        }
+    };
+
     public SAML2IdPWizardBuilder(
             final SAML2IdPsDirectoryPanel directoryPanel, final SAML2IdPTO idpTO, final PageReference pageRef) {
 
@@ -68,8 +82,7 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
         Mapping mapping = new Mapping(modelObject);
         mapping.setOutputMarkupId(true);
 
-        ItemTransformersTogglePanel mapItemTransformers =
-                new ItemTransformersTogglePanel(mapping, pageRef);
+        ItemTransformersTogglePanel mapItemTransformers = new ItemTransformersTogglePanel(mapping, pageRef);
         addOuterObject(mapItemTransformers);
         JEXLTransformersTogglePanel jexlTransformers = new JEXLTransformersTogglePanel(mapping, pageRef);
         addOuterObject(jexlTransformers);
@@ -80,7 +93,7 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
         return wizardModel;
     }
 
-    private static final class IdP extends WizardStep {
+    private final class IdP extends WizardStep {
 
         private static final long serialVersionUID = 854012593185195024L;
 
@@ -94,6 +107,14 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
             name.setRequired(true);
             fields.add(name);
 
+            AjaxCheckBoxPanel createUnmatching = new AjaxCheckBoxPanel(
+                    "field", "createUnmatching", new PropertyModel<Boolean>(idpTO, "createUnmatching"), false);
+            fields.add(createUnmatching);
+
+            AjaxCheckBoxPanel updateMatching = new AjaxCheckBoxPanel(
+                    "field", "updateMatching", new PropertyModel<Boolean>(idpTO, "updateMatching"), false);
+            fields.add(updateMatching);
+
             AjaxCheckBoxPanel useDeflateEncoding = new AjaxCheckBoxPanel(
                     "field", "useDeflateEncoding", new PropertyModel<Boolean>(idpTO, "useDeflateEncoding"), false);
             fields.add(useDeflateEncoding);
@@ -104,6 +125,15 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> {
             bindingType.setChoices(Arrays.asList(SAML2BindingType.values()));
             fields.add(bindingType);
 
+            AjaxPalettePanel<String> actionsClassNames = new AjaxPalettePanel.Builder<String>().
+                    setAllowMoveAll(true).setAllowOrder(true).
+                    setName(new StringResourceModel("actionsClassNames", directoryPanel).getString()).
+                    build("field",
+                            new PropertyModel<List<String>>(idpTO, "actionsClassNames"),
+                            new ListModel<>(actionsClasses.getObject()));
+            actionsClassNames.setOutputMarkupId(true);
+            fields.add(actionsClassNames);
+
             add(new ListView<Component>("fields", fields) {
 
                 private static final long serialVersionUID = -9180479401817023838L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
index f9c6403..97d8690 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties
@@ -21,3 +21,7 @@ any.edit=Edit ${entityID}
 connObjectKeyValidation=There must be exactly one Remote Key
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Actions
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
index 312da5d..76464c0 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties
@@ -21,3 +21,7 @@ any.edit=Modifica ${entityID}
 connObjectKeyValidation=Deve essere definito esattamente una Chiave remota
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Azioni
+template.title=template utenti

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
index 4e053f7..d81f0a8 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties
@@ -21,3 +21,7 @@ any.edit=Alterar ${entityID}
 connObjectKeyValidation=Precisa ser exatamente um Remote Key
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=Actions
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
----------------------------------------------------------------------
diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
index 8bedf47..8712eeb 100644
--- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
+++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties
@@ -21,3 +21,7 @@ any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${entityID}
 connObjectKeyValidation=\u0422\u0430\u043c \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0434\u0438\u0441\u0442\u0430\u043d\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430
 bindingType=Binding
 html.title=metadata
+createUnmatching=Create unmatching users
+updateMatching=Update matching users
+actionsClassNames=\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f
+template.title=user template

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
index ffda86d..022267d 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java
@@ -20,7 +20,9 @@ package org.apache.syncope.common.lib.to;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.PathParam;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
@@ -45,14 +47,22 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
 
     private String metadata;
 
+    private boolean createUnmatching;
+
+    private boolean updateMatching;
+
     private boolean useDeflateEncoding;
 
     private SAML2BindingType bindingType;
 
     private boolean logoutSupported;
 
+    private UserTO userTemplate;
+
     private final List<ItemTO> items = new ArrayList<>();
 
+    private final Set<String> actionsClassNames = new HashSet<>();
+
     @Override
     public String getKey() {
         return key;
@@ -88,6 +98,22 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         this.metadata = metadata;
     }
 
+    public boolean isCreateUnmatching() {
+        return createUnmatching;
+    }
+
+    public void setCreateUnmatching(final boolean createUnmatching) {
+        this.createUnmatching = createUnmatching;
+    }
+
+    public boolean isUpdateMatching() {
+        return updateMatching;
+    }
+
+    public void setUpdateMatching(final boolean updateMatching) {
+        this.updateMatching = updateMatching;
+    }
+
     public boolean isUseDeflateEncoding() {
         return useDeflateEncoding;
     }
@@ -112,6 +138,14 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         this.logoutSupported = logoutSupported;
     }
 
+    public UserTO getUserTemplate() {
+        return userTemplate;
+    }
+
+    public void setUserTemplate(final UserTO userTemplate) {
+        this.userTemplate = userTemplate;
+    }
+
     @Override
     public ItemTO getConnObjectKeyItem() {
         return IterableUtils.find(getItems(), new Predicate<ItemTO>() {
@@ -154,4 +188,11 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO, ItemContai
         return this.items.remove(item);
     }
 
+    @XmlElementWrapper(name = "actionsClassNames")
+    @XmlElement(name = "actionsClassName")
+    @JsonProperty("actionsClassNames")
+    public Set<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
index f905035..2b02eb4 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2LoginResponseTO.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.common.lib.to;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Date;
 import java.util.HashSet;
@@ -26,6 +27,8 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 
 @XmlRootElement(name = "saml2LoginResponse")
@@ -124,6 +127,17 @@ public class SAML2LoginResponseTO extends AbstractBaseBean {
         this.username = username;
     }
 
+    @JsonIgnore
+    public AttrTO getAttr(final String schema) {
+        return IterableUtils.find(attrs, new Predicate<AttrTO>() {
+
+            @Override
+            public boolean evaluate(final AttrTO object) {
+                return object.getSchema().equals(schema);
+            }
+        });
+    }
+
     @XmlElementWrapper(name = "attrs")
     @XmlElement(name = "attr")
     @JsonProperty("attrs")

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
index 4ea2dec..556be9b 100644
--- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
+++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java
@@ -33,8 +33,6 @@ public class SAML2ReceivedResponseTO extends AbstractBaseBean {
 
     private String urlContext;
 
-    private String clientAddress;
-
     private String samlResponse;
 
     private String relayState;
@@ -55,14 +53,6 @@ public class SAML2ReceivedResponseTO extends AbstractBaseBean {
         this.urlContext = urlContext;
     }
 
-    public String getClientAddress() {
-        return clientAddress;
-    }
-
-    public void setClientAddress(final String clientAddress) {
-        this.clientAddress = clientAddress;
-    }
-
     public String getSamlResponse() {
         return samlResponse;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
index b432c04..ae3f075 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java
@@ -26,6 +26,7 @@ import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.List;
+import java.util.Set;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.commons.lang3.ArrayUtils;
@@ -35,6 +36,7 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
+import org.apache.syncope.core.logic.init.SAML2SPClassPathScanImplementationLookup;
 import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter;
 import org.apache.syncope.core.logic.saml2.SAML2IdPCache;
 import org.apache.syncope.core.logic.saml2.SAML2IdPEntity;
@@ -68,8 +70,16 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
     private SAML2IdPDAO idpDAO;
 
     @Autowired
+    private SAML2SPClassPathScanImplementationLookup implLookup;
+
+    @Autowired
     private SAML2ReaderWriter saml2rw;
 
+    @PreAuthorize("isAuthenticated()")
+    public Set<String> getActionsClasses() {
+        return implLookup.getActionsClasses();
+    }
+
     private SAML2IdPTO complete(final SAML2IdP idp, final SAML2IdPTO idpTO) {
         SAML2IdPEntity idpEntity = cache.get(idpTO.getEntityID());
         if (idpEntity == null) {
@@ -159,7 +169,7 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
             connObjectKeyItem.setExtAttrName("NameID");
             idpTO.setConnObjectKeyItem(connObjectKeyItem);
 
-            SAML2IdPEntity idp = cache.put(idpEntityDescriptor, connObjectKeyItem, false, SAML2BindingType.POST);
+            SAML2IdPEntity idp = cache.put(idpEntityDescriptor, idpTO);
             if (idp.getSSOLocation(SAML2BindingType.POST) != null) {
                 idpTO.setBindingType(SAML2BindingType.POST);
             } else if (idp.getSSOLocation(SAML2BindingType.REDIRECT) != null) {
@@ -225,9 +235,7 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> {
 
         saml2Idp = idpDAO.save(binder.update(saml2Idp, saml2IdpTO));
 
-        idpEntity.setUseDeflateEncoding(saml2Idp.isUseDeflateEncoding());
-        idpEntity.setBindingType(saml2Idp.getBindingType());
-        idpEntity.setConnObjectKeyItem(binder.getIdPTO(saml2Idp).getConnObjectKeyItem());
+        idpEntity.setIdpTO(binder.getIdPTO(saml2Idp));
     }
 
     @PreAuthorize("hasRole('" + SAML2SPEntitlement.IDP_DELETE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
index a9b63ed..f6953e6 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.logic;
 
+import org.apache.syncope.core.logic.saml2.SAML2UserManager;
 import com.fasterxml.uuid.Generators;
 import com.fasterxml.uuid.impl.RandomBasedGenerator;
 import java.io.OutputStream;
@@ -25,14 +26,12 @@ import java.io.OutputStreamWriter;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Resource;
-import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
@@ -43,9 +42,7 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.SAML2RequestTO;
 import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
-import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
@@ -53,23 +50,11 @@ import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter;
 import org.apache.syncope.core.logic.saml2.SAML2IdPCache;
 import org.apache.syncope.core.logic.saml2.SAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.SAML2IdPDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
-import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
-import org.apache.syncope.core.provisioning.api.utils.EntityUtils;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.joda.time.DateTime;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.core.xml.schema.XSString;
@@ -118,8 +103,8 @@ import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
-import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
 import org.apache.syncope.core.spring.security.Encryptor;
 
@@ -149,27 +134,18 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
     private SAML2IdPCache cache;
 
     @Autowired
-    private UserDAO userDAO;
+    private SAML2UserManager userManager;
 
     @Autowired
     private SAML2IdPDAO saml2IdPDAO;
 
     @Autowired
-    private PlainSchemaDAO plainSchemaDAO;
-
-    @Autowired
     private AccessTokenDAO accessTokenDAO;
 
     @Autowired
-    private IntAttrNameParser intAttrNameParser;
-
-    @Autowired
     private AuthDataAccessor authDataAccessor;
 
     @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
     private SAML2ReaderWriter saml2rw;
 
     @Resource(name = "syncopeJWTSSOProviderDelegate")
@@ -352,73 +328,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         return requestTO;
     }
 
-    private List<String> findMatchingUser(final String keyValue, final ItemTO connObjectKeyItem) {
-        List<String> result = new ArrayList<>();
-
-        String transformed = keyValue;
-        for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
-            List<Object> output = transformer.beforePull(
-                    null,
-                    null,
-                    Collections.<Object>singletonList(transformed));
-            if (output != null && !output.isEmpty()) {
-                transformed = output.get(0).toString();
-            }
-        }
-
-        IntAttrName intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), AnyTypeKind.USER);
-
-        if (intAttrName.getField() != null) {
-            switch (intAttrName.getField()) {
-                case "key":
-                    User byKey = userDAO.find(transformed);
-                    if (byKey != null) {
-                        result.add(byKey.getKey());
-                    }
-                    break;
-
-                case "username":
-                    User byUsername = userDAO.findByUsername(transformed);
-                    if (byUsername != null) {
-                        result.add(byUsername.getKey());
-                    }
-                    break;
-
-                default:
-            }
-        } else if (intAttrName.getSchemaType() != null) {
-            switch (intAttrName.getSchemaType()) {
-                case PLAIN:
-                    PlainAttrValue value = entityFactory.newEntity(UPlainAttrValue.class);
-
-                    PlainSchema schema = plainSchemaDAO.find(intAttrName.getSchemaName());
-                    if (schema == null) {
-                        value.setStringValue(transformed);
-                    } else {
-                        try {
-                            value.parseValue(schema, transformed);
-                        } catch (ParsingValidationException e) {
-                            LOG.error("While parsing provided key value {}", transformed, e);
-                            value.setStringValue(transformed);
-                        }
-                    }
-
-                    CollectionUtils.collect(userDAO.findByAttrValue(intAttrName.getSchemaName(), value),
-                            EntityUtils.keyTransformer(), result);
-                    break;
-
-                case DERIVED:
-                    CollectionUtils.collect(userDAO.findByDerAttrValue(intAttrName.getSchemaName(), transformed),
-                            EntityUtils.keyTransformer(), result);
-                    break;
-
-                default:
-            }
-        }
-
-        return result;
-    }
-
     @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')")
     public SAML2LoginResponseTO validateLoginResponse(final SAML2ReceivedResponseTO response) {
         check();
@@ -454,7 +363,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         }
 
         // 3. validate the SAML response and, if needed, decrypt the provided assertion(s)
-        SAML2IdPEntity idp = getIdP(samlResponse.getIssuer().getValue());
+        final SAML2IdPEntity idp = getIdP(samlResponse.getIssuer().getValue());
         if (idp.getConnObjectKeyItem() == null) {
             throw new IllegalArgumentException("No mapping provided for SAML 2.0 IdP '" + idp.getId() + "'");
         }
@@ -463,7 +372,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
                     samlResponse,
                     idp,
                     getAssertionConsumerURL(response.getSpEntityID(), response.getUrlContext()),
-                    response.getClientAddress(),
                     relayState.getJwtClaims().getSubject(),
                     response.getSpEntityID());
         } catch (Exception e) {
@@ -474,7 +382,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         }
 
         // 4. prepare the result: find matching user (if any) and return the received attributes
-        SAML2LoginResponseTO responseTO = new SAML2LoginResponseTO();
+        final SAML2LoginResponseTO responseTO = new SAML2LoginResponseTO();
         responseTO.setIdp(idp.getId());
         responseTO.setSloSupported(idp.getSLOLocation(idp.getBindingType()) != null);
 
@@ -525,19 +433,49 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> {
         if (nameID == null) {
             throw new IllegalArgumentException("NameID not found");
         }
+        final String nameIDValue = nameID.getValue();
 
-        List<String> matchingUsers = keyValue == null
+        final List<String> matchingUsers = keyValue == null
                 ? Collections.<String>emptyList()
-                : findMatchingUser(keyValue, idp.getConnObjectKeyItem());
+                : userManager.findMatchingUser(keyValue, idp.getConnObjectKeyItem());
         LOG.debug("Found {} matching users for NameID {}", matchingUsers.size(), nameID.getValue());
 
+        String username;
         if (matchingUsers.isEmpty()) {
-            throw new NotFoundException("User matching the provided NameID value " + nameID.getValue());
+            if (idp.isCreateUnmatching()) {
+                LOG.debug("No user matching NameID {}, about to create", nameID.getValue());
+
+                username = AuthContextUtils.execWithAuthContext(
+                        AuthContextUtils.getDomain(), new AuthContextUtils.Executable<String>() {
+
+                    @Override
+                    public String exec() {
+                        return userManager.create(idp, responseTO, nameIDValue);
+                    }
+                });
+            } else {
+                throw new NotFoundException("User matching the provided NameID value " + nameID.getValue());
+            }
         } else if (matchingUsers.size() > 1) {
             throw new IllegalArgumentException("Several users match the provided NameID value " + nameID.getValue());
+        } else {
+            if (idp.isUpdateMatching()) {
+                LOG.debug("About to update {} for NameID {}", matchingUsers.get(0), nameID.getValue());
+
+                username = AuthContextUtils.execWithAuthContext(
+                        AuthContextUtils.getDomain(), new AuthContextUtils.Executable<String>() {
+
+                    @Override
+                    public String exec() {
+                        return userManager.update(matchingUsers.get(0), idp, responseTO);
+                    }
+                });
+            } else {
+                username = matchingUsers.get(0);
+            }
         }
-        responseTO.setUsername(userDAO.find(matchingUsers.get(0)).getUsername());
 
+        responseTO.setUsername(username);
         responseTO.setNameID(nameID.getValue());
 
         // 5. generate JWT for further access

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
new file mode 100644
index 0000000..13b3cab
--- /dev/null
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPClassPathScanImplementationLookup.java
@@ -0,0 +1,78 @@
+/*
+ * 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.logic.init;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.SyncopeLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+
+@Component
+public class SAML2SPClassPathScanImplementationLookup implements SyncopeLoader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ImplementationLookup.class);
+
+    private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.core";
+
+    private Set<String> actionsClasses;
+
+    @Override
+    public Integer getPriority() {
+        return Integer.MIN_VALUE;
+    }
+
+    @Override
+    public void load() {
+        actionsClasses = new HashSet<>();
+
+        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+        scanner.addIncludeFilter(new AssignableTypeFilter(SAML2IdPActions.class));
+
+        for (BeanDefinition bd : scanner.findCandidateComponents(DEFAULT_BASE_PACKAGE)) {
+            try {
+                Class<?> clazz = ClassUtils.resolveClassName(
+                        bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
+                boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
+
+                if (SAML2IdPActions.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    actionsClasses.add(clazz.getName());
+                }
+            } catch (Throwable t) {
+                LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
+            }
+        }
+
+        actionsClasses = Collections.unmodifiableSet(actionsClasses);
+    }
+
+    public Set<String> getActionsClasses() {
+        return actionsClasses;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
index ed244d0..e56b4f8 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java
@@ -28,8 +28,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import net.shibboleth.utilities.java.support.xml.XMLParserException;
-import org.apache.syncope.common.lib.to.ItemTO;
-import org.apache.syncope.common.lib.types.SAML2BindingType;
+import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.core.logic.init.SAML2SPLoader;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder;
@@ -66,13 +65,10 @@ public class SAML2IdPCache {
 
     public SAML2IdPEntity put(
             final EntityDescriptor entityDescriptor,
-            final ItemTO connObjectKeyItem,
-            final boolean useDeflateEncoding,
-            final SAML2BindingType bindingType)
+            final SAML2IdPTO idpTO)
             throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
 
-        SAML2IdPEntity idp = new SAML2IdPEntity(
-                entityDescriptor, connObjectKeyItem, useDeflateEncoding, bindingType, loader.getKeyPass());
+        SAML2IdPEntity idp = new SAML2IdPEntity(entityDescriptor, idpTO, loader.getKeyPass());
         cache.put(entityDescriptor.getEntityID(), idp);
         return idp;
     }
@@ -85,11 +81,7 @@ public class SAML2IdPCache {
         Element element = OpenSAMLUtil.getParserPool().parse(
                 new InputStreamReader(new ByteArrayInputStream(idp.getMetadata()))).getDocumentElement();
         EntityDescriptor entityDescriptor = (EntityDescriptor) OpenSAMLUtil.fromDom(element);
-        return put(
-                entityDescriptor,
-                binder.getIdPTO(idp).getConnObjectKeyItem(),
-                idp.isUseDeflateEncoding(),
-                idp.getBindingType());
+        return put(entityDescriptor, binder.getIdPTO(idp));
     }
 
     public SAML2IdPEntity remove(final String entityID) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
index c060e58..58ccfca 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java
@@ -31,7 +31,10 @@ import java.util.Base64;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.opensaml.saml.common.xml.SAMLConstants;
 import org.opensaml.saml.saml2.metadata.Endpoint;
@@ -51,11 +54,7 @@ public class SAML2IdPEntity {
 
     private final String id;
 
-    private boolean useDeflateEncoding;
-
-    private SAML2BindingType bindingType;
-
-    private ItemTO connObjectKeyItem;
+    private SAML2IdPTO idpTO;
 
     private final Map<String, Endpoint> ssoBindings = new HashMap<>();
 
@@ -67,16 +66,12 @@ public class SAML2IdPEntity {
 
     public SAML2IdPEntity(
             final EntityDescriptor entityDescriptor,
-            final ItemTO connObjectKeyItem,
-            final boolean useDeflateEncoding,
-            final SAML2BindingType bindingType,
+            final SAML2IdPTO idpTO,
             final String keyPass)
             throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
 
         this.id = entityDescriptor.getEntityID();
-        this.connObjectKeyItem = connObjectKeyItem;
-        this.useDeflateEncoding = useDeflateEncoding;
-        this.bindingType = bindingType;
+        this.idpTO = idpTO;
 
         IDPSSODescriptor idpdescriptor = entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
 
@@ -122,32 +117,44 @@ public class SAML2IdPEntity {
         }
     }
 
+    public void setIdpTO(final SAML2IdPTO idpTO) {
+        this.idpTO = idpTO;
+    }
+
     public String getId() {
         return id;
     }
 
-    public boolean isUseDeflateEncoding() {
-        return useDeflateEncoding;
+    public boolean isCreateUnmatching() {
+        return idpTO.isCreateUnmatching();
     }
 
-    public void setUseDeflateEncoding(final boolean useDeflateEncoding) {
-        this.useDeflateEncoding = useDeflateEncoding;
+    public boolean isUpdateMatching() {
+        return idpTO.isUpdateMatching();
     }
 
-    public SAML2BindingType getBindingType() {
-        return bindingType;
+    public boolean isUseDeflateEncoding() {
+        return idpTO.isUseDeflateEncoding();
     }
 
-    public void setBindingType(final SAML2BindingType bindingType) {
-        this.bindingType = bindingType;
+    public SAML2BindingType getBindingType() {
+        return idpTO.getBindingType();
     }
 
     public ItemTO getConnObjectKeyItem() {
-        return connObjectKeyItem;
+        return idpTO.getConnObjectKeyItem();
+    }
+
+    public List<ItemTO> getItems() {
+        return idpTO.getItems();
+    }
+
+    public UserTO getUserTemplate() {
+        return idpTO.getUserTemplate();
     }
 
-    public void setConnObjectKeyItem(final ItemTO connObjectKeyItem) {
-        this.connObjectKeyItem = connObjectKeyItem;
+    public Set<String> getActionsClassNames() {
+        return idpTO.getActionsClassNames();
     }
 
     public Endpoint getSSOLocation(final SAML2BindingType bindingType) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
index 1852a39..3431a86 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
@@ -214,7 +214,6 @@ public class SAML2ReaderWriter {
             final Response samlResponse,
             final SAML2IdPEntity idp,
             final String assertionConsumerURL,
-            final String clientAddress,
             final String requestId,
             final String spEntityID)
             throws WSSecurityException {

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
new file mode 100644
index 0000000..a7af5e2
--- /dev/null
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
@@ -0,0 +1,292 @@
+/*
+ * 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.logic.saml2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class SAML2UserManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SAML2UserManager.class);
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private IntAttrNameParser intAttrNameParser;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private TemplateUtils templateUtils;
+
+    @Autowired
+    private UserProvisioningManager provisioningManager;
+
+    @Autowired
+    private UserDataBinder binder;
+
+    @Transactional(readOnly = true)
+    public List<String> findMatchingUser(final String keyValue, final ItemTO connObjectKeyItem) {
+        List<String> result = new ArrayList<>();
+
+        String transformed = keyValue;
+        for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
+            List<Object> output = transformer.beforePull(
+                    null,
+                    null,
+                    Collections.<Object>singletonList(transformed));
+            if (output != null && !output.isEmpty()) {
+                transformed = output.get(0).toString();
+            }
+        }
+
+        IntAttrName intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), AnyTypeKind.USER);
+
+        if (intAttrName.getField() != null) {
+            switch (intAttrName.getField()) {
+                case "key":
+                    User byKey = userDAO.find(transformed);
+                    if (byKey != null) {
+                        result.add(byKey.getUsername());
+                    }
+                    break;
+
+                case "username":
+                    User byUsername = userDAO.findByUsername(transformed);
+                    if (byUsername != null) {
+                        result.add(byUsername.getUsername());
+                    }
+                    break;
+
+                default:
+            }
+        } else if (intAttrName.getSchemaType() != null) {
+            switch (intAttrName.getSchemaType()) {
+                case PLAIN:
+                    PlainAttrValue value = entityFactory.newEntity(UPlainAttrValue.class);
+
+                    PlainSchema schema = plainSchemaDAO.find(intAttrName.getSchemaName());
+                    if (schema == null) {
+                        value.setStringValue(transformed);
+                    } else {
+                        try {
+                            value.parseValue(schema, transformed);
+                        } catch (ParsingValidationException e) {
+                            LOG.error("While parsing provided key value {}", transformed, e);
+                            value.setStringValue(transformed);
+                        }
+                    }
+
+                    CollectionUtils.collect(
+                            userDAO.findByAttrValue(intAttrName.getSchemaName(), value),
+                            new Transformer<User, String>() {
+
+                        @Override
+                        public String transform(final User input) {
+                            return input.getUsername();
+                        }
+                    }, result);
+                    break;
+
+                case DERIVED:
+                    CollectionUtils.collect(
+                            userDAO.findByDerAttrValue(intAttrName.getSchemaName(), transformed),
+                            new Transformer<User, String>() {
+
+                        @Override
+                        public String transform(final User input) {
+                            return input.getUsername();
+                        }
+                    }, result);
+                    break;
+
+                default:
+            }
+        }
+
+        return result;
+    }
+
+    private List<SAML2IdPActions> getActions(final SAML2IdPEntity idp) {
+        List<SAML2IdPActions> actions = new ArrayList<>();
+
+        for (String className : idp.getActionsClassNames()) {
+            try {
+                Class<?> actionsClass = Class.forName(className);
+                SAML2IdPActions idpActions = (SAML2IdPActions) ApplicationContextProvider.getBeanFactory().
+                        createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+
+                actions.add(idpActions);
+            } catch (Exception e) {
+                LOG.warn("Class '{}' not found", className, e);
+            }
+        }
+
+        return actions;
+    }
+
+    private void fill(final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO, final UserTO userTO) {
+        for (ItemTO item : idp.getItems()) {
+            IntAttrName intAttrName = intAttrNameParser.parse(item.getIntAttrName(), AnyTypeKind.USER);
+
+            List<String> values = Collections.emptyList();
+            AttrTO samlAttr = responseTO.getAttr(item.getExtAttrName());
+            if (samlAttr != null && !samlAttr.getValues().isEmpty()) {
+                values = samlAttr.getValues();
+
+                List<Object> transformed = new ArrayList<Object>(values);
+                for (ItemTransformer transformer : MappingUtils.getItemTransformers(item)) {
+                    transformed = transformer.beforePull(null, userTO, transformed);
+                }
+                values.clear();
+                for (Object value : transformed) {
+                    values.add(value.toString());
+                }
+            }
+
+            if (intAttrName.getField() != null) {
+                switch (intAttrName.getField()) {
+                    case "username":
+                        if (!values.isEmpty()) {
+                            userTO.setUsername(values.get(0));
+                        }
+                        break;
+
+                    default:
+                        LOG.warn("Unsupported: {}", intAttrName.getField());
+                }
+            } else if (intAttrName.getSchemaType() != null) {
+                switch (intAttrName.getSchemaType()) {
+                    case PLAIN:
+                        AttrTO attr = userTO.getPlainAttr(intAttrName.getSchemaName());
+                        if (attr == null) {
+                            attr = new AttrTO.Builder().schema(intAttrName.getSchemaName()).build();
+                            userTO.getPlainAttrs().add(attr);
+                        }
+                        attr.getValues().clear();
+                        attr.getValues().addAll(values);
+                        break;
+
+                    default:
+                        LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchemaName());
+                }
+            }
+        }
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public String create(final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO, final String nameID) {
+        UserTO userTO = new UserTO();
+
+        if (idp.getUserTemplate() != null) {
+            templateUtils.apply(userTO, idp.getUserTemplate());
+        }
+
+        List<SAML2IdPActions> actions = getActions(idp);
+        for (SAML2IdPActions action : actions) {
+            userTO = action.beforeCreate(userTO, responseTO);
+        }
+
+        fill(idp, responseTO, userTO);
+
+        if (userTO.getRealm() == null) {
+            userTO.setRealm(SyncopeConstants.ROOT_REALM);
+        }
+        if (userTO.getUsername() == null) {
+            userTO.setUsername(nameID);
+        }
+
+        Pair<String, List<PropagationStatus>> created = provisioningManager.create(userTO, false, false);
+        userTO = binder.getUserTO(created.getKey());
+
+        for (SAML2IdPActions action : actions) {
+            userTO = action.afterCreate(userTO, responseTO);
+        }
+
+        return userTO.getUsername();
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    public String update(final String username, final SAML2IdPEntity idp, final SAML2LoginResponseTO responseTO) {
+        UserTO userTO = binder.getUserTO(username);
+        UserTO original = SerializationUtils.clone(userTO);
+
+        fill(idp, responseTO, userTO);
+
+        UserPatch userPatch = AnyOperations.diff(userTO, original, true);
+
+        List<SAML2IdPActions> actions = getActions(idp);
+        for (SAML2IdPActions action : actions) {
+            userPatch = action.beforeUpdate(userPatch, responseTO);
+        }
+
+        Pair<String, List<PropagationStatus>> updated = provisioningManager.update(userPatch, false);
+        userTO = binder.getUserTO(updated.getKey());
+
+        for (SAML2IdPActions action : actions) {
+            userTO = action.afterUpdate(userTO, responseTO);
+        }
+
+        return userTO.getUsername();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
index 7d1a5da..5588334 100644
--- a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
+++ b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2IdP.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.entity;
 
 import java.util.List;
+import java.util.Set;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 
 public interface SAML2IdP extends Entity {
@@ -35,6 +36,14 @@ public interface SAML2IdP extends Entity {
 
     void setMetadata(byte[] metadata);
 
+    boolean isCreateUnmatching();
+
+    void setCreateUnmatching(boolean createUnmatching);
+
+    boolean isUpdateMatching();
+
+    void setUpdateMatching(boolean updateMatching);
+
     boolean isUseDeflateEncoding();
 
     void setUseDeflateEncoding(boolean useDeflateEncoding);
@@ -43,6 +52,10 @@ public interface SAML2IdP extends Entity {
 
     void setBindingType(SAML2BindingType bindingType);
 
+    SAML2UserTemplate getUserTemplate();
+
+    void setUserTemplate(SAML2UserTemplate userTemplate);
+
     SAML2IdPItem getConnObjectKeyItem();
 
     void setConnObjectKeyItem(SAML2IdPItem item);
@@ -51,4 +64,6 @@ public interface SAML2IdP extends Entity {
 
     List<? extends SAML2IdPItem> getItems();
 
+    Set<String> getActionsClassNames();
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
new file mode 100644
index 0000000..a26227b
--- /dev/null
+++ b/ext/saml2sp/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/SAML2UserTemplate.java
@@ -0,0 +1,26 @@
+/*
+ * 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.api.entity;
+
+public interface SAML2UserTemplate extends AnyTemplate {
+
+    SAML2IdP getIdP();
+
+    void setIdP(SAML2IdP idp);
+}


[3/4] syncope git commit: [SYNCOPE-1146][SYNCOPE-1147] Now it possible to configure an IdP to create unmatching users / update matching users; the defined mapping, alongside with user template and SAML2IdPActions implementations are taken into account to

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
index 2277c14..888e870 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2EntityFactory.java
@@ -22,6 +22,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.SAML2EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -36,6 +37,8 @@ public class JPASAML2EntityFactory implements SAML2EntityFactory {
             result = (E) new JPASAML2IdP();
         } else if (reference.equals(SAML2IdPItem.class)) {
             result = (E) new JPASAML2IdPItem();
+        } else if (reference.equals(SAML2UserTemplate.class)) {
+            result = (E) new JPASAML2UserTemplate();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
index cf1e3a8..d6df04d 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdP.java
@@ -19,15 +19,21 @@
 package org.apache.syncope.core.persistence.jpa.entity;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import javax.persistence.Basic;
 import javax.persistence.Cacheable;
 import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
 import javax.persistence.Column;
+import javax.persistence.ElementCollection;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
 import javax.persistence.Lob;
 import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
@@ -37,6 +43,7 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.apache.syncope.core.persistence.jpa.validation.entity.SAML2IdPCheck;
 
 @Entity
@@ -65,11 +72,31 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     @Min(0)
     @Max(1)
     @Column(nullable = false)
+    private Integer createUnmatching;
+
+    @Min(0)
+    @Max(1)
+    @Column(nullable = false)
+    private Integer updateMatching;
+
+    @Min(0)
+    @Max(1)
+    @Column(nullable = false)
     private Integer useDeflateEncoding;
 
     @Column(nullable = false)
     private SAML2BindingType bindingType;
 
+    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "idp")
+    private JPASAML2UserTemplate userTemplate;
+
+    @ElementCollection(fetch = FetchType.EAGER)
+    @Column(name = "actionClassName")
+    @CollectionTable(name = TABLE + "_actionsClassNames",
+            joinColumns =
+            @JoinColumn(name = "saml2IdP_id", referencedColumnName = "id"))
+    private Set<String> actionsClassNames = new HashSet<>();
+
     @Override
     public String getEntityID() {
         return entityID;
@@ -101,6 +128,26 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     }
 
     @Override
+    public boolean isCreateUnmatching() {
+        return isBooleanAsInteger(createUnmatching);
+    }
+
+    @Override
+    public void setCreateUnmatching(final boolean createUnmatching) {
+        this.createUnmatching = getBooleanAsInteger(createUnmatching);
+    }
+
+    @Override
+    public boolean isUpdateMatching() {
+        return isBooleanAsInteger(updateMatching);
+    }
+
+    @Override
+    public void setUpdateMatching(final boolean updateMatching) {
+        this.updateMatching = getBooleanAsInteger(updateMatching);
+    }
+
+    @Override
     public boolean isUseDeflateEncoding() {
         return isBooleanAsInteger(useDeflateEncoding);
     }
@@ -121,6 +168,17 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
     }
 
     @Override
+    public SAML2UserTemplate getUserTemplate() {
+        return userTemplate;
+    }
+
+    @Override
+    public void setUserTemplate(final SAML2UserTemplate userTemplate) {
+        checkType(userTemplate, JPASAML2UserTemplate.class);
+        this.userTemplate = (JPASAML2UserTemplate) userTemplate;
+    }
+
+    @Override
     public boolean add(final SAML2IdPItem item) {
         checkType(item, JPASAML2IdPItem.class);
         return items.contains((JPASAML2IdPItem) item) || items.add((JPASAML2IdPItem) item);
@@ -148,4 +206,8 @@ public class JPASAML2IdP extends AbstractGeneratedKeyEntity implements SAML2IdP
         this.add(item);
     }
 
+    @Override
+    public Set<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
new file mode 100644
index 0000000..538fa94
--- /dev/null
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2UserTemplate.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
+import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractAnyTemplate;
+
+@Entity
+@Table(name = JPASAML2UserTemplate.TABLE, uniqueConstraints =
+        @UniqueConstraint(columnNames = { "idp_id" }))
+public class JPASAML2UserTemplate extends AbstractAnyTemplate implements SAML2UserTemplate {
+
+    private static final long serialVersionUID = -4575039890434426856L;
+
+    public static final String TABLE = "SAML2UserTemplate";
+
+    @ManyToOne
+    private JPASAML2IdP idp;
+
+    @Override
+    public SAML2IdP getIdP() {
+        return idp;
+    }
+
+    @Override
+    public void setIdP(final SAML2IdP idp) {
+        checkType(idp, JPASAML2IdP.class);
+        this.idp = (JPASAML2IdP) idp;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
new file mode 100644
index 0000000..6f23804
--- /dev/null
+++ b/ext/saml2sp/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/SAML2IdPActions.java
@@ -0,0 +1,34 @@
+/*
+ * 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.provisioning.api;
+
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+
+public interface SAML2IdPActions {
+
+    UserTO beforeCreate(UserTO input, SAML2LoginResponseTO loginResponse);
+
+    UserTO afterCreate(UserTO input, SAML2LoginResponseTO loginResponse);
+
+    UserPatch beforeUpdate(UserPatch input, SAML2LoginResponseTO loginResponse);
+
+    UserTO afterUpdate(UserTO input, SAML2LoginResponseTO loginResponse);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
new file mode 100644
index 0000000..ba5fc40
--- /dev/null
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultSAML2IdPActions.java
@@ -0,0 +1,48 @@
+/*
+ * 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.provisioning.java;
+
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
+
+public class DefaultSAML2IdPActions implements SAML2IdPActions {
+
+    @Override
+    public UserTO beforeCreate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserTO afterCreate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserPatch beforeUpdate(final UserPatch input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+    @Override
+    public UserTO afterUpdate(final UserTO input, final SAML2LoginResponseTO loginResponse) {
+        return input;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
index b2668c5..2f2f9d0 100644
--- a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -36,6 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.SAML2EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
+import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder;
@@ -168,9 +170,24 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         idp.setEntityID(idpTO.getEntityID());
         idp.setName(idpTO.getName());
         idp.setMetadata(Base64.decode(idpTO.getMetadata()));
+        idp.setCreateUnmatching(idpTO.isCreateUnmatching());
+        idp.setUpdateMatching(idpTO.isUpdateMatching());
         idp.setUseDeflateEncoding(idpTO.isUseDeflateEncoding());
         idp.setBindingType(idpTO.getBindingType());
 
+        if (idpTO.getUserTemplate() == null) {
+            idp.setUserTemplate(null);
+        } else {
+            SAML2UserTemplate userTemplate = idp.getUserTemplate();
+            if (userTemplate == null) {
+                userTemplate = entityFactory.newEntity(SAML2UserTemplate.class);
+                userTemplate.setAnyType(anyTypeDAO.findUser());
+                userTemplate.setIdP(idp);
+                idp.setUserTemplate(userTemplate);
+            }
+            userTemplate.set(idpTO.getUserTemplate());
+        }
+
         idp.getItems().clear();
         AnyTypeClassTO allowedSchemas = new AnyTypeClassTO();
         for (AnyTypeClass anyTypeClass : anyTypeDAO.findUser().getClasses()) {
@@ -186,6 +203,9 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         }
         populateItems(idpTO, idp, allowedSchemas);
 
+        idp.getActionsClassNames().clear();
+        idp.getActionsClassNames().addAll(idpTO.getActionsClassNames());
+
         return saml2IdPDAO.save(idp);
     }
 
@@ -213,10 +233,18 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         idpTO.setName(idp.getName());
         idpTO.setUseDeflateEncoding(idp.isUseDeflateEncoding());
         idpTO.setBindingType(idp.getBindingType());
+        idpTO.setCreateUnmatching(idp.isCreateUnmatching());
+        idpTO.setUpdateMatching(idp.isUpdateMatching());
         idpTO.setMetadata(Base64.encode(idp.getMetadata()));
 
+        if (idp.getUserTemplate() != null) {
+            idpTO.setUserTemplate((UserTO) idp.getUserTemplate().get());
+        }
+
         populateItems(idp, idpTO);
 
+        idpTO.getActionsClassNames().addAll(idp.getActionsClassNames());
+
         return idpTO;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java b/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
index 5761c01..0d528c3 100644
--- a/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
+++ b/ext/saml2sp/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPService.java
@@ -20,6 +20,7 @@ package org.apache.syncope.common.rest.api.service;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -40,6 +41,16 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 public interface SAML2IdPService extends JAXRSService {
 
     /**
+     * Returns the list of available SAML2IdPActions implementations.
+     *
+     * @return the list of available SAML2IdPActions implementations
+     */
+    @GET
+    @Path("actionsClasses")
+    @Produces({ MediaType.APPLICATION_JSON })
+    Set<String> getActionsClasses();
+
+    /**
      * Returns a list of all defined SAML 2.0 Identity Providers.
      *
      * @return list of all defined SAML 2.0 Identity Providers

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java b/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
index 1f2790f..ebda30d 100644
--- a/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
+++ b/ext/saml2sp/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPServiceImpl.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.rest.cxf.service;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -35,6 +36,11 @@ public class SAML2IdPServiceImpl extends AbstractServiceImpl implements SAML2IdP
     private SAML2IdPLogic logic;
 
     @Override
+    public Set<String> getActionsClasses() {
+        return logic.getActionsClasses();
+    }
+
+    @Override
     public List<SAML2IdPTO> list() {
         return logic.list();
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f1c004bf/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
index f0cd386..b87db1b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2ITCase.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -48,6 +49,7 @@ import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.lib.to.SAML2LoginResponseTO;
 import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO;
 import org.apache.syncope.common.lib.to.SAML2RequestTO;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.SAML2SPService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.SAML2SPDetector;
@@ -171,13 +173,20 @@ public class SAML2ITCase extends AbstractITCase {
 
         SAML2IdPTO ssoCircle = ssoCircleOpt.get();
         assertNotNull(ssoCircle);
+        assertFalse(ssoCircle.isCreateUnmatching());
+        assertNull(ssoCircle.getUserTemplate());
         assertFalse(ssoCircle.getItems().isEmpty());
         assertNotNull(ssoCircle.getConnObjectKeyItem());
         assertNotEquals("email", ssoCircle.getConnObjectKeyItem().getIntAttrName());
         assertNotEquals("EmailAddress", ssoCircle.getConnObjectKeyItem().getExtAttrName());
 
-        ssoCircle.getItems().clear();
+        ssoCircle.setCreateUnmatching(true);
+
+        UserTO userTemplate = new UserTO();
+        userTemplate.setRealm("'/'");
+        ssoCircle.setUserTemplate(userTemplate);
 
+        ssoCircle.getItems().clear();
         ItemTO keyMapping = new ItemTO();
         keyMapping.setIntAttrName("email");
         keyMapping.setExtAttrName("EmailAddress");
@@ -186,6 +195,8 @@ public class SAML2ITCase extends AbstractITCase {
         saml2IdPService.update(ssoCircle);
 
         ssoCircle = saml2IdPService.read(ssoCircle.getKey());
+        assertTrue(ssoCircle.isCreateUnmatching());
+        assertEquals(userTemplate, ssoCircle.getUserTemplate());
         assertEquals("email", ssoCircle.getConnObjectKeyItem().getIntAttrName());
         assertEquals("EmailAddress", ssoCircle.getConnObjectKeyItem().getExtAttrName());
     }