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/10/30 10:34:02 UTC

[2/4] syncope git commit: SYNCOPE-701 first working implementation

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/img/ajax-loader.gif
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/img/ajax-loader.gif b/client/enduser/src/main/resources/META-INF/resources/app/img/ajax-loader.gif
new file mode 100644
index 0000000..766cf24
Binary files /dev/null and b/client/enduser/src/main/resources/META-INF/resources/app/img/ajax-loader.gif differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/img/busy.gif
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/img/busy.gif b/client/enduser/src/main/resources/META-INF/resources/app/img/busy.gif
new file mode 100644
index 0000000..e77264f
Binary files /dev/null and b/client/enduser/src/main/resources/META-INF/resources/app/img/busy.gif differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/img/favicon.png
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/img/favicon.png b/client/enduser/src/main/resources/META-INF/resources/app/img/favicon.png
new file mode 100644
index 0000000..aa2f3e2
Binary files /dev/null and b/client/enduser/src/main/resources/META-INF/resources/app/img/favicon.png differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/img/logo-green.png
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/img/logo-green.png b/client/enduser/src/main/resources/META-INF/resources/app/img/logo-green.png
new file mode 100644
index 0000000..c57b86c
Binary files /dev/null and b/client/enduser/src/main/resources/META-INF/resources/app/img/logo-green.png differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/img/logo.png
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/img/logo.png b/client/enduser/src/main/resources/META-INF/resources/app/img/logo.png
new file mode 100644
index 0000000..f05105e
Binary files /dev/null and b/client/enduser/src/main/resources/META-INF/resources/app/img/logo.png differ

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..6cb7ae6
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -0,0 +1,116 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<!--[if lt IE 7]>      <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>         <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>         <html lang="en" ng-app="myApp" class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html lang="en" ng-app="SyncopeEnduserApp" class="no-js"> <!--<![endif]-->
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>SyncopeEnduserApp</title>
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <!--<link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/normalize.css">-->
+    <!--<link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/main.css">-->
+    <link rel="stylesheet" href="css/app.css">
+    <!--<script src="bower_components/html5-boilerplate/dist/js/vendor/modernizr-2.8.3.min.js"></script>-->
+  </head>
+  <body ng-cloak >
+    <!--<button ng-click=""-->
+
+    <!--[if lt IE 7]>
+        <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+    <![endif]-->
+
+    <!--<div ng-view ng-cloak ng-controller="ApplicationController"></div>-->
+    <div ui-view ng-cloak ng-controller="ApplicationController"></div>
+
+    <!--    <footer id="footer" class="hidden-print">
+          <ul class="nav pull-right">
+              <li>
+                Copyright &copy; 2015, Apache Syncope
+              </li>
+          </ul>
+        </footer>-->
+
+    <!--default global growl message-->
+    <!--<div growl></div>-->
+
+    <!--    <div class="hidden-print" id="initialLoaderDiv">
+          <img src="img/ajax-loader.gif" class="ajax-loader"/>
+        </div>-->
+
+    <!-- In production use:
+    <script src="//ajax.googleapis.com/ajax/libs/angularjs/x.x.x/angular.min.js"></script>
+    -->
+    <script type="text/javascript" src="../webjars/jquery/${jquery.version}/jquery.js"></script>
+    <script src="../webjars/angular/${angular.version}/angular.js"></script>
+    <script src="../webjars/angular-ui-router/${angular-ui-router.version}/angular-ui-router.js"></script>
+    <script src="../webjars/angular-animate/${angular-animate.version}/angular-animate.js"></script>
+    <script src="../webjars/angular-resource/${angular-resource.version}/angular-resource.js"></script>
+    <script src="../webjars/angular-cookies/${angular-cookies.version}/angular-cookies.js"></script>
+    <script src="../webjars/angular-sanitize/${angular-sanitize.version}/angular-sanitize.js"></script>
+    <script src="../webjars/angular-ui-bootstrap/${angular-ui-bootstrap.version}/ui-bootstrap-tpls.js"></script>
+    <script src="../webjars/angular-ui-select/${angular-ui-select.version}/select.js"></script>
+    <script src="../webjars/angular-growl-2/${angular-growl-2.version}/angular-growl.js"></script>
+    <script type="text/javascript" src="../webjars/bootstrap-select/${bootstrap-select.version}/js/bootstrap-select.min.js"></script>
+    <script src="../webjars/FileSaver.js/${FileSaver.version}/FileSaver.js" type="text/javascript"></script>
+    <!--main angular application-->
+    <script src="js/app.js"></script>
+    <!--services-->
+    <script src="js/services/authService.js"></script>
+    <script src="js/services/userSelfService.js"></script>
+    <script src="js/services/schemaService.js"></script>
+    <script src="js/services/realmService.js"></script>
+    <script src="js/services/securityQuestionService.js"></script>
+    <!--controllers-->
+    <script src="js/controllers/HomeController.js"></script>
+    <script src="js/controllers/LoginController.js"></script>
+    <script src="js/controllers/LanguageController.js"></script>
+    <script src="js/controllers/UserController.js"></script>
+    <!--directives-->
+    <script src="js/directives/dynamicAttribute.js"></script>
+    <script src="js/directives/dynamicPlainAttributes.js"></script>
+    <script src="js/directives/dynamicDerivedAttributes.js"></script>
+    <script src="js/directives/dynamicVirtualAttributes.js"></script>
+    <script src="js/directives/navigationButtons.js"></script>
+    <script src="js/directives/loader.js"></script>
+    <script src="js/directives/equals.js"></script>
+    <!--filters-->
+    <script src="js/filters/propsFilter.js"></script>
+
+
+    <link rel="shortcut icon" href="img/favicon.png" type="image/png"/>
+    <link href="css/login.css" rel="stylesheet" type="text/css" />
+    <link href="../webjars/jquery-ui/${jquery-ui.version}/jquery-ui.css" rel="stylesheet" type="text/css" />
+    <link href="../webjars/bootstrap/${bootstrap.version}/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
+    <link href="../webjars/bootstrap-select/${bootstrap-select.version}/css/bootstrap-select.min.css" rel="stylesheet" type="text/css" />
+    <link href="../webjars/font-awesome/${font-awesome.version}/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
+    <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" type="text/css"/>
+    <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" />
+
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..1a53f00
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -0,0 +1,283 @@
+/**
+ 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('home', []);
+angular.module('login', []);
+angular.module('language', []);
+angular.module('self', []);
+
+// Declare app level module which depends on views, and components
+var app = angular.module('SyncopeEnduserApp', [
+  'ui.router',
+  'ui.bootstrap',
+  'ui.select',
+  'ngSanitize',
+  'ngAnimate',
+  'ngResource',
+  'ngCookies',
+  'angular-growl',
+  'home',
+  'login',
+  'language',
+  'self'
+]);
+
+app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'growlProvider',
+  function ($stateProvider, $urlRouterProvider, $httpProvider, growlProvider) {
+    // route configuration
+    $stateProvider
+            .state('home', {
+              url: '/',
+              templateUrl: 'views/self.html'
+            })
+            .state('self', {
+              url: '/self',
+              templateUrl: 'views/self.html'
+            })
+            .state('user-self-update', {
+              url: '/user-self-update',
+              templateUrl: 'views/home.html',
+              controller: 'HomeController',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            .state('create', {
+              url: '/self/create',
+              templateUrl: 'views/editUser.html'
+            })
+            // nested states 
+            // each of these sections will have their own view
+            // url will be nested (/self/create)
+            .state('create.credentials', {
+              url: '/credentials',
+              templateUrl: 'views/user-credentials.html'
+            })
+            .state('create.plainSchemas', {
+              url: '/plainSchemas',
+              templateUrl: 'views/user-plain-schemas.html'
+            })
+            .state('create.derivedSchemas', {
+              url: '/derivedSchemas',
+              templateUrl: 'views/user-derived-schemas.html'
+            })
+            .state('create.virtualSchemas', {
+              url: '/virtualSchemas',
+              templateUrl: 'views/user-virtual-schemas.html'
+            })
+            // url will be /self/create/schema
+            .state('create.groups', {
+              url: '/groups',
+              templateUrl: 'views/user-groups.html'
+            })
+            .state('create.resources', {
+              url: '/resources',
+              templateUrl: 'views/user-resources.html'
+            })
+            .state('update', {
+              url: '/self/update',
+              templateUrl: 'views/editUser.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            // nested states 
+            // each of these sections will have their own view
+            // url will be nested (/self/create)
+            .state('update.credentials', {
+              url: '/credentials',
+              templateUrl: 'views/user-credentials.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            .state('update.plainSchemas', {
+              url: '/plainSchemas',
+              templateUrl: 'views/user-plain-schemas.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            .state('update.derivedSchemas', {
+              url: '/derivedSchemas',
+              templateUrl: 'views/user-derived-schemas.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            .state('update.virtualSchemas', {
+              url: '/virtualSchemas',
+              templateUrl: 'views/user-virtual-schemas.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            // url will be /self/create/schema
+            .state('update.groups', {
+              url: '/groups',
+              templateUrl: 'views/user-groups.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            })
+            .state('update.resources', {
+              url: '/resources',
+              templateUrl: 'views/user-resources.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
+            });
+
+    // catch all other routes
+    // send users to the home page 
+    $urlRouterProvider.otherwise('/');
+
+    // HTTP service configuration
+    $httpProvider.defaults.withCredentials = true;
+
+    $httpProvider.interceptors.push(function ($q, $rootScope, $location) {
+      var numLoadings = 0;
+      return {
+//        'request': function (config) {
+//          numLoadings++;
+//          // Show loader
+//          if (config.url.indexOf("skipLoader=true") == -1) {
+//            $rootScope.$broadcast("loader_show");
+//          }
+//          return config || $q.when(config);
+//        },
+//        'response': function (response) {
+//          if ((--numLoadings) === 0) {
+//            // Hide loader
+//            $rootScope.$broadcast("loader_hide");
+//          }
+//          return response || $q.when(response);
+//        },
+        'responseError': function (response) {
+          if (response.config.url.indexOf("acceptError=true") == -1) {
+            var status = response.status;
+            if (status == 401) {
+              console.log("ERROR " + status);
+//              $location.path("/self");
+            }
+            if (status == 403) {
+              console.log("UNAUTHORIZED " + status);
+//              $location.path("/self");
+            }
+            if (status == 400 || status == 404 || status == 412 || status == 500) {
+//              if (response.data.validationErrors != undefined) {
+//                for (var i in response.data.validationErrors) {
+//                  $rootScope.$broadcast('growlMessage', {text: response.data.validationErrors[i] || '', severity: 'error'});
+//                }
+//              } else if (response.data.message != undefined) {
+//                $rootScope.$broadcast('growlMessage', {text: response.data.message || '', severity: 'error'})
+//              }
+              console.log("GENERIC ERROR " + status);
+            }
+          }
+          return $q.reject(response);
+        }
+      };
+    });
+    
+    growlProvider.globalTimeToLive(10000);
+    growlProvider.globalPosition('bottom-left');
+    growlProvider.globalInlineMessages(true);
+    growlProvider.globalDisableIcons(true);
+    //to enable html in growl
+//    growlProvider.globalEnableHtml(true);
+  }]);
+
+app.run(['$rootScope', '$location', '$cookies', '$state',
+  function ($rootScope, $location, $cookies, $state) {
+    // main program
+    // keep user logged in after page refresh
+    // check if user is logged or not
+    $rootScope.currentUser = $cookies.get('currentUser') || null;
+//If the route change failed due to authentication error, redirect them out
+    $rootScope.$on('$routeChangeError', function (event, current, previous, rejection) {
+      if (rejection === 'Not Authenticated') {
+        $location.path('/self');
+      }
+    });
+
+//    $rootScope.$on('success', function (event, args) {
+//      console.log("IN CONFIG EVENTO: ", event);
+//      $rootScope.$broadcast("error", "success");
+//    });
+
+    $rootScope.$on('$stateChangeSuccess', function (event, toState) {
+      if (toState.name === 'create') {
+        $state.go('create.credentials');
+      } else if (toState.name === 'update') {
+        $state.go('update.credentials');
+      }
+    });
+//        $rootScope.$on('$locationChangeStart', function (event, next, current) {
+//            // redirect to login page if not logged in
+//            if ($location.path() !== '/self' && !$rootScope.globals.currentUser) {
+//                $location.path('/self');
+//            }
+//        });
+  }]);
+
+app.controller('ApplicationController', function ($scope) {
+// DO NOTHING
+//  $scope.$on('success', function (event, args) {
+//    console.log("IN CONFIG EVENTO: ", event)
+//    $scope.$broadcast("error", "success");
+//  });
+});
+
+app.factory('AuthenticationHelper', ['$q', '$rootScope',
+  function ($q, $rootScope) {
+    return {
+      authenticated: function () {
+
+        var currentUser = $rootScope.currentUser;
+
+        console.log("AuthenticationHelper, currentUser: ", currentUser);
+
+        if (angular.isDefined(currentUser) && currentUser) {
+          return true;
+        } else {
+          console.log("NOT AUTHENTICATED, REDIRECT TO LOGIN PAGE");
+          return $q.reject('Not Authenticated');
+        }
+      }
+    };
+  }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/HomeController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/HomeController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/HomeController.js
new file mode 100644
index 0000000..bf86413
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/HomeController.js
@@ -0,0 +1,39 @@
+/**
+ 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("home").controller("HomeController", ['$scope', '$http', '$location', function ($scope, $http, $location) {
+    $scope.title = 'Hello world!';
+    $scope.subtitle = 'Hello world SUBTITLE!';
+    $scope.name = "";
+
+// check if user is logged or not, check session variables: if user isn't logged redirect to login page
+
+      console.log("SONO IN HomeController");
+      
+//      var isLogged = false;
+//      if (!isLogged) {
+//        console.log("REDIRECT TO LOGIN PAGE");
+////        window.location = "./self.html";
+//        $location.path("/self");
+//      }
+
+    
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LanguageController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LanguageController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LanguageController.js
new file mode 100644
index 0000000..9e1fa6c
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LanguageController.js
@@ -0,0 +1,66 @@
+/* 
+ * 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('language')
+        .controller('LanguageController', function ($scope) {
+
+          $scope.languages = {
+            availableLanguages: [
+              {id: '1', name: 'Italiano'},
+              {id: '2', name: 'English'},
+              {id: '3', name: 'Portugese'}
+            ],
+            selectedLanguage: {id: '2', name: 'English'}
+          };
+
+          $scope.init = function () {
+//            MainService.settings().then(function (response) {
+//              $scope.mainSettings = response;
+//            });
+
+            console.log("Init language controller");
+          };
+
+          $scope.changeLanguage = function (language) {
+
+            console.log("Language changed to: ", language);
+            
+            $scope.languages.selectedLanguage = language;
+            
+//            $translate.use(langKey);
+//            LanguageService.switchLocale.query({language: langKey}, {}, function (response) {
+//              $scope.selectedLanguage.locale = langKey;
+//            });
+          };
+
+          this.retrieveLanguages = function () {
+//            LanguageService.language.query({}, function (response) {
+//              $scope.languages = response;
+//            });
+            console.log("Retriebìving available languages");
+          };
+
+
+          this.retrieveLanguages();
+
+
+        });
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
new file mode 100644
index 0000000..c962571
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
@@ -0,0 +1,93 @@
+/* 
+ * 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("login").controller("LoginController", ['$scope', '$rootScope', '$http', '$location', '$cookies',
+  'AuthService', 'growl', function ($scope, $rootScope, $http, $location, $cookies, AuthService, growl) {
+
+    $scope.credentials = {
+      username: '',
+      password: '',
+      errorMessage: ''
+    };
+
+    $scope.login = function (credentials) {
+
+      console.log("CREDENTIALS FROM PAGE: ", credentials);
+      console.log("AUTHSERVICE: ", AuthService);
+
+      AuthService.login($scope.credentials).then(function (user) {
+        console.log("LOGIN SUCCESS FOR: ", user);
+        console.log("DOPO AVER SETTATO CURRENT USER: ", $rootScope.currentUser);
+        console.log("COOKIE CURRENT USER: ", $cookies.get('currentUser'));
+        // reset error message
+        $scope.credentials.errorMessage = '';
+        // got to update page
+        $location.path("/self/update");
+      }, function (response) {
+        console.log("LOGIN FAILED: ", response);
+        $scope.credentials.errorMessage = "Login failed: " + response;
+        growl.error($scope.credentials.errorMessage, {referenceId: 1});
+      });
+    };
+
+    $scope.logout = function () {
+
+      console.log("PERFORMING LOGOUT");
+
+      AuthService.logout().then(function (response) {
+        console.log("LOGOUT SUCCESS: ", response);
+      }, function () {
+        console.log("LOGOUT FAILED");
+      });
+    };
+
+    $scope.isLogged = function () {
+      return angular.isDefined($rootScope.currentUser) && $rootScope.currentUser;
+    };
+
+    $scope.selfCreate = function () {
+      $location.path("/self/create");
+    };
+
+    $scope.passwordReset = function () {
+      // TODO
+      console.log("NOT YET IMPLEMENTED")
+    };
+
+    $scope.errorAPI = function () {
+      $http.get("/syncope-enduser/api/error").success(function (data) {
+        console.log("errorAPI response: ", data);
+      });
+    };
+
+    $scope.sampleAPI = function () {
+      $http.get("/syncope-enduser/api/user-self").success(function (data) {
+        console.log("sampleAPI response: ", data);
+      });
+    };
+
+    $scope.schemaAPI = function () {
+      $http.get("/syncope-enduser/api/schema").success(function (data) {
+        console.log("schemaAPI response: ", data);
+      });
+    };
+
+  }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..892de21
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -0,0 +1,206 @@
+/* 
+ * 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").controller("UserController", ['$scope', '$rootScope', '$location', 'AuthService',
+  'UserSelfService', 'SchemaService', 'RealmService', 'SecurityQuestionService', 'growl', function ($scope, $rootScope,
+          $location, AuthService, UserSelfService, SchemaService, RealmService, SecurityQuestionService, growl) {
+
+    $scope.user = {};
+    $scope.confirmPassword = {
+      value: ''
+    };
+    $scope.userFormValid = false;
+    $scope.createMode = $location.path().indexOf("/self/create") > -1;
+
+    $scope.availableRealms = [];
+    $scope.availableSecurityQuestions = [];
+
+    $scope.initialSecurityQuestion = undefined;
+
+    $scope.initUser = function () {
+
+      $scope.dynamicForm = {
+        plainSchemas: [],
+        derSchemas: [],
+        virSchemas: [],
+        selectedDerSchemas: [],
+        selectedVirSchemas: [],
+        errorMessage: '',
+        attributeTable: {}
+      };
+
+
+      var initSchemas = function () {
+        // initialization is done here synchronously to have all schema fields populated correctly
+        SchemaService.getUserSchemas().then(function (schemas) {
+          $scope.dynamicForm.plainSchemas = schemas.plainSchemas;
+          $scope.dynamicForm.derSchemas = schemas.derSchemas;
+          $scope.dynamicForm.virSchemas = schemas.virSchemas;
+
+          // initialize plain attributes
+          for (var i = 0; i < schemas.plainSchemas.length; i++) {
+
+            var plainSchemaKey = schemas.plainSchemas[i].key;
+
+            if (!$scope.user.plainAttrs[plainSchemaKey]) {
+
+              $scope.user.plainAttrs[plainSchemaKey] = {
+                schema: plainSchemaKey,
+                values: [],
+                readonly: schemas.plainSchemas[i].readonly
+              };
+
+              // 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]
+                };
+              }
+            } else {
+              // initialize multivalue schema and support table: update mode, all provided values
+              if (schemas.plainSchemas[i].multivalue) {
+                $scope.dynamicForm.attributeTable[schemas.plainSchemas[i].key] = {
+                  fields: [schemas.plainSchemas[i].key + "_" + 0]
+                };
+                // add other values
+                for (var j = 1; j < $scope.user.plainAttrs[plainSchemaKey].values.length; j++) {
+                  $scope.dynamicForm.attributeTable[schemas.plainSchemas[i].key].fields.push(schemas.plainSchemas[i].key + "_" + j);
+                }
+              }
+            }
+          }
+
+          // initialize derived attributes
+          for (var i = 0; i < schemas.derSchemas.length; i++) {
+
+            var derSchemaKey = schemas.derSchemas[i].key;
+
+            if ($scope.user.derAttrs[derSchemaKey]) {
+              $scope.dynamicForm.selectedDerSchemas.push(schemas.derSchemas[i]);
+            }
+          }
+
+          // initialize virtual attributes
+          for (var i = 0; i < schemas.virSchemas.length; i++) {
+
+            var virSchemaKey = schemas.virSchemas[i].key;
+
+            if ($scope.user.virAttrs[virSchemaKey]) {
+              $scope.dynamicForm.selectedVirSchemas.push(schemas.virSchemas[i]);
+            }
+          }
+
+        }, function () {
+          console.log("Error retrieving user schemas");
+        });
+        console.log("USER WITH ATTRTO: ", $scope.user);
+
+      };
+
+      var initSecurityQuestions = function () {
+        SecurityQuestionService.getAvailableSecurityQuestions().then(function (response) {
+          $scope.availableSecurityQuestions = response;
+        }, function () {
+          console.log("Error");
+        });
+      };
+
+      var initRealms = function () {
+        $scope.availableRealms = RealmService.getAvailableRealmsStub();
+      };
+
+      var initUserRealm = function () {
+        $scope.user.realm = RealmService.getUserRealm();
+      };
+
+
+      var readUser = function () {
+        UserSelfService.read().then(function (response) {
+          $scope.user = response;
+          $scope.user.password = undefined;
+          $scope.initialSecurityQuestion = $scope.user.securityQuestion;
+        }, function () {
+          console.log("Error");
+        });
+      };
+
+      if ($scope.createMode) {
+
+        $scope.user = {
+          username: '',
+          password: '',
+          realm: '',
+          securityQuestion: undefined,
+          securityAnswer: '',
+          plainAttrs: {},
+          derAttrs: {},
+          virAttrs: {}
+        };
+
+        // retrieve user realm or all available realms
+        initUserRealm();
+
+      } else {
+
+        // read user from syncope core
+        readUser();
+        // read user security question
+
+      }
+
+      initRealms();
+      //retrieve security available questions
+      initSecurityQuestions();
+      // initialize user attributes starting from any object schemas
+      initSchemas();
+
+    };
+
+    $scope.saveUser = function (user) {
+      console.log("Save user: ", user);
+
+      if ($scope.createMode) {
+
+        UserSelfService.create(user).then(function (response) {
+          console.log("Created user: ", response);
+          growl.success("User " + $scope.user.username + " successfully created", {referenceId: 1});
+          $location.path('/self');
+        }, function (response) {
+          console.log("Error during user creation: ", response);
+          growl.error("Error: " + response, {referenceId: 2});
+        });
+
+      } else {
+
+        UserSelfService.update(user).then(function (response) {
+          console.log("Updated user: ", response);
+          growl.success("User " + $scope.user.username + " successfully updated", {referenceId: 1});
+          $location.path('/self');
+        }, function (response) {
+          console.log("Error during user update: ", response);
+          growl.error("Error: " + response, {referenceId: 2});
+        });
+      }
+    };
+
+
+
+  }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicAttribute.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicAttribute.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicAttribute.js
new file mode 100644
index 0000000..6a00507
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicAttribute.js
@@ -0,0 +1,190 @@
+/* 
+ * 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('dynamicAttribute', function ($filter) {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/dynamicAttribute.html',
+            scope: {
+              schema: "=",
+              index: "=",
+              user: "="
+            },
+            controller: function ($scope, $element, $window) {
+              $scope.initAttribute = function (schema, index) {
+
+                switch (schema.type) {
+                  case "Long":
+                  case "Double":
+                    $scope.user.plainAttrs[schema.key].values[index] = Number($scope.user.plainAttrs[schema.key].values[index])
+                            || undefined;
+                    break;
+                  case "Enum":
+                    $scope.enumerationValues = [];
+                    var enumerationValuesSplitted = schema.enumerationValues.toString().split(";");
+                    for (var i = 0; i < enumerationValuesSplitted.length; i++) {
+                      $scope.enumerationValues.push(enumerationValuesSplitted[i]);
+                    }
+                    $scope.user.plainAttrs[schema.key].values[index] = $scope.user.plainAttrs[schema.key].values[index]
+                            || $scope.enumerationValues[0];
+                    break;
+                  case "Binary":
+
+                    $scope.userFile = $scope.userFile || '';
+                    //for multivalue fields 
+//                    $scope.fileInputId = "fileInputId_" + index;
+
+                    $element.bind("change", function (changeEvent) {
+                      $scope.$apply(function () {
+                        var reader = new FileReader();
+                        var file = changeEvent.target.files[0];
+                        $scope.userFile = file.name;
+                        reader.onload = function (readerEvt) {
+                          var binaryString = readerEvt.target.result;
+                          $scope.user.plainAttrs[schema.key].values[index] = btoa(binaryString);
+                        };
+                        reader.readAsBinaryString(file);
+                      });
+                    });
+
+                    $scope.download = function () {
+                      var byteString = atob($scope.user.plainAttrs[schema.key].values[index]);
+
+                      var ab = new ArrayBuffer(byteString.length);
+                      var ia = new Uint8Array(ab);
+                      for (var i = 0; i < byteString.length; i++) {
+                        ia[i] = byteString.charCodeAt(i);
+                      }
+
+                      var blob = new Blob([ia], {type: schema.mimeType});
+
+                      saveAs(blob, schema.key);
+                    };
+                    $scope.remove = function () {
+                      $scope.user.plainAttrs[schema.key].values.splice(index, 1);
+                      $scope.userFile = '';
+                      $("#fileInput").replaceWith($("#fileInput").clone(true));
+                    };
+                    break;
+                  case "Date":
+
+                    $scope.selectedDate = $scope.user.plainAttrs[schema.key].values[index];
+                    $scope.format = $scope.schema.conversionPattern;
+                    $scope.includeTimezone = false;
+                    if ($scope.schema.conversionPattern.indexOf(".SSS") > -1) {
+                      $scope.format = $scope.format.replace(".SSS", ".sss");
+                    }
+                    if ($scope.schema.conversionPattern.indexOf("Z") > -1) {
+                      $scope.includeTimezone = true;
+                      $scope.format = $scope.format.replace("Z", "");
+                    }
+                    if ($scope.schema.conversionPattern.indexOf("\'") > -1) {
+                      $scope.format = $scope.format.replace(new RegExp("\'", "g"), "");
+                    }
+
+                    $scope.bindDateToModel = function (selectedDate, format) {
+                      var newFormat = $scope.includeTimezone ? format.concat(" Z") : format;
+                      if (selectedDate) {
+                        selectedDate = $filter('date')(selectedDate, newFormat);
+                        var dateGood = selectedDate.toString();
+                        $scope.user.plainAttrs[schema.key].values[index] = dateGood;
+                      } else {
+                        $scope.user.plainAttrs[schema.key].values[index] = selectedDate;
+                      }
+                    };
+
+                    $scope.clear = function () {
+                      $scope.user.plainAttrs[schema.key].values[index] = null;
+                    };
+
+                    // Disable weekend selection
+                    $scope.disabled = function (date, mode) {
+                      // example if you want to disable weekends
+                      // return (mode === 'day' && (date.getDay() === 0 || date.getDay() === 6));
+                      return false;
+                    };
+
+                    $scope.toggleMin = function () {
+                      $scope.minDate = $scope.minDate ? null : new Date();
+                    };
+
+                    $scope.maxDate = new Date(2050, 5, 22);
+
+                    $scope.open = function ($event) {
+                      $scope.status.opened = true;
+                    };
+
+                    $scope.setDate = function (year, month, day) {
+                      $scope.user.plainAttrs[schema.key].values[index] = new Date(year, month, day);
+                    };
+
+                    $scope.dateOptions = {
+                      startingDay: 1
+                    };
+
+                    $scope.status = {
+                      opened: false
+                    };
+
+                    var tomorrow = new Date();
+                    tomorrow.setDate(tomorrow.getDate() + 1);
+                    var afterTomorrow = new Date();
+                    afterTomorrow.setDate(tomorrow.getDate() + 2);
+                    $scope.events =
+                            [
+                              {
+                                date: tomorrow,
+                                status: 'full'
+                              },
+                              {
+                                date: afterTomorrow,
+                                status: 'partially'
+                              }
+                            ];
+
+                    $scope.getDayClass = function (date, mode) {
+                      if (mode === 'day') {
+                        var dayToCheck = new Date(date).setHours(0, 0, 0, 0);
+
+                        for (var i = 0; i < $scope.events.length; i++) {
+                          var currentDay = new Date($scope.events[i].date).setHours(0, 0, 0, 0);
+
+                          if (dayToCheck === currentDay) {
+                            return $scope.events[i].status;
+                          }
+                        }
+                      }
+
+                    };
+                    break;
+
+                  case "Boolean":
+                    $scope.user.plainAttrs[schema.key].values[index] =
+                            Boolean($scope.user.plainAttrs[schema.key].values[index]) || false;
+                    break;
+
+                }
+              }
+              ;
+            },
+            replace: true
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
new file mode 100644
index 0000000..887b5c6
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
@@ -0,0 +1,52 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+angular.module('self')
+        .directive('dynamicDerivedAttributes', function () {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/dynamicDerivedAttributes.html',
+            scope: {
+              dynamicForm: "=form",
+              user: "="
+            },
+            controller: function ($scope) {
+
+              $scope.addDerivedAttribute = function (item, model) {
+                var derSchemaKey = item.key;
+                console.log("ADDING DERIVED item: ", derSchemaKey);
+                $scope.user.derAttrs[derSchemaKey] = {
+                  schema: derSchemaKey,
+                  values: [],
+                  readonly: false
+                };
+
+              };
+
+              $scope.removeDerivedAttribute = function (item, model) {
+                var derSchemaKey = item.key;
+                console.log("REMOVING DERIVED item: ", derSchemaKey);
+                delete $scope.user.derAttrs[derSchemaKey];
+                
+              };
+
+            }
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
new file mode 100644
index 0000000..1a0a4c3
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
@@ -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.
+ */
+'use strict';
+
+angular.module('self')
+        .directive('dynamicPlainAttributes', function (SchemaService) {
+            return {
+              restrict: 'E',
+              templateUrl: 'views/dynamicPlainAttributes.html',
+              scope: {
+                dynamicForm: "=form",
+                user: "="
+              },
+              controller: function ($scope) {
+
+                $scope.addAttributeField = function (plainSchemaKey) {
+                  console.log("ADDING: ", plainSchemaKey + "_" + ($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
+                  $scope.dynamicForm.attributeTable[plainSchemaKey].fields.push(plainSchemaKey + "_" + ($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
+                };
+
+                $scope.removeAttributeField = function (plainSchemaKey, index) {
+                  console.log("REMOVING FROM: " + plainSchemaKey + " ATTRIBUTE INDEX: " + index);
+                  $scope.dynamicForm.attributeTable[plainSchemaKey].fields.splice(index, 1);
+                  // clean user model
+                  $scope.user.plainAttrs[plainSchemaKey].values.splice(index, 1);
+                };
+              }
+            };
+          });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
new file mode 100644
index 0000000..62c1591
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
@@ -0,0 +1,52 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+angular.module('self')
+        .directive('dynamicVirtualAttributes', function () {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/dynamicVirtualAttributes.html',
+            scope: {
+              dynamicForm: "=form",
+              user: "="
+            },
+            controller: function ($scope) {
+
+              $scope.addVirtualAttribute = function (item, model) {
+                var virSchemaKey = item.key;
+                console.log("ADDING VIRTUAL item: ", virSchemaKey);
+                $scope.user.virAttrs[virSchemaKey] = {
+                  schema: virSchemaKey,
+                  values: [],
+                  readonly: false
+                };
+
+              };
+
+              $scope.removeVirtualAttribute = function (item, model) {
+                var virSchemaKey = item.key;
+                console.log("REMOVING VIRTUAL item: ", virSchemaKey);
+                delete $scope.user.virAttrs[virSchemaKey];
+                
+              };
+
+            }
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/equals.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/equals.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/equals.js
new file mode 100644
index 0000000..54c2022
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/equals.js
@@ -0,0 +1,49 @@
+/* 
+ * 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.
+ */
+
+angular.module('self')
+        .directive('equals', function () {
+          return {
+            restrict: 'A',
+            require: '?ngModel',
+            link: function (scope, elem, attrs, ngModel) {
+              if (!ngModel)
+                return; // do nothing if no ng-model
+
+              // watch own value and re-validate on change
+              scope.$watch(attrs.ngModel, function () {
+                validate();
+              });
+
+              // observe the other value and re-validate on change
+              attrs.$observe('equals', function (val) {
+                validate();
+              });
+
+              var validate = function () {
+                // values
+                var val1 = ngModel.$viewValue;
+                var val2 = attrs.equals;
+
+                // set validity
+                ngModel.$setValidity('equals', !val1 || !val2 || val1 === val2);
+              };
+            }
+          };
+        });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/loader.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/loader.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/loader.js
new file mode 100644
index 0000000..603fb34
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/loader.js
@@ -0,0 +1,32 @@
+/* 
+ * 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('SyncopeEnduserApp')
+        .directive("loader", function ($rootScope) {
+          return function ($scope, element, attrs) {
+            $scope.$on("loader_show", function () {
+              return element.show();
+            });
+            return $scope.$on("loader_hide", function () {
+              return element.hide();
+            });
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
new file mode 100644
index 0000000..ff3eebf
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
@@ -0,0 +1,31 @@
+/* 
+ * 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('navigationButtons', function () {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/navigationButtons.html',
+            scope: {
+              next: "@",
+              previous: "@"
+            }
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/directives/passwordStrengthEstimator.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/passwordStrengthEstimator.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/passwordStrengthEstimator.js
new file mode 100644
index 0000000..4bf52b2
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/passwordStrengthEstimator.js
@@ -0,0 +1,102 @@
+/* 
+ * 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('checkStrength', function () {
+          return {
+            replace: false,
+            restrict: 'EACM',
+            link: function (scope, iElement, iAttrs) {
+
+              var strength = {
+                colors: ['#F00', '#F90', '#FF0', '#9F0', '#0F0'],
+                mesureStrength: function (p) {
+
+                  var _force = 0;
+                  var _regex = /[$-/:-?{-~!"^_`\[\]]/g;
+
+                  var _lowerLetters = /[a-z]+/.test(p);
+                  var _upperLetters = /[A-Z]+/.test(p);
+                  var _numbers = /[0-9]+/.test(p);
+                  var _symbols = _regex.test(p);
+
+                  var _flags = [_lowerLetters, _upperLetters, _numbers, _symbols];
+                  var _passedMatches = $.grep(_flags, function (el) {
+                    return el === true;
+                  }).length;
+
+                  _force += 2 * p.length + ((p.length >= 10) ? 1 : 0);
+                  _force += _passedMatches * 10;
+
+                  // penality (short password)
+                  _force = (p.length <= 6) ? Math.min(_force, 10) : _force;
+
+                  // penality (poor variety of characters)
+                  _force = (_passedMatches == 1) ? Math.min(_force, 10) : _force;
+                  _force = (_passedMatches == 2) ? Math.min(_force, 20) : _force;
+                  _force = (_passedMatches == 3) ? Math.min(_force, 40) : _force;
+
+                  return _force;
+
+                },
+                getColor: function (s) {
+
+                  var idx = 0;
+                  if (s <= 10) {
+                    idx = 0;
+                  }
+                  else if (s <= 20) {
+                    idx = 1;
+                  }
+                  else if (s <= 30) {
+                    idx = 2;
+                  }
+                  else if (s <= 40) {
+                    idx = 3;
+                  }
+                  else {
+                    idx = 4;
+                  }
+
+                  return {idx: idx + 1, col: this.colors[idx]};
+
+                }
+              };
+
+              scope.$watch(iAttrs.checkStrength, function () {
+                if (scope.pw === '') {
+                  iElement.css({"display": "none"});
+                } else {
+                  var strength = strength.mesureStrength(scope.pw);
+                  var c = strength.getColor(strength);
+                  iElement.css({"display": "inline"});
+                  iElement.children('li')
+                          .css({"background": "#DDD"})
+                          .slice(0, c.idx)
+                          .css({"background": c.col});
+                }
+              });
+
+            },
+            template: ''
+          };
+        });
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/filters/propsFilter.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/filters/propsFilter.js b/client/enduser/src/main/resources/META-INF/resources/app/js/filters/propsFilter.js
new file mode 100644
index 0000000..a092d0c
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/filters/propsFilter.js
@@ -0,0 +1,52 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+'use strict'
+
+angular.module("self")
+        .filter('propsFilter', function () {
+          return function (items, props) {
+            var out = [];
+
+            if (angular.isArray(items)) {
+              items.forEach(function (item) {
+                var itemMatches = false;
+
+                var keys = Object.keys(props);
+                for (var i = 0; i < keys.length; i++) {
+                  var prop = keys[i];
+                  var text = props[prop].toLowerCase();
+                  if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
+                    itemMatches = true;
+                    break;
+                  }
+                }
+
+                if (itemMatches) {
+                  out.push(item);
+                }
+              });
+            } else {
+              // Let the output be the input untouched
+              out = items;
+            }
+
+            return out;
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/services/authService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/authService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/authService.js
new file mode 100644
index 0000000..3c3f7af
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/authService.js
@@ -0,0 +1,74 @@
+/* 
+ * 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('login')
+        .factory('AuthService', ['$rootScope', '$resource', '$q', '$http', '$cookies',
+          function ($rootScope, $resource, $q, $http, $cookies) {
+
+            var authService = {};
+
+            var clearUserCookie = function () {
+              $rootScope.currentUser = null;
+              $cookies.remove('currentUser');
+            };
+
+            authService.login = function (credentials) {
+              return $http
+                      .post('/syncope-enduser/api/login', credentials)
+                      .then(function (response) {
+                        var username = response.data;
+                        $cookies.put('currentUser', username);
+                        $rootScope.currentUser = username;
+                        return username;
+                      }, function (response) {
+                        clearUserCookie();
+                        console.log("Something went wrong during login, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            authService.logout = function () {
+              return $http
+                      .get('/syncope-enduser/api/logout')
+                      .then(function (response) {
+                        clearUserCookie();
+                        return response;
+                      }, function (response) {
+                        clearUserCookie();
+                        console.log("Something went wrong during logout, exit with status: ", response);
+                      });
+            };
+
+            return authService;
+//            return {
+//              login: $resource('/syncope-enduser/api/login', {}, {
+//                do: {method: 'POST', params: {}, isArray: false}
+//              })
+//            };
+//            return {
+//              logout: $resource('/cradleDashboard/api/logout', {}, {
+//                query: {method: 'GET', params: {}, isArray: false}
+//              })
+//            };
+
+          }]);
+
+

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

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..be9f510
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.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('SchemaService', ['$resource', '$q', '$http',
+          function ($resource, $q, $http) {
+
+            var schemaService = {};
+
+            schemaService.getUserSchemas = function () {
+
+              return  $http.get("/syncope-enduser/api/schemas")
+                      .then(function (response) {
+                        console.log("schemaAPI response: ", response);
+                        return response.data;
+                      }, function (response) {
+                        console.log("Something went wrong during schema retrieval, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+            return schemaService;
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/services/securityQuestionService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/securityQuestionService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/securityQuestionService.js
new file mode 100644
index 0000000..ff91f18
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/securityQuestionService.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('SecurityQuestionService', ['$resource', '$q', '$http',
+          function ($resource, $q, $http) {
+
+            var securityQuestionService = {};
+
+            securityQuestionService.getAvailableSecurityQuestions = function () {
+              return  $http.get("/syncope-enduser/api/securityQuestions")
+                      .then(function (response) {
+                        console.log("security questions response: ", response);
+                        return response.data;
+                      }, function (response) {
+                        console.log("Something went wrong during security questions retrieval, exit with status: ",
+                                response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            return securityQuestionService;
+          }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/js/services/userSelfService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/userSelfService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userSelfService.js
new file mode 100644
index 0000000..3a99e7f
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userSelfService.js
@@ -0,0 +1,69 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+'use strict';
+
+angular.module('login')
+        .factory('UserSelfService', ['$resource', '$q', '$http',
+          function ($resource, $q, $http) {
+
+            var userSelfService = {};
+
+            userSelfService.read = function () {
+              return $http
+                      .get('/syncope-enduser/api/self/read')
+                      .then(function (response) {
+                        console.log("response read: ", response.data);
+                        return response.data;
+                      }, function (response) {
+                        console.log("Something went wrong during user self read, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            userSelfService.create = function (user) {
+              return $http
+                      .post('/syncope-enduser/api/self/create', user)
+                      .then(function (response) {
+                        console.log("response save: ", response)
+                        var username = response;
+                      }, function (response) {
+                        console.log("Something went wrong during user self creation, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            userSelfService.update = function (user) {
+              return $http
+                      .post('/syncope-enduser/api/self/update', user)
+                      .then(function (response) {
+                        var username = response;
+                      }, function (response) {
+                        console.log("Something went wrong during user self update, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            userSelfService.passwordReset = function () {
+            };
+
+            return userSelfService;
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicAttribute.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicAttribute.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicAttribute.html
new file mode 100644
index 0000000..9c6b1d9
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicAttribute.html
@@ -0,0 +1,58 @@
+<div ng-switch="schema.type" >
+  <input ng-switch-when="String" 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)"/>
+  <input ng-switch-when="Encrypted" 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 ng-switch-when="Boolean">
+    <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"
+         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"
+         ng-init="initAttribute(schema, index)" />
+  <p ng-switch-when="Date" class="input-group" >
+    <input type="text" class="form-control" 
+           uib-datepicker-popup="{{format}}"
+           ng-model="selectedDate"
+           ng-change="bindDateToModel(selectedDate, format)"
+           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>
+
+  <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>
+
+  <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/223a64e2/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
new file mode 100644
index 0000000..9400877
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
@@ -0,0 +1,21 @@
+<ui-select on-select="addDerivedAttribute($item, $model)" on-remove="removeDerivedAttribute($item, $model)" multiple
+           ng-model="dynamicForm.selectedDerSchemas" theme="select2" class="attribute-ui-select">
+  <ui-select-match placeholder="Select derived attribute...">{{$item.key}}</ui-select-match>
+  <ui-select-choices repeat="derSchema in dynamicForm.derSchemas | propsFilter: {key: $select.search}">
+    <div ng-bind-html="derSchema.key | highlight: $select.search"></div>
+    <small>
+      name: {{derSchema.key}}
+      expression: {{derSchema.expression}}
+    </small>
+  </ui-select-choices>
+</ui-select>
+
+<ul class="attribute-virtual-value-container">
+  <li class="attribute-virtual-value-field" ng-repeat="selectedDerSchema in dynamicForm.selectedDerSchemas| filter:q as results">
+    {{selectedDerSchema.key}}
+    <input style="font-weight: normal" class="form-control" type="text" ng-disabled="true" ng-model="user.derAttrs[selectedDerSchema.key].values[0]"/>
+  </li>
+  <li class="attribute-virtual-value-field" ng-if="results.length == 0">
+    <strong>No derived attributes selected...</strong>
+  </li>
+</ul>

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..074abbd
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
@@ -0,0 +1,22 @@
+<div id="attribute" class="form-group" ng-repeat="plainSchema in dynamicForm.plainSchemas">
+  <label for="plainSchema.key">{{plainSchema.key}} <span ng-if="Boolean(plainSchema.mandatoryCondition)">*</span></label>
+  <div ng-if="!plainSchema.multivalue">
+    <dynamic-attribute schema="plainSchema" user="user" index="0"></dynamic-attribute>
+  </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]'>
+      <dynamic-attribute schema="plainSchema" user="user" index="$index"></dynamic-attribute>
+      <span>
+        <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>
+    </div>
+    <span>
+      <button class="btn btn-default btn-sm" type="button" ng-click="addAttributeField(plainSchema.key)">
+        <i class="glyphicon glyphicon-plus" title="Add value"></i>
+      </button>
+    </span>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..897eb2c
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
@@ -0,0 +1,18 @@
+<ui-select on-select="addVirtualAttribute($item, $model)" on-remove="removeVirtualAttribute($item, $model)" multiple 
+           ng-model="dynamicForm.selectedVirSchemas" theme="select2" ng-disabled="false" class="attribute-ui-select">
+  <ui-select-match placeholder="Select virtual attribute...">{{$item.key}}</ui-select-match>
+  <ui-select-choices repeat="virSchema in dynamicForm.virSchemas | propsFilter: {key: $select.search}">
+    <div ng-bind-html="virSchema.key | highlight: $select.search"></div>
+  </ui-select-choices>
+</ui-select>
+
+<ul class="attribute-virtual-value-container">
+  <li class="attribute-virtual-value-field" ng-repeat="selectedVirSchema in dynamicForm.selectedVirSchemas| filter:q as results">
+    {{selectedVirSchema.key}}
+    <input style="font-weight: normal" class="form-control" type="text" ng-disabled="selectedVirSchema.readonly"
+            ng-model="user.virAttrs[selectedVirSchema.key].values[0]"/>
+  </li>
+  <li class="attribute-virtual-value-field" ng-if="results.length == 0">
+    <strong>No virtual attributes selected...</strong>
+  </li>
+</ul>
\ No newline at end of file