You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by an...@apache.org on 2015/12/30 16:29:53 UTC

syncope git commit: [SYNCOPE-719] Added resource management to enduser

Repository: syncope
Updated Branches:
  refs/heads/master 44700ae51 -> 24cb528e6


[SYNCOPE-719] Added resource management to enduser


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

Branch: refs/heads/master
Commit: 24cb528e63752e36e3821e7f9303fef0a37c6753
Parents: 44700ae
Author: Andrea Patricelli <an...@tirasa.net>
Authored: Wed Dec 30 16:21:21 2015 +0100
Committer: Andrea Patricelli <an...@tirasa.net>
Committed: Wed Dec 30 16:21:21 2015 +0100

----------------------------------------------------------------------
 client/enduser/pom.xml                          | 10 +++
 .../enduser/SyncopeEnduserApplication.java      | 11 +++
 .../enduser/adapters/ResourceTOAdapter.java     | 41 ++++++++++
 .../client/enduser/adapters/UserTOAdapter.java  |  4 +
 .../client/enduser/model/ResourceResponse.java  | 45 +++++++++++
 .../client/enduser/model/UserTORequest.java     | 17 +++++
 .../resources/SyncopeResourceResource.java      | 78 ++++++++++++++++++++
 .../META-INF/resources/app/css/editUser.css     |  6 +-
 .../resources/META-INF/resources/app/index.html |  3 +
 .../resources/META-INF/resources/app/js/app.js  |  2 +-
 .../app/js/controllers/UserController.js        | 26 ++++---
 .../resources/app/js/directives/resources.js    | 51 +++++++++++++
 .../app/js/services/resourceService.js          | 42 +++++++++++
 .../META-INF/resources/app/views/captcha.html   |  6 +-
 .../app/views/dynamicDerivedAttribute.html      |  4 +-
 .../app/views/dynamicDerivedAttributes.html     |  4 +-
 .../app/views/dynamicPlainAttribute.html        | 60 +++++++--------
 .../app/views/dynamicPlainAttributes.html       |  6 +-
 .../app/views/dynamicVirtualAttribute.html      |  4 +-
 .../app/views/dynamicVirtualAttributes.html     |  6 +-
 .../META-INF/resources/app/views/resources.html | 12 +++
 .../META-INF/resources/app/views/self.html      | 17 +----
 .../resources/app/views/user-credentials.html   |  3 +-
 .../resources/app/views/user-resources.html     |  4 +-
 pom.xml                                         | 12 +++
 25 files changed, 404 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/client/enduser/pom.xml b/client/enduser/pom.xml
index 24b27b8..b1c49ec 100644
--- a/client/enduser/pom.xml
+++ b/client/enduser/pom.xml
@@ -129,6 +129,16 @@ under the License.
     </dependency>
     <dependency>
       <groupId>org.webjars.bower</groupId>
+      <artifactId>select2</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.webjars.bower</groupId>
+          <artifactId>jquery</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
       <artifactId>FileSaver.js</artifactId>
     </dependency>
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
index 5a565a4..d1ab35c 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
@@ -34,6 +34,7 @@ import org.apache.syncope.client.enduser.resources.LoginResource;
 import org.apache.syncope.client.enduser.resources.LogoutResource;
 import org.apache.syncope.client.enduser.resources.SchemaResource;
 import org.apache.syncope.client.enduser.resources.SecurityQuestionResource;
+import org.apache.syncope.client.enduser.resources.SyncopeResourceResource;
 import org.apache.syncope.client.enduser.resources.UserSelfCreateResource;
 import org.apache.syncope.client.enduser.resources.UserSelfReadResource;
 import org.apache.syncope.client.enduser.resources.UserSelfUpdateResource;
@@ -182,6 +183,16 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali
             }
         });
 
