Repository: syncope
Updated Branches:
  refs/heads/2_0_X ade7e99a5 -> daa1d0d2f


[SYNCOPE-1009] USER form customization through json file, made minor (but 
necesary) bugfixes on wizard


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

Branch: refs/heads/2_0_X
Commit: daa1d0d2fe7f03cc98068ddc23afe2a3a0075a58
Parents: ade7e99
Author: Andrea Patricelli <andreapatrice...@apache.org>
Authored: Thu Apr 6 08:55:44 2017 +0200
Committer: Andrea Patricelli <andreapatrice...@apache.org>
Committed: Thu Apr 6 08:55:44 2017 +0200

----------------------------------------------------------------------
 .../enduser/model/CustomAttributesInfo.java     | 62 ++++++++++++++
 .../enduser/resources/SchemaResource.java       | 90 ++++++++++++++++++--
 .../resources/UserSelfCreateResource.java       |  2 +-
 .../resources/UserSelfUpdateResource.java       |  6 +-
 .../resources/app/configuration/customForm.json |  1 +
 .../resources/META-INF/resources/app/index.html |  1 +
 .../resources/META-INF/resources/app/js/app.js  | 11 ++-
 .../app/js/controllers/UserController.js        | 58 ++++++++++---
 .../app/js/directives/dynamicPlainAttribute.js  | 20 +++--
 .../js/directives/dynamicVirtualAttribute.js    | 35 ++++++--
 .../app/js/services/configurationService.js     | 41 +++++++++
 .../app/js/services/saml2IdPService.js          |  2 +-
 .../resources/app/js/services/schemaService.js  | 38 +++++----
 .../META-INF/resources/app/views/captcha.html   |  4 +-
 .../app/views/dynamicPlainAttribute.html        | 87 ++++++++++---------
 .../app/views/dynamicVirtualAttribute.html      |  2 +-
 16 files changed, 361 insertions(+), 99 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