+        mountResource("/api/resources", new ResourceReference("resources") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new SyncopeResourceResource();
+            }
+        });
+        
         mountResource("/api/securityQuestions", new ResourceReference("securityQuestions") {
 
             private static final long serialVersionUID = -128426276529456602L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/ResourceTOAdapter.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/ResourceTOAdapter.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/ResourceTOAdapter.java
new file mode 100644
index 0000000..5245901
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/ResourceTOAdapter.java
@@ -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.
+ */
+package org.apache.syncope.client.enduser.adapters;
+
+import java.util.List;
+import org.apache.syncope.client.enduser.model.ResourceResponse;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ResourceTOAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ResourceTOAdapter.class);
+
+    public ResourceResponse fromResourceTO(final List<ResourceTO> resourceTOs) {
+
+        ResourceResponse resourceTOResponse = new ResourceResponse();
+
+        for (ResourceTO resourceTO : resourceTOs) {
+            resourceTOResponse.getResources().add(resourceTO.getKey());
+        }
+
+        return resourceTOResponse;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.java
index f27ac85..2dc32dd 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.java
@@ -54,6 +54,8 @@ public class UserTOAdapter {
         userTO.getPlainAttrs().addAll(userTORequest.getPlainAttrs().values());
         userTO.getDerAttrs().addAll(userTORequest.getDerAttrs().values());
         userTO.getVirAttrs().addAll(userTORequest.getVirAttrs().values());
+        // add resources
+        userTO.getResources().addAll(userTORequest.getResources());
 
         return userTO;
     }
@@ -70,6 +72,8 @@ public class UserTOAdapter {
         userTORequest.getPlainAttrs().putAll(userTO.getPlainAttrMap());
         userTORequest.getDerAttrs().putAll(userTO.getDerAttrMap());
         userTORequest.getVirAttrs().putAll(userTO.getVirAttrMap());
+        
+        userTORequest.getResources().addAll(userTO.getResources());
 
         return userTORequest;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/ResourceResponse.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/ResourceResponse.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/ResourceResponse.java
new file mode 100644
index 0000000..77efecc
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/ResourceResponse.java
@@ -0,0 +1,45 @@
+/*
+ * 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.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class ResourceResponse implements Serializable {
+
+    private static final long serialVersionUID = -7499232727784886980L;
+
+    private Set<String> resources = new HashSet<>();
+
+    public Set<String> getResources() {
+        return resources;
+    }
+
+    public void setResources(final Set<String> resources) {
+        this.resources = resources;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java
index 09bc219..7196f39 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java
@@ -20,7 +20,9 @@ package org.apache.syncope.client.enduser.model;
 
 import java.io.Serializable;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.syncope.common.lib.to.AttrTO;
 
@@ -46,6 +48,8 @@ public class UserTORequest implements Serializable {
 
     private Map<String, AttrTO> virAttrs = new HashMap<>();
 
+    private Set<String> resources = new HashSet<>();
+
     public UserTORequest() {
     }
 
@@ -121,6 +125,14 @@ public class UserTORequest implements Serializable {
         this.virAttrs = virAttrs;
     }
 
+    public Set<String> getResources() {
+        return resources;
+    }
+
+    public void setResources(final Set<String> resources) {
+        this.resources = resources;
+    }
+
     public UserTORequest key(final Long value) {
         this.key = value;
         return this;
@@ -166,6 +178,11 @@ public class UserTORequest implements Serializable {
         return this;
     }
 
+    public UserTORequest resources(final Set<String> value) {
+        this.resources = value;
+        return this;
+    }
+
     @Override
     public String toString() {
         return ToStringBuilder.reflectionToString(this);

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SyncopeResourceResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SyncopeResourceResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SyncopeResourceResource.java
new file mode 100644
index 0000000..0331ce1
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SyncopeResourceResource.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.client.enduser.resources;
+
+import java.io.IOException;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.adapters.ResourceTOAdapter;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.rest.api.service.ResourceService;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.apache.wicket.request.resource.IResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SyncopeResourceResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = 7475706378304995200L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeResourceResource.class);
+
+    private final ResourceService resourceService;
+
+    private final ResourceTOAdapter resourceTOAdapter;
+
+    public SyncopeResourceResource() {
+        resourceTOAdapter = new ResourceTOAdapter();
+        resourceService = SyncopeEnduserSession.get().getService(ResourceService.class);
+    }
+
+    @Override
+    protected AbstractResource.ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
+
+        LOG.debug("Search all available resources");
+
+        AbstractResource.ResourceResponse response = new AbstractResource.ResourceResponse();
+
+        try {
+            final List<ResourceTO> resourceTOs = resourceService.list();
+
+            response.setWriteCallback(new AbstractResource.WriteCallback() {
+
+                @Override
+                public void writeData(final IResource.Attributes attributes) throws IOException {
+                    attributes.getResponse().write(POJOHelper.serialize(resourceTOAdapter.fromResourceTO(resourceTOs)));
+                }
+            });
+            response.setStatusCode(Response.Status.OK.getStatusCode());
+        } catch (Exception e) {
+            LOG.error("Error retrieving available resources", e);
+            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
+                    .append("ErrorMessage{{ ")
+                    .append(e.getMessage())
+                    .append(" }}")
+                    .toString());
+        }
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
index eb03a60..f87981b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
@@ -84,9 +84,9 @@ under the License.
 }
 
 .attribute-ui-select {
-  width: 100%;
-  padding-right: 7%;
-  padding-left: 7%;
+  width: 60%;
+  margin-left: 20%;
+  list-style: none;
 }
 
 #previous {

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 493e629..21f88d0 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
@@ -82,6 +82,7 @@ under the License.
     <script src="js/services/realmService.js"></script>
     <script src="js/services/securityQuestionService.js"></script>
     <script src="js/services/infoService.js"></script>
+    <script src="js/services/resourceService.js"></script>
     <script src="js/services/captchaService.js"></script>
     <!--controllers-->
     <script src="js/controllers/HomeController.js"></script>
@@ -99,6 +100,7 @@ under the License.
     <script src="js/directives/loader.js"></script>
     <script src="js/directives/equals.js"></script>
     <script src="js/directives/captcha.js"></script>
+    <script src="js/directives/resources.js"></script>
     <!--filters-->
     <script src="js/filters/propsFilter.js"></script>
 
@@ -112,6 +114,7 @@ under the License.
     <link href="../webjars/ionicons/${ionicons.version}/css/ionicons.min.css" rel="stylesheet" type="text/css" />
     <link href="../webjars/angular-ui-select/${angular-ui-select.version}/select.css" rel="stylesheet" type="text/css"/>
     <link href="../webjars/angular-growl-2/${angular-growl-2.version}/angular-growl.css" rel="stylesheet" type="text/css"/>
+    <link href="../webjars/select2/${select2.version}/select2.css" rel="stylesheet" />
     <link href="css/app.css" rel="stylesheet" type="text/css" />
     <link href="css/login.css" rel="stylesheet" type="text/css" />
     <link href="css/editUser.css" rel="stylesheet" type="text/css" />

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 4253e11..4792ea4 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
@@ -29,7 +29,7 @@ angular.module('info', []);
 var app = angular.module('SyncopeEnduserApp', [
   'ui.router',
   'ui.bootstrap',
-//  'ui.select',
+  'ui.select',
   'ngSanitize',
   'ngAnimate',
   'ngResource',

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 10fb3b7..cb1689a 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
@@ -20,9 +20,9 @@
 'use strict';
 
 angular.module("self").controller("UserController", ['$scope', '$rootScope', '$location', '$compile', 'AuthService',
-  'UserSelfService', 'SchemaService', 'RealmService', 'SecurityQuestionService', 'CaptchaService', 'growl', function ($scope,
-          $rootScope, $location, $compile, AuthService, UserSelfService, SchemaService, RealmService, SecurityQuestionService,
-          CaptchaService, growl) {
+  'UserSelfService', 'SchemaService', 'RealmService', 'ResourceService', 'SecurityQuestionService', 'CaptchaService',
+  'growl', function ($scope, $rootScope, $location, $compile, AuthService, UserSelfService, SchemaService, RealmService,
+          ResourceService, SecurityQuestionService, CaptchaService, growl) {
 
     $scope.user = {};
     $scope.confirmPassword = {
@@ -45,9 +45,11 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         plainSchemas: [],
         derSchemas: [],
         virSchemas: [],
+        resources: [],
         errorMessage: '',
         attributeTable: {},
-        virtualAttributeTable: {}
+        virtualAttributeTable: {},
+        selectedResources: []
       };
 
       var initSchemas = function () {
@@ -141,8 +143,6 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
           }
           console.log("Error retrieving user schemas: ", errorMessage);
         });
-        console.log("USER WITH ATTRTO: ", $scope.user);
-
       };
 
       var initSecurityQuestions = function () {
@@ -167,12 +167,19 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         $scope.user.realm = RealmService.getUserRealm();
       };
 
+      var initResources = function () {
+        ResourceService.getResources().then(function (response) {
+          $scope.dynamicForm.resources = response.resources;
+        });
+      };
 
       var readUser = function () {
         UserSelfService.read().then(function (response) {
           $scope.user = response;
           $scope.user.password = undefined;
           $scope.initialSecurityQuestion = $scope.user.securityQuestion;
+          // initialize already assigned resources
+          $scope.dynamicForm.selectedResources = $scope.user.resources;
         }, function () {
           console.log("Error");
         });
@@ -188,12 +195,11 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
           securityAnswer: '',
           plainAttrs: {},
           derAttrs: {},
-          virAttrs: {}
+          virAttrs: {},
+          resources: []
         };
-
         // retrieve user realm or all available realms
         initUserRealm();
-
       } else {
         // read user from syncope core
         readUser();
@@ -204,6 +210,8 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
       initSecurityQuestions();
       // initialize user attributes starting from any object schemas
       initSchemas();
+      // initialize available resources
+      initResources();
     };
 
     $scope.saveUser = function (user) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/resources.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/resources.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/resources.js
new file mode 100644
index 0000000..bf1a822
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/resources.js
@@ -0,0 +1,51 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+angular.module('self')
+        .directive('resources', function () {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/resources.html',
+            scope: {
+              dynamicForm: "=form",
+              user: "="
+            },
+            controller: function ($scope, $filter) {
+              $scope.addResource = function (item, model) {
+                var resource = item;
+                $scope.user.resources.push(resource);
+              };
+
+              $scope.removeResource = function (item, model) {
+                var resourceIndex = $scope.getIndex(item);
+                $scope.user.resources.splice(resourceIndex, 1);
+
+              };
+
+              $scope.getIndex = function (selectedResource) {
+                var resourceIndex = $scope.user.resources.map(function (resourceName) {
+                  return resourceName;
+                }).indexOf(selectedResource);
+                return resourceIndex;
+              };
+
+            }
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/js/services/resourceService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/resourceService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/resourceService.js
new file mode 100644
index 0000000..06e3788
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/resourceService.js
@@ -0,0 +1,42 @@
+/* 
+ * 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('ResourceService', ['$resource', '$q', '$http',
+          function ($resource, $q, $http) {
+
+            var resourceService = {};
+
+            resourceService.getResources = function () {
+
+              return  $http.get("/syncope-enduser/api/resources")
+                      .then(function (response) {
+                        console.log("resourceAPI response: ", response);
+                        return response.data;
+                      }, function (response) {
+                        console.log("Something went wrong during resources retrieval, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+            return resourceService;
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 3613778..06582dc 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
@@ -4,8 +4,10 @@
       <div class="navbar-header">
         <img alt="captcha" ng-src="{{captchaUrl}}'"/>
         <div style="margin-top: 5%">
-          <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"/>
+          <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"/>
         </div>
         <input class="form-control" style="margin-top: 5%" type="text" ng-model="input.value"/>
       </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttribute.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttribute.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttribute.html
index f0d325b..e2dbcc1 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttribute.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttribute.html
@@ -1,5 +1,5 @@
-<div>
+<p>
   <input class="form-control" type="text"
          ng-model="user.derAttrs[schema.key].values[index]"
          ng-disabled="true"/>
-</div>
\ No newline at end of file
+</p>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
index e58a8de..0a3ce3e 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
@@ -1,4 +1,6 @@
 <div id="attribute" class="form-group" ng-repeat="derSchema in dynamicForm.derSchemas">
-    <label for="derivedSchema.key">{{derSchema.key}}</label>  <span uib-popover="{{derSchema.expression}}" popover-trigger="mouseenter" class="glyphicon glyphicon-question-sign"/>
+  <label for="derivedSchema.key">{{derSchema.key}}</label>  <span uib-popover="{{derSchema.expression}}" 
+                                                                  popover-trigger="mouseenter" 
+                                                                  class="glyphicon glyphicon-question-sign"/>
   <dynamic-derived-attribute schema="derSchema" user="user" index="0"></dynamic-derived-attribute>
 </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 9c6b1d9..a0ccac6 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
@@ -11,9 +11,11 @@
     <input type="checkbox" ng-model="user.plainAttrs[schema.key].values[index]" ng-required="schema.mandatoryCondition" 
            ng-init="initAttribute(schema, index)" />
   </div>
-  <input ng-switch-when="Long" class="form-control" type="number" ng-model="user.plainAttrs[schema.key].values[index]" ng-required="schema.mandatoryCondition"
+  <input ng-switch-when="Long" class="form-control" type="number" ng-model="user.plainAttrs[schema.key].values[index]" 
+         ng-required="schema.mandatoryCondition"
          ng-init="initAttribute(schema, index)" />
-  <input ng-switch-when="Double" class="form-control" type="number" ng-model="user.plainAttrs[schema.key].values[index]" ng-required="schema.mandatoryCondition"
+  <input ng-switch-when="Double" class="form-control" type="number" ng-model="user.plainAttrs[schema.key].values[index]"
+         ng-required="schema.mandatoryCondition"
          ng-init="initAttribute(schema, index)" />
   <p ng-switch-when="Date" class="input-group" >
     <input type="text" class="form-control" 
@@ -23,36 +25,36 @@
            min-date="minDate" max-date="maxDate"
            is-open="status.opened" datepicker-options="dateOptions"
            ng-required="schema.mandatoryCondition" close-text="Close" ng-init="initAttribute(schema, index)"/>
-    <span class="input-group-btn">
-      <button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
-    </span>
-  </p>
+  <span class="input-group-btn">
+    <button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+  </span>
+</p>
 
-  <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}}">{{schema.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">
+    <option ng-repeat="value in enumerationValues" value="{{value}}">{{schema.enumerationKeys[$index] || value}}</option>
+  </select>
+</div>
 
-  <div ng-switch-when="Binary" ng-init="initAttribute(schema, index)">
-    <div enctype="multipart/form-data" accept-charset="UTF-8">
-      <input id="fileInput" type="file" ng-required="schema.mandatoryCondition"/>
-      <button type="button" title="Download file" class="fileButton btn btn-default btn-sm" ng-click="download()">
-        <i class="glyphicon glyphicon-download" ></i>
-      </button>
-      <button type="button" class="fileButton btn btn-default btn-sm" title="Remove file" ng-click="remove()">
-        <i class="glyphicon glyphicon-remove-sign" ></i>
-      </button>
-      <h4><span class="label label-primary" ng-model="userFile">{{userFile}}</span></h4>
-    </div>
-    
+<div ng-switch-when="Binary" ng-init="initAttribute(schema, index)">
+  <div enctype="multipart/form-data" accept-charset="UTF-8">
+    <input id="fileInput" type="file" ng-required="schema.mandatoryCondition"/>
+    <button type="button" title="Download file" class="fileButton btn btn-default btn-sm" ng-click="download()">
+      <i class="glyphicon glyphicon-download" ></i>
+    </button>
+    <button type="button" class="fileButton btn btn-default btn-sm" title="Remove file" ng-click="remove()">
+      <i class="glyphicon glyphicon-remove-sign" ></i>
+    </button>
+    <h4><span class="label label-primary" ng-model="userFile">{{userFile}}</span></h4>
   </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" ng-init="initAttribute(schema, index)"/>
 
 </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
index 5d4bb40..1b39950 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
@@ -5,10 +5,12 @@
   </div>
 
   <div ng-if="plainSchema.multivalue">
-    <div ng-repeat="field in dynamicForm.attributeTable[plainSchema.key].fields track by $index" ng-model='dynamicForm.attributeTable[plainSchema.key].fields[$index]'>
+    <div ng-repeat="field in dynamicForm.attributeTable[plainSchema.key].fields track by $index" 
+         ng-model='dynamicForm.attributeTable[plainSchema.key].fields[$index]'>
       <dynamic-plain-attribute schema="plainSchema" user="user" index="$index"></dynamic-plain-attribute>
       <span>
-        <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" ng-click="removeAttributeField(plainSchema.key, $index)">
+        <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
+                ng-click="removeAttributeField(plainSchema.key, $index)">
           <i class="glyphicon glyphicon-minus" title="Remove value"></i>
         </button>
       </span>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/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 826f249..e7f6b9b 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
@@ -1,5 +1,5 @@
-<div>
+<p>
   <input class="form-control" type="text"
          ng-model="user.virAttrs[schema.key].values[index]"
          ng-disabled="schema.readonly"/>
-</div>
\ No newline at end of file
+</p>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
index 1334611..8600e3a 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
@@ -1,10 +1,12 @@
 <div id="attribute" class="form-group" ng-repeat="virtualSchema in dynamicForm.virSchemas">
   <label for="virtualSchema.key">{{virtualSchema.key}}</label>
   <!--all virtual schemas are multivalue-->
-  <div ng-repeat="field in dynamicForm.virtualAttributeTable[virtualSchema.key].fields track by $index" ng-model='dynamicForm.virtualAttributeTable[virtualSchema.key].fields[$index]'>
+  <div ng-repeat="field in dynamicForm.virtualAttributeTable[virtualSchema.key].fields track by $index" 
+       ng-model='dynamicForm.virtualAttributeTable[virtualSchema.key].fields[$index]'>
     <dynamic-virtual-attribute schema="virtualSchema" user="user" index="$index"></dynamic-virtual-attribute>
     <span>
-      <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" ng-click="removeVirtualAttributeField(virtualSchema.key, $index)">
+      <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
+              ng-click="removeVirtualAttributeField(virtualSchema.key, $index)">
         <i class="glyphicon glyphicon-minus" title="Remove value"></i>
       </button>
     </span>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/resources.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/resources.html b/client/enduser/src/main/resources/META-INF/resources/app/views/resources.html
new file mode 100644
index 0000000..f2c1a10
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/resources.html
@@ -0,0 +1,12 @@
+<ui-select on-select="addResource($item, $model)" on-remove="removeResource($item, $model)" multiple
+           ng-model="dynamicForm.selectedResources" theme="select2" class="attribute-ui-select">
+  <ui-select-match placeholder="Select resource...">{{$item}}</ui-select-match>
+  <ui-select-choices repeat="resource in dynamicForm.resources">
+    <div ng-bind-html="resource | highlight: $select.search"></div>
+  </ui-select-choices>
+</ui-select>
+<ul class="attribute-ui-select">
+  <li class="attribute-virtual-value-field" ng-if="dynamicForm.selectedResources.length == 0">
+    <strong>No resources selected...</strong>
+  </li>
+</ul>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/self.html b/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
index 1a87c46..93d85fb 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/self.html
@@ -75,7 +75,8 @@ under the License.
 
                 <div class="form-group">
 
-                  <button type="submit" id="login-btn" class="btn btn-default btn-signin login-btn" ng-click="login(credentials)">Login</button>
+                  <button type="submit" id="login-btn" class="btn btn-default btn-signin login-btn" 
+                          ng-click="login(credentials)">Login</button>
                 </div>
               </fieldset>
             </form>
@@ -96,20 +97,6 @@ under the License.
         <!-- /#login-container -->
       </div> <!-- /#login -->
     </div>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
     <!--    <div class="container" ng-cloak ng-controller="LoginController">
           <div class="card card-container">
             <img class="login-logo" src="img/logo-green.png" />

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
index 31bc00d..a257ebb 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
@@ -47,7 +47,8 @@ under the License.
 
 <div id="attribute" class="form-group">
   <label for="securityAnswer">Security Answer</label>
-  <input ng-disabled="user.securityQuestion === initialSecurityQuestion" name="securityAnswer" type="text" class="form-control" ng-model="user.securityAnswer"
+  <input ng-disabled="user.securityQuestion === initialSecurityQuestion" name="securityAnswer" type="text" class="form-control" 
+         ng-model="user.securityAnswer"
          placeholder="security answer">
 </div>
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
index 30b79c5..d2498c0 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
@@ -17,7 +17,9 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-
+<div>
+  <resources form="dynamicForm" user="user"></resources>
+</div>
 <div id="attribute" class="form-group row">
   <navigation-buttons ng-show="createMode" next="create.finish" previous="create.plainSchemas"></navigation-buttons>
   <navigation-buttons ng-show="!createMode" next="update.finish" previous="update.plainSchemas"></navigation-buttons>

http://git-wip-us.apache.org/repos/asf/syncope/blob/24cb528e/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f722c6d..a000388 100644
--- a/pom.xml
+++ b/pom.xml
@@ -402,6 +402,7 @@ under the License.
     <angular-ui-select.version>0.13.1</angular-ui-select.version>
     <angular-sanitize.version>1.4.8</angular-sanitize.version>
     <angular-growl-2.version>0.7.3</angular-growl-2.version>
+    <select2.version>3.4.8</select2.version>
     <FileSaver.version>0.0.2</FileSaver.version>
     
     <izpack.version>5.0.6</izpack.version>
@@ -1079,6 +1080,17 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.webjars.bower</groupId>
+        <artifactId>select2</artifactId>
+        <version>${select2.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.webjars.bower</groupId>
+            <artifactId>jquery</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>org.webjars.bower</groupId>
         <artifactId>FileSaver.js</artifactId>
         <version>${FileSaver.version}</version>
       </dependency>