new file mode 100644
index 0000000..61d08f1
--- /dev/null
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.enduser.model;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class CustomAttributesInfo implements Serializable {
+
+    private static final long serialVersionUID = 878444785696091916L;
+
+    private Boolean show = Boolean.TRUE;
+
+    private Map<String, ?> attributes = new LinkedHashMap<>();
+
+    public CustomAttributesInfo() {
+    }
+
+    public Boolean getShow() {
+        return show;
+    }
+
+    public void setShow(final Boolean show) {
+        this.show = show;
+    }
+
+    public Map<String, ?> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(final Map<String, ?> attributes) {
+        this.attributes = attributes;
+    }
+
+    public CustomAttributesInfo show(final Boolean value) {
+        this.show = value;
+        return this;
+    }
+
+    public CustomAttributesInfo attributes(final Map<String, ?> value) {
+        this.attributes = value;
+        return this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
index a68704d..169d1b4 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
@@ -18,17 +18,27 @@
  */
 package org.apache.syncope.client.enduser.resources;
 
+import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
+
+import com.fasterxml.jackson.core.type.TypeReference;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
 import org.apache.syncope.client.enduser.model.SchemaResponse;
 import org.apache.syncope.common.lib.to.AbstractSchemaTO;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
@@ -49,6 +59,7 @@ public class SchemaResource extends BaseResource {
 
     private static final long serialVersionUID = 6453101466981543020L;
 
+    @SuppressWarnings("unchecked")
     @Override
     protected AbstractResource.ResourceResponse newResourceResponse(final 
IResource.Attributes attributes) {
         LOG.debug("Search all {} any type kind related schemas", 
AnyTypeKind.USER.name());
@@ -92,29 +103,51 @@ public class SchemaResource extends BaseResource {
                 }
             }
 
+            Map<String, CustomAttributesInfo> customForm = 
MAPPER.readValue(request.getReader().readLine(),
+                    new TypeReference<HashMap<String, CustomAttributesInfo>>() 
{
+            });
+
             SchemaService schemaService = 
SyncopeEnduserSession.get().getService(SchemaService.class);
             final List<AbstractSchemaTO> plainSchemas = classes.isEmpty()
                     ? Collections.<AbstractSchemaTO>emptyList()
-                    : schemaService.list(
-                            new 
SchemaQuery.Builder().type(SchemaType.PLAIN).anyTypeClasses(classes).build());
+                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.PLAIN.name()) == null
+                    ? schemaService.list(
+                            new 
SchemaQuery.Builder().type(SchemaType.PLAIN).anyTypeClasses(classes).build())
+                    : customForm.get(SchemaType.PLAIN.name()).getShow()
+                    ? customizeSchemas(schemaService.list(new 
SchemaQuery.Builder().type(SchemaType.PLAIN).
+                            anyTypeClasses(classes).build()), groupParam, 
customForm.get(SchemaType.PLAIN.name()).
+                            getAttributes())
+                    : Collections.<AbstractSchemaTO>emptyList();
             final List<AbstractSchemaTO> derSchemas = classes.isEmpty()
                     ? Collections.<AbstractSchemaTO>emptyList()
-                    : schemaService.list(
-                            new 
SchemaQuery.Builder().type(SchemaType.DERIVED).anyTypeClasses(classes).build());
+                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.DERIVED.name()) == null
+                    ? schemaService.list(
+                            new 
SchemaQuery.Builder().type(SchemaType.DERIVED).anyTypeClasses(classes).build())
+                    : customForm.get(SchemaType.DERIVED.name()).getShow()
+                    ? customizeSchemas(schemaService.list(new 
SchemaQuery.Builder().type(SchemaType.DERIVED).
+                            anyTypeClasses(classes).build()), groupParam, 
customForm.get(SchemaType.DERIVED.name()).
+                            getAttributes())
+                    : Collections.<AbstractSchemaTO>emptyList();
             final List<AbstractSchemaTO> virSchemas = classes.isEmpty()
                     ? Collections.<AbstractSchemaTO>emptyList()
-                    : schemaService.list(
-                            new 
SchemaQuery.Builder().type(SchemaType.VIRTUAL).anyTypeClasses(classes).build());
+                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.VIRTUAL.name()) == null
+                    ? schemaService.list(
+                            new 
SchemaQuery.Builder().type(SchemaType.VIRTUAL).anyTypeClasses(classes).build())
+                    : customForm.get(SchemaType.VIRTUAL.name()).getShow()
+                    ? customizeSchemas(schemaService.list(new 
SchemaQuery.Builder().type(SchemaType.VIRTUAL).
+                            anyTypeClasses(classes).build()), groupParam, 
customForm.get(SchemaType.VIRTUAL.name()).
+                            getAttributes())
+                    : Collections.<AbstractSchemaTO>emptyList();
 
             if (groupParam != null) {
                 for (AbstractSchemaTO schema : plainSchemas) {
-                    schema.setKey(groupParam + "#" + schema.getKey());
+                    schema.setKey(compositeSchemaKey(groupParam, 
schema.getKey()));
                 }
                 for (AbstractSchemaTO schema : derSchemas) {
-                    schema.setKey(groupParam + "#" + schema.getKey());
+                    schema.setKey(compositeSchemaKey(groupParam, 
schema.getKey()));
                 }
                 for (AbstractSchemaTO schema : virSchemas) {
-                    schema.setKey(groupParam + "#" + schema.getKey());
+                    schema.setKey(compositeSchemaKey(groupParam, 
schema.getKey()));
                 }
             }
 
@@ -142,4 +175,43 @@ public class SchemaResource extends BaseResource {
         return response;
     }
 
+    private List<AbstractSchemaTO> customizeSchemas(final 
List<AbstractSchemaTO> schemaTOs, final String groupParam,
+            final Map<String, ?> customForm) {
+
+        if (customForm.isEmpty()) {
+            return schemaTOs;
+        }
+        final boolean isGroupBlank = StringUtils.isBlank(groupParam);
+
+        CollectionUtils.filter(schemaTOs, new Predicate<AbstractSchemaTO>() {
+
+            @Override
+            public boolean evaluate(final AbstractSchemaTO object) {
+                return customForm.containsKey(isGroupBlank
+                        ? object.getKey()
+                        : compositeSchemaKey(groupParam, object.getKey()));
+            }
+        });
+
+        Collections.sort(schemaTOs, new Comparator<AbstractSchemaTO>() {
+
+            @Override
+            public int compare(final AbstractSchemaTO schemaTO1, final 
AbstractSchemaTO schemaTO2) {
+                List<String> order = new ArrayList<>(customForm.keySet());
+                return order.indexOf(isGroupBlank
+                        ? schemaTO1.getKey()
+                        : compositeSchemaKey(groupParam, schemaTO1.getKey()))
+                        - order.indexOf(isGroupBlank
+                                ? schemaTO2.getKey()
+                                : compositeSchemaKey(groupParam, 
schemaTO2.getKey()));
+            }
+        });
+
+        return schemaTOs;
+    }
+
+    private String compositeSchemaKey(final String prefix, final String 
schemaKey) {
+        return prefix + "#" + schemaKey;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
index 2634640..c8baa53 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
@@ -65,7 +65,7 @@ public class UserSelfCreateResource extends BaseResource {
     @Override
     protected ResourceResponse newResourceResponse(final Attributes 
attributes) {
         ResourceResponse response = new ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
+        response.setContentType(MediaType.TEXT_PLAIN);
         try {
             HttpServletRequest request = (HttpServletRequest) 
attributes.getRequest().getContainerRequest();
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
index 4a7e78c..2ee22db 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
@@ -35,6 +35,7 @@ import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
@@ -164,9 +165,10 @@ public class UserSelfUpdateResource extends BaseResource {
             }
             userTO.getVirAttrs().removeAll(membAttrs);
 
-            // update user
+            // update user by patch
             Response res = SyncopeEnduserSession.get().
-                    getService(userTO.getETagValue(), 
UserSelfService.class).update(userTO);
+                    getService(userTO.getETagValue(), 
UserSelfService.class).update(AnyOperations.diff(userTO,
+                    SyncopeEnduserSession.get().getSelfTO(), true));
 
             final String responseMessage = 
res.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL)
                     ? new StringBuilder().

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json
 
b/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/index.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/index.html 
b/client/enduser/src/main/resources/META-INF/resources/app/index.html
index 21a7984..7378236 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/index.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -99,6 +99,7 @@ under the License.
   <script src="js/services/groupService.js"></script>
   <script src="js/services/anyService.js"></script>
   <script src="js/services/saml2IdPService.js"></script>
+  <script src="js/services/configurationService.js"></script>
   <!--controllers-->
   <script src="js/controllers/HomeController.js"></script>
   <script src="js/controllers/LoginController.js"></script>

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js 
b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
index d8ceeb4..64d535f 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -332,7 +332,8 @@ app.run(['$rootScope', '$location', '$state', 'AuthService',
     };
   }]);
 app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 
'InfoService', 'SAML2IdPService',
-  function ($scope, $rootScope, $location, InfoService, SAML2IdPService) {
+  'ConfigurationService',
+  function ($scope, $rootScope, $location, InfoService, SAML2IdPService, 
ConfigurationService) {
     $scope.initApplication = function () {
       /* 
        * disable by default wizard buttons in self-registration
@@ -405,6 +406,14 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
         return $rootScope.version;
       };
       /* 
+       * USER Attributes form customization
+       */
+      ConfigurationService.get("customForm.json").then(function (response) {
+        $rootScope.customForm = response;
+      }, function (e) {
+        console.warn("Unable to retrieve form customization file provided, 
applying default configuration.");
+      });
+      /* 
        * USER Attributes sorting strategies
        */
       $rootScope.attributesSorting = {

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
index 18ae153..ed9bc8f 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -21,10 +21,10 @@
 
 'use strict';
 
-angular.module("self").controller("UserController", ['$scope', '$rootScope', 
'$location', '$compile', "$state",
-  'AuthService', 'UserSelfService', 'SchemaService', 'RealmService', 
'ResourceService', 'SecurityQuestionService',
+angular.module("self").controller("UserController", ['$scope', '$rootScope', 
'$location', "$state",
+  'UserSelfService', 'SchemaService', 'RealmService', 'ResourceService', 
'SecurityQuestionService',
   'GroupService', 'AnyService', 'UserUtil', 'GenericUtil', 
'ValidationExecutor', '$translate',
-  function ($scope, $rootScope, $location, $compile, $state, AuthService, 
UserSelfService, SchemaService, RealmService,
+  function ($scope, $rootScope, $location, $state, UserSelfService, 
SchemaService, RealmService,
           ResourceService, SecurityQuestionService, GroupService, AnyService, 
UserUtil, GenericUtil, ValidationExecutor, $translate) {
 
     $scope.user = {};
@@ -41,6 +41,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
     $scope.captchaInput = {
       value: ""
     };
+    $scope.customForm = {};
 
     $scope.initUser = function () {
       $scope.dynamicForm = {
@@ -64,14 +65,24 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
         // initialization is done here synchronously to have all schema fields 
populated correctly
         var schemaService;
         if (group) {
-          schemaService = SchemaService.getTypeExtSchemas(group, 
$rootScope.attributesSorting.ASC);
+          /* 
+           * if you want to sort with custom JS function defined in put also a 
sorting function as last parameter
+           * e.g. $rootScope.attributesSorting.ASC
+           */
+          schemaService = SchemaService.getTypeExtSchemas(group, 
$rootScope.customForm);
         } else {
-          schemaService = SchemaService.getUserSchemas(anyTypeClass, 
$rootScope.attributesSorting.ASC);
+          /* 
+           * if you want to sort with custom JS function defined in put also a 
sorting function as last parameter
+           * e.g. $rootScope.attributesSorting.ASC
+           */
+          schemaService = SchemaService.getUserSchemas(anyTypeClass, 
$rootScope.customForm);
         }
         schemaService.then(function (schemas) {
           if (group && (schemas.plainSchemas.length > 0 || 
schemas.derSchemas.length > 0 || schemas.virSchemas.length > 0))
             $scope.dynamicForm.groupSchemas.push(group);
-          //initializing user schemas values
+          /* 
+           * initializing user schemas values, i.e. USER attributes
+           */
           initSchemaValues(schemas);
         }, function (response) {
           // parse error response and log
@@ -90,19 +101,37 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
         // initialize plain attributes
         for (var i = 0; i < schemas.plainSchemas.length; i++) {
           var plainSchemaKey = schemas.plainSchemas[i].key;
+          var initialAttributeValues = $rootScope.customForm != null
+                  && $rootScope.customForm["PLAIN"] != null
+                  && $rootScope.customForm["PLAIN"]["attributes"] != null
+                  && 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey] != null
+                  && 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey].defaultValues
+                  ? 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey].defaultValues
+                  : [];
           if (!$scope.user.plainAttrs[plainSchemaKey]) {
             $scope.user.plainAttrs[plainSchemaKey] = {
               schema: plainSchemaKey,
-              values: []
+              values: initialAttributeValues
             };
-            // initialize multivalue schema and support table: create mode, 
only first value
             if (schemas.plainSchemas[i].multivalue) {
-              $scope.dynamicForm.attributeTable[schemas.plainSchemas[i].key] = 
{
-                fields: [schemas.plainSchemas[i].key + "_" + 0]
-              };
+              // initialize multivalue schema and support table: create mode, 
default multivalues
+              if (initialAttributeValues.length > 0) {
+                // attribute create mode, init empty fields  
+                $scope.dynamicForm.attributeTable[plainSchemaKey] = {
+                  fields: []
+                };
+                for (var j = 0; j < initialAttributeValues.length; j++) {
+                  
$scope.dynamicForm.attributeTable[plainSchemaKey].fields.push(plainSchemaKey + 
"_" + j);
+                }
+              } else {
+                // initialize multivalue schema and support table: create 
mode, only first value
+                $scope.dynamicForm.attributeTable[schemas.plainSchemas[i].key] 
= {
+                  fields: [schemas.plainSchemas[i].key + "_" + 0]
+                };
+              }
             }
           } else if (schemas.plainSchemas[i].multivalue) {
-            // initialize multivalue schema and support table: update mode, 
all provided values
+            // initialize multivalue attribute and support table: update mode, 
all provided values
             $scope.dynamicForm.attributeTable[schemas.plainSchemas[i].key] = {
               fields: [schemas.plainSchemas[i].key + "_" + 0]
             };
@@ -133,12 +162,12 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
               schema: virSchemaKey,
               values: []
             };
-            // initialize multivalue schema and support table: create mode, 
only first value
+            // initialize multivalue attribute and support table: create mode, 
only first value
             
$scope.dynamicForm.virtualAttributeTable[schemas.virSchemas[i].key] = {
               fields: [schemas.virSchemas[i].key + "_" + 0]
             };
           } else {
-            // initialize multivalue schema and support table: update mode, 
all provided values
+            // initialize multivalue attribute and support table: update mode, 
all provided values
             
$scope.dynamicForm.virtualAttributeTable[schemas.virSchemas[i].key] = {
               fields: [schemas.virSchemas[i].key + "_" + 0]
             };
@@ -271,6 +300,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
           if ($scope.user.mustChangePassword) {
             $location.path('/mustchangepassword');
           } else {
+//            initConfiguration();
             initProperties();
           }
         }, function (e) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
index c267087..f67b926 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
@@ -19,7 +19,7 @@
 
 'use strict';
 angular.module('self')
-        .directive('dynamicPlainAttribute', function ($filter) {
+        .directive('dynamicPlainAttribute', function () {
           return {
             restrict: 'E',
             templateUrl: 'views/dynamicPlainAttribute.html',
@@ -50,10 +50,10 @@ angular.module('self')
                       
$scope.enumerationValues.push(enumerationValuesSplitted[i]);
                     }
                     //SYNCOPE-1024 enumeration keys mgmt
-                    if ( schema.enumerationKeys ) {
-                      var enumerationKeysSplitted = 
schema.enumerationKeys.toString().split( ";" );
-                      for ( var i = 0; i < enumerationKeysSplitted.length; i++ 
) {
-                        $scope.enumerationKeys.push( 
enumerationKeysSplitted[i] );
+                    if (schema.enumerationKeys) {
+                      var enumerationKeysSplitted = 
schema.enumerationKeys.toString().split(";");
+                      for (var i = 0; i < enumerationKeysSplitted.length; i++) 
{
+                        
$scope.enumerationKeys.push(enumerationKeysSplitted[i]);
                       }
                     }
                     $scope.user.plainAttrs[schema.key].values[index] = 
$scope.user.plainAttrs[schema.key].values[index]
@@ -119,12 +119,20 @@ angular.module('self')
 
                   case "Boolean":
                     $scope.user.plainAttrs[schema.key].values[index] =
-                            $scope.user.plainAttrs[schema.key].values[index] 
=== "true" ? true : false;
+                            $scope.user.plainAttrs[schema.key].values[index] 
=== "true" ? "true" : "false";
                     break;
 
                 }
               };
 
+              $scope.customReadonly = function (schemaKey) {
+                return  $rootScope.customForm != null
+                        && $rootScope.customForm["PLAIN"] != null
+                        && $rootScope.customForm["PLAIN"]["attributes"] != null
+                        && 
$rootScope.customForm["PLAIN"]["attributes"][schemaKey] != null
+                        && 
$rootScope.customForm["PLAIN"]["attributes"][schemaKey].readonly;
+              };
+
               $scope.$watch(function () {
                 return 
$scope.user.plainAttrs[$scope.schema.key].values[$scope.index];
               }, function (newValue, oldValue) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
index 6fc926b..85e2934 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
@@ -28,16 +28,41 @@ angular.module('self')
               index: "=",
               user: "="
             },
-            controller: function ($scope) {
+            controller: function ($scope, $rootScope) {
+              var customValues = $rootScope.customForm != null
+                      && $rootScope.customForm["VIRTUAL"] != null
+                      && $rootScope.customForm["VIRTUAL"]["attributes"] != null
+                      && 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key] != null
+                      && 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
+                      ? 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
+                      : [];
+
               $scope.$watch(function () {
                 return 
$scope.user.virAttrs[$scope.schema.key].values[$scope.index];
               }, function (newValue, oldValue) {
-                $scope.user.virAttrs[$scope.schema.key].values = 
$scope.user.virAttrs[$scope.schema.key].values
-                        .filter(function (n) {
-                          return (n !== undefined && n !== "");
-                        });
+                if ($scope.user.virAttrs[$scope.schema.key].values
+                        && 
$scope.user.virAttrs[$scope.schema.key].values.length > 0) {
+                  $scope.user.virAttrs[$scope.schema.key].values = 
$scope.user.virAttrs[$scope.schema.key].values
+                          .filter(function (n) {
+                            return (n !== undefined && n !== "");
+                          });
+                } else {
+                  $scope.user.virAttrs[$scope.schema.key].values = customValues
+                          .filter(function (n) {
+                            return (n !== undefined && n !== "");
+                          });
+                }
               });
+
+              $scope.customReadonly = function (schemaKey) {
+                return  $rootScope.customForm != null
+                        && $rootScope.customForm["VIRTUAL"] != null
+                        && $rootScope.customForm["VIRTUAL"]["attributes"] != 
null
+                        && 
$rootScope.customForm["VIRTUAL"]["attributes"][schemaKey] != null
+                        && 
$rootScope.customForm["VIRTUAL"]["attributes"][schemaKey].readonly;
+              };
             }
             //replace: true
           };
+
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js
new file mode 100644
index 0000000..25ee9f2
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js
@@ -0,0 +1,41 @@
+/* 
+ * 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.
+ */
+
+'use strict';
+
+angular.module('self')
+        .factory('ConfigurationService', ['$q', '$http',
+          function ($q, $http) {
+
+            var configuration = {};
+
+            configuration.get = function (filename) {
+              return  $http.get("/syncope-enduser/app/configuration/" + 
filename, {cache: false})
+                      .then(function (response) {
+                        return response.data;
+                      }, function (response) {
+                        console.error("Unable to retrieve " + filename);
+                        return $q.reject(response.data);
+                      });
+            };
+
+            return configuration;
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/services/saml2IdPService.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/saml2IdPService.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/saml2IdPService.js
index e8faddd..7412da7 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/saml2IdPService.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/saml2IdPService.js
@@ -30,7 +30,7 @@ angular.module('self')
                       .then(function (response) {
                         return response.data;
                       }, function (response) {
-                        console.error("Something went wrong during realms 
retrieval, exit with status: ", response);
+                        console.error("Something went wrong during saml2Idp 
extesion retrieval, exit with status: ", response);
                         return $q.reject(response.data || response.statusText);
                       });
             };

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
index 90fdee9..1918bb3 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
@@ -19,32 +19,36 @@
 
 'use strict';
 
-angular.module( 'self' )
-        .factory( 'SchemaService', [ '$q', '$http',
-          function ( $q, $http ) {
+angular.module('self')
+        .factory('SchemaService', ['$q', '$http',
+          function ($q, $http) {
 
-            var schemaService = { };
+            var schemaService = {};
 
-            schemaService.getUserSchemas = function ( anyTypeClass, 
sortingFunction ) {
-              var classParam = anyTypeClass ? "?anyTypeClass=" + encodeURI( 
anyTypeClass ) : "";
+            schemaService.getUserSchemas = function (anyTypeClass, customForm, 
sortingFunction) {
+              var classParam = anyTypeClass ? "?anyTypeClass=" + 
encodeURI(anyTypeClass) : "";
+              var body = customForm ? customForm : {};
 
-              return  $http.get( "/syncope-enduser/api/schemas" + classParam )
-                      .then( function ( response ) {
+              return  $http.post("/syncope-enduser/api/schemas" + classParam, 
body)
+                      .then(function (response) {
                         var schemas = response.data;
-                        schemas.plainSchemas.sort( sortingFunction );
-                        schemas.derSchemas.sort( sortingFunction );
-                        schemas.virSchemas.sort( sortingFunction );
+                        if (sortingFunction) {
+                          schemas.plainSchemas.sort(sortingFunction);
+                          schemas.derSchemas.sort(sortingFunction);
+                          schemas.virSchemas.sort(sortingFunction);
+                        }
                         return schemas;
-                      }, function ( response ) {
-                        console.error( "Something went wrong during schema 
retrieval, exit with status: ", response );
-                        return $q.reject( response.data || response.statusText 
);
-                      } );
+                      }, function (response) {
+                        console.error("Something went wrong during schema 
retrieval, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
             };
 
-            schemaService.getTypeExtSchemas = function (group) {
+            schemaService.getTypeExtSchemas = function (group, customForm) {
               var param = group ? "?group=" + encodeURI(group) : "";
+              var body = customForm ? customForm : {};
 
-              return  $http.get("/syncope-enduser/api/schemas" + param)
+              return  $http.post("/syncope-enduser/api/schemas" + param, body)
                       .then(function (response) {
                         return response.data;
                       }, function (response) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html 
b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
index b0ea3ff..518ab35 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
@@ -24,8 +24,8 @@ under the License.
         <div id="captchaButtons" style="margin-top: 5%; margin-bottom: 10px">
           <button id="refresh" type="button" class="btn btn-default btn-xs 
glyphicon glyphicon-refresh" 
                   ng-click="refreshCaptcha()" title="Refresh Captcha"></button>
-          <a id="refresh" class="btn btn-default btn-xs glyphicon 
glyphicon-question-sign" title="What is?" 
-             href="https://it.wikipedia.org/wiki/CAPTCHA"/>
+          <a id="refresh" class="btn btn-default btn-xs glyphicon 
glyphicon-question-sign" title="What is?"
+             href="https://it.wikipedia.org/wiki/CAPTCHA"; target="_blank"/>
         </div>
         <input class="form-control" style="margin:auto; max-width: 260px" 
type="text" ng-model="input.value"/>
       </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
 
b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
index 185f97f..b3b5822 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttribute.html
@@ -16,15 +16,14 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-
 <div ng-switch="schema.type" class="schema-type">
   <input ng-switch-when="String" class="form-control" type="text"
          ng-model="user.plainAttrs[schema.key].values[index]"
          ng-required="{{schema.mandatoryCondition}}" validate="true"
-         ng-disabled="schema.readonly" ng-init="initAttribute(schema, index)" 
name="{{schema.key}}"/>
+         ng-disabled="schema.readonly || customReadonly(schema.key)" 
ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
 
   <div ng-switch-when="Encrypted" class="input-group input-group-sm">
-    <span class="input-group-btn">  
+    <span class="input-group-btn" ng-disabled="schema.readonly || 
customReadonly(schema.key)">  
       <button type="button" class="btn btn-default btn-flat"
               confirm="{{'CONFIRM_REMOVE'| translate}}"
               onclick="var pwd = $(this).parent().next('input');
@@ -48,7 +47,7 @@ under the License.
     <input class="form-control" type="password" readonly="true"
            ng-model="user.plainAttrs[schema.key].values[index]"
            ng-required="{{schema.mandatoryCondition}}" validate="true"
-           ng-disabled="schema.readonly" ng-init="initAttribute(schema, 
index)" name="{{schema.key}}"/>
+           ng-disabled="schema.readonly || customReadonly(schema.key)" 
ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
     <span class="input-group-btn">  
       <button type="button" class="btn btn-default btn-flat"
               onmousedown="var pwd = $(this).parent().prev('input');
@@ -62,25 +61,29 @@ under the License.
                     $(this).children().attr('class', 'fa fa-eye');
                   }"
               >
-        <i class="fa fa-eye"></i>
-      </button>
-    </span>
-  </div>
+  <i class="fa fa-eye"></i>
+</button>
+</span>
+</div>
 
-  <div ng-switch-when="Boolean">
-    <input type="checkbox" 
ng-model="user.plainAttrs[schema.key].values[index]" 
ng-required="{{schema.mandatoryCondition}}" 
+<div ng-switch-when="Boolean">
+  <input type="checkbox" ng-model="user.plainAttrs[schema.key].values[index]" 
+           ng-true-value="'true'" 
+           ng-false-value="'false'" 
+           ng-required="{{schema.mandatoryCondition}}"
+           ng-disabled="schema.readonly || customReadonly(schema.key)"
            ng-init="initAttribute(schema, index)" />
-  </div>
+    </div>
 
-  <input ng-switch-when="Long" class="form-control" type="number" 
ng-model="user.plainAttrs[schema.key].values[index]" 
-         ng-required="{{schema.mandatoryCondition}}" validate="true"
-         ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
+    <input ng-switch-when="Long" class="form-control" type="number" 
ng-model="user.plainAttrs[schema.key].values[index]" 
+           ng-required="{{schema.mandatoryCondition}}" validate="true"
+           ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
 
-  <input ng-switch-when="Double" class="form-control" type="number" 
ng-model="user.plainAttrs[schema.key].values[index]"
-         ng-required="{{schema.mandatoryCondition}}" validate="true"
-         ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
-  
-  <div ng-switch-when="Date" id="date">
+    <input ng-switch-when="Double" class="form-control" type="number" 
ng-model="user.plainAttrs[schema.key].values[index]"
+           ng-required="{{schema.mandatoryCondition}}" validate="true"
+           ng-init="initAttribute(schema, index)" name="{{schema.key}}"/>
+
+    <div ng-switch-when="Date" id="date">
       <input type="text" class="dateTimePicker"
              id="dateTimePicker"
              kendo-date-time-picker
@@ -88,33 +91,37 @@ under the License.
              ng-required="{{schema.mandatoryCondition}}" close-text="Close"
              ng-init="initAttribute(schema, index)"
              ng-change="bindDateToModel(selectedDate, extendedDate)"
+             ng-disabled="schema.readonly || customReadonly(schema.key)"
              k-ng-model="selectedDate"
              data-k-format=languageFormat 
              />
-  </div>
+    </div>
 
-  <div ng-switch-when="Enum" ng-init="initAttribute(schema, index)">
-    <select class="form-control"
-            ng-model="user.plainAttrs[schema.key].values[index]"
-            ng-required="{{schema.mandatoryCondition}}">
-      <option ng-repeat="value in enumerationValues" value="{{value}}">
-        {{enumerationKeys[$index] || value}}
-      </option>
-    </select>
-  </div>
+    <div ng-switch-when="Enum" ng-init="initAttribute(schema, index)">
+      <select class="form-control"
+              ng-model="user.plainAttrs[schema.key].values[index]"
+              ng-required="{{schema.mandatoryCondition}}"
+              ng-disabled="schema.readonly || customReadonly(schema.key)">
+        <option ng-repeat="value in enumerationValues" value="{{value}}">
+          {{enumerationKeys[$index]|| value}}
+        </option>
+      </select>
+    </div>
 
-  <div ng-switch-when="Binary" ng-init="initAttribute(schema, index)">
-    <input file-input type="file" id="fileInput" name="fileInput"/>
-    <button type="button" title="Download file" class="fileButton btn 
btn-default btn-sm" ng-click="download()">
-      <i class="glyphicon glyphicon-download" ></i>
-    </button>
-  </div>
+    <div ng-switch-when="Binary"
+         ng-disabled="schema.readonly || customReadonly(schema.key)"
+         ng-init="initAttribute(schema, index)">
+      <input file-input type="file" id="fileInput" name="fileInput"/>
+      <button type="button" title="Download file" class="fileButton btn 
btn-default btn-sm" ng-click="download()">
+        <i class="glyphicon glyphicon-download" ></i>
+      </button>
+    </div>
 
-  <input ng-switch-default class="form-control" type="text"
-         ng-model="user.plainAttrs[schema.key].values[index]"
-         ng-required="{{schema.mandatoryCondition}}" 
-         ng-disabled="schema.readonly" ng-init="initAttribute(schema, index)"/>
-</div>
+    <input ng-switch-default class="form-control" type="text"
+           ng-model="user.plainAttrs[schema.key].values[index]"
+           ng-required="{{schema.mandatoryCondition}}" 
+           ng-disabled="schema.readonly || customReadonly(schema.key)" 
ng-init="initAttribute(schema, index)"/>
+  </div>
 
 
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/daa1d0d2/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttribute.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttribute.html
 
b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttribute.html
index a54a60c..ee84784 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttribute.html
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttribute.html
@@ -19,5 +19,5 @@ under the License.
 <p>
   <input class="form-control" type="text"
          ng-model="user.virAttrs[schema.key].values[index]"
-         ng-disabled="schema.readonly"/>
+         ng-disabled="schema.readonly || customReadonly(schema.key)"/>
 </p>

Reply via email to