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

[39/50] [abbrv] syncope git commit: SYNCOPE-701 first working implementation

SYNCOPE-701 first working implementation


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

Branch: refs/heads/SYNCOPE-156
Commit: 223a64e26deff69a4ed8275d0f1348ca4e130a01
Parents: 764fa2e
Author: Andrea Patricelli <an...@tirasa.net>
Authored: Mon Oct 5 15:12:55 2015 +0200
Committer: Andrea Patricelli <an...@tirasa.net>
Committed: Fri Oct 30 10:26:17 2015 +0100

----------------------------------------------------------------------
 client/enduser/pom.xml                          | 194 +++++++++
 .../enduser/SyncopeEnduserApplication.java      | 157 +++++++
 .../client/enduser/SyncopeEnduserSession.java   | 279 +++++++++++++
 .../client/enduser/adapters/UserTOAdapter.java  |  78 ++++
 .../client/enduser/model/Credentials.java       |  65 +++
 .../client/enduser/model/SchemaResponse.java    |  79 ++++
 .../client/enduser/model/UserTORequest.java     | 174 ++++++++
 .../syncope/client/enduser/pages/HomePage.java  |  35 ++
 .../enduser/resources/AbstractBaseResource.java |  58 +++
 .../client/enduser/resources/ErrorResource.java |  50 +++
 .../client/enduser/resources/LoginResource.java |  84 ++++
 .../enduser/resources/LogoutResource.java       |  43 ++
 .../enduser/resources/SchemaResource.java       | 116 ++++++
 .../resources/SecurityQuestionResource.java     |  73 ++++
 .../resources/UserSelfCreateResource.java       |  97 +++++
 .../enduser/resources/UserSelfReadResource.java |  66 +++
 .../resources/UserSelfUpdateResource.java       |  96 +++++
 .../META-INF/resources/app/css/app.css          |  28 ++
 .../META-INF/resources/app/css/editUser.css     | 253 ++++++++++++
 .../META-INF/resources/app/css/login.css        | 103 +++++
 .../META-INF/resources/app/img/ajax-loader.gif  | Bin 0 -> 1924 bytes
 .../META-INF/resources/app/img/busy.gif         | Bin 0 -> 2834 bytes
 .../META-INF/resources/app/img/favicon.png      | Bin 0 -> 641 bytes
 .../META-INF/resources/app/img/logo-green.png   | Bin 0 -> 12178 bytes
 .../META-INF/resources/app/img/logo.png         | Bin 0 -> 8913 bytes
 .../resources/META-INF/resources/app/index.html | 116 ++++++
 .../resources/META-INF/resources/app/js/app.js  | 283 +++++++++++++
 .../app/js/controllers/HomeController.js        |  39 ++
 .../app/js/controllers/LanguageController.js    |  66 +++
 .../app/js/controllers/LoginController.js       |  93 +++++
 .../app/js/controllers/UserController.js        | 206 +++++++++
 .../app/js/directives/dynamicAttribute.js       | 190 +++++++++
 .../js/directives/dynamicDerivedAttributes.js   |  52 +++
 .../app/js/directives/dynamicPlainAttributes.js |  45 ++
 .../js/directives/dynamicVirtualAttributes.js   |  52 +++
 .../resources/app/js/directives/equals.js       |  49 +++
 .../resources/app/js/directives/loader.js       |  32 ++
 .../app/js/directives/navigationButtons.js      |  31 ++
 .../js/directives/passwordStrengthEstimator.js  | 102 +++++
 .../resources/app/js/filters/propsFilter.js     |  52 +++
 .../resources/app/js/services/authService.js    |  74 ++++
 .../resources/app/js/services/realmService.js   |  47 +++
 .../resources/app/js/services/schemaService.js  |  42 ++
 .../app/js/services/securityQuestionService.js  |  41 ++
 .../app/js/services/userSelfService.js          |  69 ++++
 .../resources/app/views/dynamicAttribute.html   |  58 +++
 .../app/views/dynamicDerivedAttributes.html     |  21 +
 .../app/views/dynamicPlainAttributes.html       |  22 +
 .../app/views/dynamicVirtualAttributes.html     |  18 +
 .../META-INF/resources/app/views/editUser.html  |  73 ++++
 .../resources/app/views/generic-error.html      |  24 ++
 .../META-INF/resources/app/views/home.html      |  34 ++
 .../resources/app/views/navigationButtons.html  |   8 +
 .../META-INF/resources/app/views/self.html      | 131 ++++++
 .../resources/app/views/user-credentials.html   |  60 +++
 .../app/views/user-derived-schemas.html         |  37 ++
 .../resources/app/views/user-groups.html        |  37 ++
 .../resources/app/views/user-plain-schemas.html |  37 ++
 .../resources/app/views/user-resources.html     |  28 ++
 .../app/views/user-virtual-schemas.html         |  37 ++
 .../main/resources/META-INF/web-fragment.xml    |  72 ++++
 .../src/main/resources/enduser.properties       |  30 ++
 .../syncope/client/enduser/pages/HomePage.html  |  22 +
 .../enduser/SyncopeEnduserApplicationTest.java  |  69 ++++
 client/pom.xml                                  |   1 +
 .../syncope/common/lib/types/Entitlement.java   |  10 -
 .../syncope/core/logic/AnyTypeClassLogic.java   |   4 +-
 .../apache/syncope/core/logic/AnyTypeLogic.java |   4 +-
 .../apache/syncope/core/logic/SchemaLogic.java  |   2 +-
 .../core/reference/AuthenticationITCase.java    |  11 -
 fit/enduser-reference/pom.xml                   | 413 +++++++++++++++++++
 .../src/main/resources/context.xml              |  23 ++
 .../src/main/resources/enduser.properties       |  30 ++
 .../src/main/resources/log4j2.xml               |  58 +++
 .../src/main/webapp/WEB-INF/glassfish-web.xml   |  25 ++
 .../WEB-INF/jboss-deployment-structure.xml      |  37 ++
 .../src/main/webapp/WEB-INF/weblogic.xml        |  35 ++
 fit/pom.xml                                     |   1 +
 pom.xml                                         |  89 +++-
 79 files changed, 5343 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/client/enduser/pom.xml b/client/enduser/pom.xml
new file mode 100644
index 0000000..a9dc260
--- /dev/null
+++ b/client/enduser/pom.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-client</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+    
+  <name>Apache Syncope Client Enduser</name>
+  <description>Apache Syncope Client Enduser</description>
+  <groupId>org.apache.syncope.client</groupId>
+  <artifactId>syncope-client-enduser</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+  </properties>
+  
+  <dependencies>
+    
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-misc</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency> 
+      <groupId>javax.servlet</groupId> 
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket</artifactId>
+      <type>pom</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-extensions</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-datetime</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-spring</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-auth-roles</artifactId>
+    </dependency>
+    
+    <!--AngularJS-->    
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular-route</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular-resource</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>angular-ui-router</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular-animate</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular-cookies</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>angular-growl-2</artifactId>
+    </dependency>
+    <!--Bootstrap-->
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>font-awesome</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>bootstrap-select</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>bootstrap</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>ionicons</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>angular-ui-bootstrap</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>angular-ui-select</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>angular-sanitize</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>select2</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars.bower</groupId>
+      <artifactId>FileSaver.js</artifactId>
+    </dependency>
+
+    <!--Jquery-->
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jquery</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jquery-cookie</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jquery-ui</artifactId>
+    </dependency>
+    
+    <!--Logging-->
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.syncope.client</groupId>
+      <artifactId>syncope-client-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <!-- TEST -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+    
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+    
+  </build>
+  
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..4acc756
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import org.apache.syncope.client.enduser.pages.HomePage;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import org.apache.syncope.client.enduser.resources.ErrorResource;
+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.UserSelfCreateResource;
+import org.apache.syncope.client.enduser.resources.UserSelfReadResource;
+import org.apache.syncope.client.enduser.resources.UserSelfUpdateResource;
+import org.apache.wicket.Page;
+import org.apache.wicket.Session;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SyncopeEnduserApplication extends WebApplication implements Serializable {
+
+    private static final long serialVersionUID = -6445919351044845120L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserApplication.class);
+
+    public static final List<Locale> SUPPORTED_LOCALES = Collections.unmodifiableList(Arrays.asList(
+            new Locale[] {
+                Locale.ENGLISH, Locale.ITALIAN, new Locale("pt", "BR")
+            }));
+
+    @Override
+    protected void init() {
+        super.init();
+
+        LOG.debug("init SyncopeEnduserApplication");
+
+        // resource to provide login functionality managed by wicket
+        mountResource("/api/login", new ResourceReference("login") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new LoginResource();
+            }
+        });
+
+        // resource to provide logout functionality managed by wicket
+        mountResource("/api/logout", new ResourceReference("logout") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new LogoutResource();
+            }
+        });
+
+        // resource to retrieve info about logged user
+        mountResource("/api/self/read", new ResourceReference("userSelfRead") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new UserSelfReadResource();
+            }
+        });
+
+        // resource to provide user self create functionality managed by wicket
+        mountResource("/api/self/create", new ResourceReference("userSelfCreate") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new UserSelfCreateResource();
+            }
+        });
+
+        // resource to provide user self update functionality managed by wicket
+        mountResource("/api/self/update", new ResourceReference("userSelfUpdate") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new UserSelfUpdateResource();
+            }
+        });
+
+        mountResource("/api/schemas", new ResourceReference("schemas") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new SchemaResource();
+            }
+        });
+        
+        mountResource("/api/securityQuestions", new ResourceReference("securityQuestions") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new SecurityQuestionResource();
+            }
+        });
+
+        mountResource("/api/error", new ResourceReference("error") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new ErrorResource();
+            }
+        });
+    }
+
+    @Override
+    public Class<? extends Page> getHomePage() {
+        return HomePage.class;
+    }
+
+    @Override
+    public Session newSession(final Request request, final Response response) {
+        return new SyncopeEnduserSession(request);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
new file mode 100644
index 0000000..c5abc1d
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -0,0 +1,279 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.SyncopeTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.Session;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.request.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom Syncope Enduser Session class.
+ */
+public class SyncopeEnduserSession extends WebSession {
+
+    private static final long serialVersionUID = 1284946129513378647L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserSession.class);
+
+    public static final List<Locale> SUPPORTED_LOCALES = Arrays.asList(new Locale[] {
+        Locale.ENGLISH, Locale.ITALIAN, new Locale("pt", "BR") });
+
+    protected static final String ENDUSER_PROPERTIES = "enduser.properties";
+
+    private String username;
+
+    private String password;
+
+    private String scheme;
+
+    private String host;
+
+    private String port;
+
+    private String rootPath;
+
+    private String anonymousUser;
+
+    private String anonymousKey;
+
+    private Boolean storePassword;
+
+    private String version;
+
+    private String license;
+
+    private final SyncopeClientFactoryBean clientFactory;
+
+    private SyncopeClient client;
+
+    private final SyncopeClient anonymousClient;
+
+    private final SyncopeTO syncopeTO;
+
+    private UserTO selfTO;
+
+    private final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<Class<?>, Object>());
+
+    public static SyncopeEnduserSession get() {
+        return (SyncopeEnduserSession) Session.get();
+    }
+
+    public SyncopeEnduserSession(final Request request) {
+        super(request);
+
+        // load properties from classpath file
+        loadProperties();
+
+        clientFactory = new SyncopeClientFactoryBean();
+        clientFactory.setAddress(new StringBuilder(scheme)
+                .append("://")
+                .append(host)
+                .append(":")
+                .append(port)
+                .append("/")
+                .append(rootPath)
+                .toString());
+        clientFactory.setContentType(SyncopeClientFactoryBean.ContentType.JSON);
+
+        anonymousClient = clientFactory.create(anonymousUser, anonymousKey);
+        syncopeTO = anonymousClient.getService(SyncopeService.class).info();
+
+    }
+
+    public boolean authenticate(final String username, final String password) {
+        boolean authenticated = false;
+
+        try {
+            client = clientFactory.setDomain(SyncopeConstants.MASTER_DOMAIN).create(username, password);
+
+            Pair<Map<String, Set<String>>, UserTO> self = client.self();
+            selfTO = self.getValue();
+
+            this.username = username;
+            this.password = password;
+            // bind explicitly this session to have a stateful behavior during http requests, unless session will expire
+            // for every  request
+            this.bind();
+            authenticated = true;
+        } catch (Exception e) {
+            LOG.error("Authentication failed", e);
+        }
+
+        return authenticated;
+    }
+
+    public <T> void resetClient(final Class<T> service) {
+        T serviceInstance = getCachedService(service);
+        WebClient.client(serviceInstance).reset();
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T getCachedService(final Class<T> serviceClass) {
+        T service;
+        if (services.containsKey(serviceClass)) {
+            service = (T) services.get(serviceClass);
+        } else {
+            service = client == null ? anonymousClient.getService(serviceClass) : client.getService(serviceClass);
+            services.put(serviceClass, service);
+        }
+
+        return service;
+    }
+
+    public <T> T getService(final Class<T> serviceClass) {
+        return getCachedService(serviceClass);
+    }
+
+    public <T> T getService(final String etag, final Class<T> serviceClass) {
+        T serviceInstance = getCachedService(serviceClass);
+        WebClient.client(serviceInstance).match(new EntityTag(etag), false).
+                type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
+
+        return serviceInstance;
+    }
+
+    public <T> T getService(final MediaType mediaType, final Class<T> serviceClass) {
+        T service;
+
+        synchronized (clientFactory) {
+            SyncopeClientFactoryBean.ContentType preType = clientFactory.getContentType();
+
+            clientFactory.setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString()));
+            service = clientFactory.create(username, password).getService(serviceClass);
+            clientFactory.setContentType(preType);
+        }
+
+        return service;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getScheme() {
+        return scheme;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public String getRootPath() {
+        return rootPath;
+    }
+
+    public String getAnonymousUser() {
+        return anonymousUser;
+    }
+
+    public String getAnonymousKey() {
+        return anonymousKey;
+    }
+
+    public Boolean storePassword() {
+        return this.storePassword;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public SyncopeTO getSyncopeTO() {
+        return syncopeTO;
+    }
+
+    public UserTO getSelfTO() {
+        return selfTO;
+    }
+
+    public boolean isAuthenticated() {
+        return getUsername() != null;
+    }
+
+    public DateFormat getDateFormat() {
+        final Locale locale = getLocale() == null ? Locale.ENGLISH : getLocale();
+
+        return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
+    }
+
+    private void loadProperties() {
+        Properties properties = new Properties();
+
+        try {
+            properties.load(getClass().getResourceAsStream("/" + ENDUSER_PROPERTIES));
+            File enduserDir = new File(properties.getProperty("enduser.directory"));
+            if (enduserDir.exists() && enduserDir.canRead() && enduserDir.isDirectory()) {
+                File enduserDirProps = FileUtils.getFile(enduserDir, ENDUSER_PROPERTIES);
+                if (enduserDirProps.exists() && enduserDirProps.canRead() && enduserDirProps.isFile()) {
+                    properties.clear();
+                    properties.load(FileUtils.openInputStream(enduserDir));
+                }
+            }
+        } catch (Exception e) {
+            LOG.error("Error loading {} file", ENDUSER_PROPERTIES, e);
+//            throw new WicketRuntimeException("Could not read " + ENDUSER_PROPERTIES, e);
+        }
+
+        this.scheme = properties.getProperty("scheme");
+        this.host = properties.getProperty("host");
+        this.port = properties.getProperty("port");
+        this.rootPath = properties.getProperty("rootPath");
+        this.anonymousUser = properties.getProperty("anonymousUser");
+        this.anonymousKey = properties.getProperty("anonymousKey");
+        this.storePassword = Boolean.valueOf(properties.getProperty("storePassword"));
+        version = properties.getProperty("version");
+        license = properties.getProperty("license");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..551555f
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/UserTOAdapter.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.adapters;
+
+import org.apache.syncope.client.enduser.model.UserTORequest;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserTOAdapter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserTOAdapter.class);
+
+    public UserTO fromUserTORequest(final UserTORequest userTORequest, final String oldSelfPassword) {
+
+        // adapter code, to be moved in a new utility class
+        final UserTO userTO = new UserTO();
+        // set key if in update mode
+        final Long key = userTORequest.getKey();
+        if (key != null) {
+            userTO.setKey(key);
+        }
+        // set username...
+        userTO.setUsername(userTORequest.getUsername());
+        // ...and password
+        String requestPassword = userTORequest.getPassword();
+        if (requestPassword == null || requestPassword.isEmpty()) {
+            userTO.setPassword(oldSelfPassword == null ? null : oldSelfPassword);
+        } else {
+            userTO.setPassword(requestPassword);
+        }
+
+        //set security question and answer
+        userTO.setSecurityQuestion(userTORequest.getSecurityQuestion());
+        userTO.setSecurityAnswer(userTORequest.getSecurityAnswer());
+        //set realm
+        userTO.setRealm(userTORequest.getRealm());
+        // add attributes
+        userTO.getPlainAttrs().addAll(userTORequest.getPlainAttrs().values());
+        userTO.getDerAttrs().addAll(userTORequest.getDerAttrs().values());
+        userTO.getVirAttrs().addAll(userTORequest.getVirAttrs().values());
+
+        return userTO;
+    }
+
+    public UserTORequest toUserTORequest(final UserTO userTO) {
+
+        final UserTORequest userTORequest = new UserTORequest().
+                key(userTO.getKey()).
+                username(userTO.getUsername()).
+                securityQuestion(userTO.getSecurityQuestion()).
+                securityAnswer(userTO.getSecurityAnswer()).
+                realm(userTO.getRealm());
+
+        userTORequest.getPlainAttrs().putAll(userTO.getPlainAttrMap());
+        userTORequest.getDerAttrs().putAll(userTO.getDerAttrMap());
+        userTORequest.getVirAttrs().putAll(userTO.getVirAttrMap());
+
+        return userTORequest;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/Credentials.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/Credentials.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/Credentials.java
new file mode 100644
index 0000000..68e3106
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/Credentials.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Credentials {
+
+    private String username;
+
+    private String password;
+
+    public Credentials() {
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(final String password) {
+        this.password = password;
+    }
+
+    public Credentials username(final String username) {
+        this.username = username;
+        return this;
+    }
+
+    public Credentials password(final String password) {
+        this.password = password;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
new file mode 100644
index 0000000..912f287
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+
+public class SchemaResponse implements Serializable {
+
+    private static final long serialVersionUID = -8896862106241712829L;
+
+    private List<PlainSchemaTO> plainSchemas = new ArrayList<>();
+
+    private List<DerSchemaTO> derSchemas = new ArrayList<>();
+
+    private List<VirSchemaTO> virSchemas = new ArrayList<>();
+
+    public SchemaResponse() {
+    }
+
+    public List<PlainSchemaTO> getPlainSchemas() {
+        return plainSchemas;
+    }
+
+    public void setPlainSchemas(final List<PlainSchemaTO> plainSchemas) {
+        this.plainSchemas = plainSchemas;
+    }
+
+    public List<DerSchemaTO> getDerSchemas() {
+        return derSchemas;
+    }
+
+    public void setDerSchemas(final List<DerSchemaTO> derSchemas) {
+        this.derSchemas = derSchemas;
+    }
+
+    public List<VirSchemaTO> getVirSchemas() {
+        return virSchemas;
+    }
+
+    public void setVirSchemas(final List<VirSchemaTO> virSchemas) {
+        this.virSchemas = virSchemas;
+    }
+
+    public SchemaResponse plainSchemas(final List<PlainSchemaTO> value) {
+        this.plainSchemas = value;
+        return this;
+    }
+
+    public SchemaResponse derSchemas(final List<DerSchemaTO> value) {
+        this.derSchemas = value;
+        return this;
+    }
+
+    public SchemaResponse virSchemas(final List<VirSchemaTO> value) {
+        this.virSchemas = value;
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/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
new file mode 100644
index 0000000..09bc219
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/UserTORequest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.syncope.common.lib.to.AttrTO;
+
+public class UserTORequest implements Serializable {
+
+    private static final long serialVersionUID = -6763020920564016374L;
+
+    private Long key;
+
+    private String username;
+
+    private String password;
+
+    private Long securityQuestion;
+
+    private String securityAnswer;
+
+    private String realm;
+
+    private Map<String, AttrTO> plainAttrs = new HashMap<>();
+
+    private Map<String, AttrTO> derAttrs = new HashMap<>();
+
+    private Map<String, AttrTO> virAttrs = new HashMap<>();
+
+    public UserTORequest() {
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+    public void setKey(final Long key) {
+        this.key = key;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(final String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(final String password) {
+        this.password = password;
+    }
+
+    public Long getSecurityQuestion() {
+        return securityQuestion;
+    }
+
+    public void setSecurityQuestion(final Long securityQuestion) {
+        this.securityQuestion = securityQuestion;
+    }
+
+    public String getSecurityAnswer() {
+        return securityAnswer;
+    }
+
+    public void setSecurityAnswer(final String securityAnswer) {
+        this.securityAnswer = securityAnswer;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(final String realm) {
+        this.realm = realm;
+    }
+
+    public Map<String, AttrTO> getPlainAttrs() {
+        return plainAttrs;
+    }
+
+    public void setPlainAttrs(final Map<String, AttrTO> plainAttrs) {
+        this.plainAttrs = plainAttrs;
+    }
+
+    public Map<String, AttrTO> getDerAttrs() {
+        return derAttrs;
+    }
+
+    public void setDerAttrs(final Map<String, AttrTO> derAttrs) {
+        this.derAttrs = derAttrs;
+    }
+
+    public Map<String, AttrTO> getVirAttrs() {
+        return virAttrs;
+    }
+
+    public void setVirAttrs(final Map<String, AttrTO> virAttrs) {
+        this.virAttrs = virAttrs;
+    }
+
+    public UserTORequest key(final Long value) {
+        this.key = value;
+        return this;
+    }
+
+    public UserTORequest username(final String value) {
+        this.username = value;
+        return this;
+    }
+
+    public UserTORequest password(final String value) {
+        this.password = value;
+        return this;
+    }
+
+    public UserTORequest securityQuestion(final Long value) {
+        this.securityQuestion = value;
+        return this;
+    }
+
+    public UserTORequest securityAnswer(final String value) {
+        this.securityAnswer = value;
+        return this;
+    }
+
+    public UserTORequest realm(final String value) {
+        this.realm = value;
+        return this;
+    }
+
+    public UserTORequest plainAttrs(final Map<String, AttrTO> value) {
+        this.plainAttrs = value;
+        return this;
+    }
+
+    public UserTORequest derAttrs(final Map<String, AttrTO> value) {
+        this.derAttrs = value;
+        return this;
+    }
+
+    public UserTORequest virAttrs(final Map<String, AttrTO> value) {
+        this.virAttrs = value;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/pages/HomePage.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/pages/HomePage.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/pages/HomePage.java
new file mode 100644
index 0000000..4c5c07e
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/pages/HomePage.java
@@ -0,0 +1,35 @@
+/*
+ * 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.pages;
+
+import org.apache.wicket.NonResettingRestartException;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class HomePage extends WebPage {
+
+    private static final long serialVersionUID = -3422492668689122688L;
+
+    public HomePage(final PageParameters parameters) {
+        super(parameters);
+//        throw new RedirectToUrlException("/app/");
+        throw new NonResettingRestartException("/app/");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/AbstractBaseResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/AbstractBaseResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/AbstractBaseResource.java
new file mode 100644
index 0000000..a3aedfc
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/AbstractBaseResource.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractBaseResource extends AbstractResource {
+
+    private static final long serialVersionUID = -7875801358718612782L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractBaseResource.class);
+
+    protected <T> T getService(final Class<T> serviceClass) {
+        return SyncopeEnduserSession.get().getService(serviceClass);
+    }
+
+    protected <T> T getService(final String etag, final Class<T> serviceClass) {
+        return SyncopeEnduserSession.get().getService(etag, serviceClass);
+    }
+
+    protected <T> void resetClient(final Class<T> serviceClass) {
+        SyncopeEnduserSession.get().resetClient(serviceClass);
+    }
+
+    protected boolean isSelfRegistrationAllowed() {
+        Boolean result = null;
+        try {
+            result = SyncopeEnduserSession.get().getSyncopeTO().isSelfRegAllowed();
+        } catch (SyncopeClientException e) {
+            LOG.error("While seeking if self registration is allowed", e);
+        }
+
+        return result == null
+                ? false
+                : result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ErrorResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ErrorResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ErrorResource.java
new file mode 100644
index 0000000..bfceeab
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ErrorResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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 javax.ws.rs.core.MediaType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Mirror REST resource for obtaining user self operations.
+ *
+ * @see org.apache.syncope.common.rest.api
+ */
+public class ErrorResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = -9184809392631523912L;
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ErrorResource.class);
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        ResourceResponse response = new ResourceResponse();
+        response.disableCaching();
+        response.setContentType(MediaType.APPLICATION_JSON);
+
+        response.setStatusCode(403);
+
+        return response;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LoginResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LoginResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LoginResource.java
new file mode 100644
index 0000000..fa6fa8c
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LoginResource.java
@@ -0,0 +1,84 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.model.Credentials;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LoginResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = -7720997467070461915L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(LoginResource.class);
+
+    public LoginResource() {
+    }
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        int responseStatus;
+        final String responseMessage;
+        ResourceResponse response = new ResourceResponse();
+
+        try {
+            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
+            Credentials credentials = POJOHelper.deserialize(IOUtils.toString(request.getInputStream()),
+                    Credentials.class);
+            final String username = credentials.getUsername();
+            final String password = credentials.getPassword().isEmpty() ? null : credentials.getPassword();
+
+            LOG.debug("Enduser login, user: {}", username);
+
+            if (StringUtils.isBlank(username)) {
+                LOG.error("Could not read credentials from request: username is blank!");
+                responseMessage = "Could not read credentials from request: username is blank!";
+                responseStatus = 400;
+            } else {
+                // authenticate user
+                final boolean authenticated = SyncopeEnduserSession.get().authenticate(username, password);
+                responseStatus = authenticated ? 200 : 401;
+                responseMessage = username;
+            }
+
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes attributes) throws IOException {
+                    attributes.getResponse().write(responseMessage);
+                }
+            });
+
+        } catch (Exception e) {
+            responseStatus = 400;
+            LOG.error("Could not read credentials from request", e);
+        }
+
+        response.setStatusCode(responseStatus);
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LogoutResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LogoutResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LogoutResource.java
new file mode 100644
index 0000000..545b44d
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/LogoutResource.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LogoutResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = -648841355644985051L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(LogoutResource.class);
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        LOG.debug("Enduser logout");
+        
+        SyncopeEnduserSession.get().invalidate();
+        
+        ResourceResponse response = new ResourceResponse();
+        response.setStatusCode(204);
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
new file mode 100644
index 0000000..544138b
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
@@ -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.
+ */
+package org.apache.syncope.client.enduser.resources;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.client.enduser.model.SchemaResponse;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
+import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
+import org.apache.syncope.common.rest.api.service.AnyTypeService;
+import org.apache.syncope.common.rest.api.service.SchemaService;
+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 SchemaResource extends AbstractBaseResource {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SchemaResource.class);
+
+    private static final long serialVersionUID = 6453101466981543020L;
+
+    private final AnyTypeService anyTypeService;
+
+    private final AnyTypeClassService anyTypeClassService;
+
+    private final SchemaService schemaService;
+
+    public SchemaResource() {
+        anyTypeService = getService(AnyTypeService.class);
+        anyTypeClassService = getService(AnyTypeClassService.class);
+        schemaService = getService(SchemaService.class);
+    }
+
+    @Override
+    protected AbstractResource.ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
+
+        AbstractResource.ResourceResponse response = new AbstractResource.ResourceResponse();
+
+        int responseStatus = 200;
+
+        try {
+
+            final AnyTypeTO anyTypeUserTO = anyTypeService.read(AnyTypeKind.USER.name());
+
+            final List<PlainSchemaTO> plainSchemas = new ArrayList<>();
+            final List<DerSchemaTO> derSchemas = new ArrayList<>();
+            final List<VirSchemaTO> virSchemas = new ArrayList<>();
+
+            // read all USER type schemas
+            for (String clazz : anyTypeUserTO.getClasses()) {
+                plainSchemas.addAll(getSchemaTOs(anyTypeClassService.read(clazz).getPlainSchemas(), SchemaType.PLAIN,
+                        PlainSchemaTO.class));
+                derSchemas.addAll(getSchemaTOs(anyTypeClassService.read(clazz).getDerSchemas(), SchemaType.DERIVED,
+                        DerSchemaTO.class));
+                virSchemas.addAll(getSchemaTOs(anyTypeClassService.read(clazz).getVirSchemas(), SchemaType.VIRTUAL,
+                        VirSchemaTO.class));
+            }
+
+            response.setWriteCallback(new AbstractResource.WriteCallback() {
+
+                @Override
+                public void writeData(final IResource.Attributes attributes) throws IOException {
+                    attributes.getResponse().write(POJOHelper.serialize(new SchemaResponse().
+                            plainSchemas(plainSchemas).
+                            derSchemas(derSchemas).
+                            virSchemas(virSchemas)));
+                }
+            });
+
+        } catch (Exception e) {
+            LOG.error("Error retrieving " + AnyTypeKind.USER.name() + " class schemas", e);
+            responseStatus = 400;
+        }
+
+        response.setStatusCode(responseStatus);
+        return response;
+    }
+
+    private <T extends AbstractSchemaTO> List<T> getSchemaTOs(final List<String> schemaNames,
+            final SchemaType schemaType, final Class<T> type) {
+
+        List<T> schemaTOs = new ArrayList<>();
+
+        for (String schemaName : schemaNames) {
+            schemaTOs.add(type.cast(schemaService.read(schemaType, schemaName)));
+        }
+
+        return schemaTOs;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SecurityQuestionResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SecurityQuestionResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SecurityQuestionResource.java
new file mode 100644
index 0000000..f1ab6c8
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SecurityQuestionResource.java
@@ -0,0 +1,73 @@
+/*
+ * 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 org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
+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 SecurityQuestionResource extends AbstractBaseResource {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SecurityQuestionResource.class);
+
+    private static final long serialVersionUID = 6453101466981543020L;
+
+    private final SecurityQuestionService securityQuestionService;
+
+    public SecurityQuestionResource() {
+        securityQuestionService = getService(SecurityQuestionService.class);
+    }
+
+    @Override
+    protected AbstractResource.ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
+
+        AbstractResource.ResourceResponse response = new AbstractResource.ResourceResponse();
+
+        int responseStatus = 200;
+
+        try {
+
+            LOG.debug("List available security questions");
+
+            final List<SecurityQuestionTO> securityQuestionTOs = securityQuestionService.list();
+
+            response.setWriteCallback(new AbstractResource.WriteCallback() {
+
+                @Override
+                public void writeData(final IResource.Attributes attributes) throws IOException {
+                    attributes.getResponse().write(POJOHelper.serialize(securityQuestionTOs));
+                }
+            });
+
+        } catch (Exception e) {
+            LOG.error("Error retrieving security questions", e);
+            responseStatus = 400;
+        }
+
+        response.setStatusCode(responseStatus);
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
new file mode 100644
index 0000000..61734a7
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
@@ -0,0 +1,97 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.adapters.UserTOAdapter;
+import org.apache.syncope.client.enduser.model.UserTORequest;
+import org.apache.syncope.common.rest.api.service.UserSelfService;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserSelfCreateResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = -2721621682300247583L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserSelfCreateResource.class);
+
+    private final UserSelfService userSelfService;
+
+    private final UserTOAdapter userTOAdapter;
+
+    public UserSelfCreateResource() {
+        userTOAdapter = new UserTOAdapter();
+        userSelfService = getService(UserSelfService.class);
+    }
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        int responseStatus = 200;
+        final StringBuilder responseMessage = new StringBuilder();
+        ResourceResponse response = new ResourceResponse();
+
+        try {
+            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
+
+            final UserTORequest userTORequest = POJOHelper.deserialize(IOUtils.toString(request.getInputStream()),
+                    UserTORequest.class);
+
+            if (isSelfRegistrationAllowed() && userTORequest != null) {
+                LOG.debug("Received user self registration request for user: [{}]", userTORequest.getUsername());
+                LOG.trace("Received user self registration request is: [{}]", userTORequest);
+                // adapt request and create user
+                userSelfService.create(userTOAdapter.fromUserTORequest(userTORequest, null),
+                        SyncopeEnduserSession.get().storePassword());
+                responseMessage.append("User").append(userTORequest.getUsername()).append("created successfully");
+            } else {
+                responseMessage.append(userTORequest == null
+                        ? "Request received is not valid"
+                        : "Self registration not allowed");
+                responseStatus = 403;
+            }
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes attributes) throws IOException {
+                    attributes.getResponse().write(responseMessage);
+                }
+            });
+
+        } catch (final Exception e) {
+            responseStatus = 400;
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes attributes) throws IOException {
+                    attributes.getResponse().write(e.getMessage());
+                }
+            });
+            LOG.error("Could not read userTO from request", e);
+        }
+
+        response.setStatusCode(responseStatus);
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
new file mode 100644
index 0000000..3519e78
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
@@ -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.
+ */
+package org.apache.syncope.client.enduser.resources;
+
+import java.io.IOException;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.adapters.UserTOAdapter;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Mirror REST resource for obtaining user self operations.
+ *
+ * @see org.apache.syncope.common.rest.api
+ */
+public class UserSelfReadResource extends AbstractResource {
+
+    private static final long serialVersionUID = -9184809392631523912L;
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(UserSelfReadResource.class);
+
+    private final UserTOAdapter userTOAdapter;
+
+    public UserSelfReadResource() {
+        userTOAdapter = new UserTOAdapter();
+    }
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        ResourceResponse response = new ResourceResponse();
+        final String selfTOJson = POJOHelper.serialize(userTOAdapter.toUserTORequest(SyncopeEnduserSession.get().
+                getSelfTO()));
+
+        response.setWriteCallback(new WriteCallback() {
+
+            @Override
+            public void writeData(final Attributes attributes) throws IOException {
+                attributes.getResponse().write(selfTOJson);
+            }
+        });
+
+        return response;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
new file mode 100644
index 0000000..5fc9c82
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
@@ -0,0 +1,96 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.adapters.UserTOAdapter;
+import org.apache.syncope.client.enduser.model.UserTORequest;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.service.UserSelfService;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserSelfUpdateResource extends AbstractBaseResource {
+
+    private static final long serialVersionUID = -2721621682300247583L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserSelfUpdateResource.class);
+
+    private final UserSelfService userSelfService;
+
+    private final UserTOAdapter userTOAdapter;
+
+    public UserSelfUpdateResource() {
+        userTOAdapter = new UserTOAdapter();
+        userSelfService = getService(UserSelfService.class);
+    }
+
+    @Override
+    protected ResourceResponse newResourceResponse(final Attributes attributes) {
+
+        int responseStatus = 200;
+        final String responseMessage;
+        ResourceResponse response = new ResourceResponse();
+
+        try {
+            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
+
+            final UserTORequest userTOResponse = POJOHelper.deserialize(IOUtils.toString(request.getInputStream()),
+                    UserTORequest.class);
+
+            LOG.debug("userTOResponse: {}", userTOResponse);
+
+            // adapt user, change self password only value passed is not null and has changed
+            UserTO userTO = userTOAdapter.fromUserTORequest(userTOResponse, SyncopeEnduserSession.get().getPassword());
+
+            LOG.debug("Enduser user self update, user: {}", userTO.toString());
+
+            // update user
+            userSelfService.update(userTO);
+            responseMessage = "User updated successfully";
+
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes attributes) throws IOException {
+                    attributes.getResponse().write(responseMessage);
+                }
+            });
+
+        } catch (final Exception e) {
+            responseStatus = 400;
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final Attributes attributes) throws IOException {
+                    attributes.getResponse().write(e.getMessage());
+                }
+            });
+            LOG.error("Could not read userTO from request", e);
+        }
+
+        response.setStatusCode(responseStatus);
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/app.css b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
new file mode 100644
index 0000000..e5ae8e5
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
@@ -0,0 +1,28 @@
+/*
+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.
+*/
+
+/* app general css stylesheet */
+
+.growl-container > .growl-item.ng-enter,
+.growl-container > .growl-item.ng-leave {
+  -webkit-transition:1s linear all;
+  -moz-transition:1s linear all;
+  -o-transition:1s linear all;
+  transition:1s linear all;
+}
\ 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/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
new file mode 100644
index 0000000..ee13bd0
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
@@ -0,0 +1,253 @@
+/*
+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.
+*/
+
+#form-container {
+  position: relative;
+  width: 100%;
+  margin: 0 auto;
+  text-align: center;
+}
+
+#form-container .page-header   { background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+                                 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+                                 background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
+                                 background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 11.10+ */
+                                 background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */ 
+                                 margin: 1% 16%;
+                                 width: 72%; padding:10px; 
+                                 /* shadows and rounded borders */
+                                 -moz-border-radius: 5px;
+                                 -webkit-border-radius: 5px;
+                                 border-radius: 5px;
+                                 -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+                                 -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+                                 box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+}
+#form-container .breadcrumb-header   { 
+  margin: 1% 16%;
+  width: 72%; padding:10px; 
+}
+
+.signup-form {
+  text-align: left;
+  padding: 2%;
+}
+
+#attribute {
+  padding: 0 255px;
+}
+
+#attribute-derived,
+#attribute-virtual {
+  padding: 0 155px;
+}
+
+.attribute-virtual-value-container {
+  margin-top: 2%;
+  list-style: none;
+  padding-right: 5%;
+}
+
+.attribute-virtual-value-field {
+  font-weight: 700;
+  padding: 6px 12px;
+  margin-bottom: 2%;
+}
+
+.minus{
+  margin-bottom: 2%;
+}
+
+.fileButton{
+  margin-top: 2%;
+}
+
+.upper-select {
+  padding-right: 71%;
+  padding-left: 18%;
+}
+
+.attribute-ui-select {
+  width: 100%;
+  padding-right: 7%;
+  padding-left: 7%;
+}
+
+#previous {
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */
+  float: left;
+  color: black;
+  width: 30%;
+}
+#previous:hover {
+  background: #658D5D;
+}
+
+#next{
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */
+  margin-top: 5px;
+  float: right;
+  color: black;
+  width: 30%;
+}
+#next:hover {
+  background: #658D5D;
+}
+
+#save{
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */
+  color: black;
+  margin-top: 5%;
+  width: 15%;
+}
+#save:hover {
+  background: #658D5D;
+}
+#cancel {
+  margin-top: 5%;
+  width: 15%;
+}
+
+#form-views             { width:auto; }
+
+/* basic styling for entering and leaving */
+/* left and right to add to ensure full width: position:absolute; left:30px; right:30px; */
+#form-views.ng-enter,
+#form-views.ng-leave      { 
+  transition:0.5s all ease; -moz-transition:0.5s all ease; -webkit-transition:0.5s all ease; 
+}
+
+/* enter animation */
+#form-views.ng-enter            { 
+  -webkit-animation:slideInRight 0.5s both ease;
+  -moz-animation:slideInRight 0.5s both ease;
+  animation:slideInRight 0.5s both ease; 
+}
+
+/* leave animation */
+#form-views.ng-leave            { 
+  -webkit-animation:slideOutLeft 0.5s both ease;
+  -moz-animation:slideOutLeft 0.5s both ease;
+  animation:slideOutLeft 0.5s both ease;   
+}
+
+/** Button breadcrumb **/
+.btn-breadcrumb .btn:not(:last-child):after {
+  content: " ";
+  display: block;
+  width: 0;
+  height: 0;
+  border-top: 17px solid transparent;
+  border-bottom: 17px solid transparent;
+  border-left: 10px solid white;
+  position: absolute;
+  top: 50%;
+  margin-top: -17px;
+  left: 100%;
+  z-index: 3;
+}
+.btn-breadcrumb .btn:not(:last-child):before {
+  content: " ";
+  display: block;
+  width: 0;
+  height: 0;
+  border-top: 17px solid transparent;
+  border-bottom: 17px solid transparent;
+  border-left: 10px solid rgb(173, 173, 173);
+  position: absolute;
+  top: 50%;
+  margin-top: -17px;
+  margin-left: 1px;
+  left: 100%;
+  z-index: 3;
+}
+
+/** The Spacing **/
+.btn-breadcrumb .btn {
+  padding:6px 12px 6px 24px;
+}
+.btn-breadcrumb .btn:first-child {
+  padding:6px 6px 6px 10px;
+}
+.btn-breadcrumb .btn:last-child {
+  padding:6px 18px 6px 24px;
+}
+
+/** Default button **/
+.btn-breadcrumb .btn.btn-default:not(:last-child):after {
+  border-left: 10px solid #fff;
+}
+.btn-breadcrumb .btn.btn-default:not(:last-child):before {
+  border-left: 10px solid #ccc;
+}
+.btn-breadcrumb .btn.btn-default:hover:not(:last-child):after {
+  border-left: 10px solid #ebebeb;
+}
+.btn-breadcrumb .btn.btn-default:hover:not(:last-child):before {
+  border-left: 10px solid #adadad;
+}
+
+.breadcrumb-disabled-link {
+  pointer-events: none;
+  cursor: default;
+}
+
+.text-validation-error{
+    color: #dd301b;
+    font-weight: 600;
+}
+/* ANIMATIONS
+============================================================================= */
+/* slide out to the left */
+@keyframes slideOutLeft {
+  to      { transform: translateX(-200%); }
+}
+@-moz-keyframes slideOutLeft {  
+  to      { -moz-transform: translateX(-200%); }
+}
+@-webkit-keyframes slideOutLeft {
+  to      { -webkit-transform: translateX(-200%); }
+}
+
+/* slide in from the right */
+@keyframes slideInRight {
+  from    { transform:translateX(200%); }
+  to      { transform: translateX(0); }
+}
+@-moz-keyframes slideInRight {
+  from    { -moz-transform:translateX(200%); }
+  to      { -moz-transform: translateX(0); }
+}
+@-webkit-keyframes slideInRight {
+  from    { -webkit-transform:translateX(200%); }
+  to      { -webkit-transform: translateX(0); }
+}
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/223a64e2/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/login.css b/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
new file mode 100644
index 0000000..fdc9e3e
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+body, html {
+  margin: 45px 0;
+  height: 100%;
+  background-repeat: no-repeat;
+  /*background-image: linear-gradient(rgb(104, 145, 162), #00a65a);*/
+  background-color: #EEEEEE;
+}
+#login-container {
+  position: relative;
+  width: 100%;
+  margin: 0 auto;
+  text-align: center;
+  background-color: #FFF;
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+  -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.65);
+  box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.65);
+}
+#login-container .row > div {
+  margin-bottom: 1em;
+}
+#login-container .row > div:last-child {
+  margin-bottom: 0;
+}
+#login-container #logo {
+  position: relative;
+  float: left;
+  margin-left: 15%;
+  border-top-right-radius: 100px;
+  border-top-left-radius: 100px;
+  border-bottom-right-radius: 100px;
+  border-bottom-left-radius: 100px;
+}
+
+#login-container #language{
+  padding: 0px;
+  height: 40px;
+}
+
+#login-container #signup-btn {
+  padding-top: 15px;
+  padding-bottom: 15px;
+}
+#login-container #signup-btn:hover {
+  background-color: #1d1d1d;
+  border-color: #181818;
+}
+
+.login-btn {
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */
+  margin-top: 5px;
+  color: black;
+}
+.login-btn:hover {
+  background: #658D5D;
+}
+
+#login {
+  position: relative;
+  padding: 25px 25px 50px 25px;
+  margin-bottom: 1em;
+}
+#login #login-form {
+  margin-top: 2em;
+  margin-bottom: 2em;
+  text-align: left;
+}
+#login-form{
+  padding: 0 195px;
+  margin: 7%;
+}
+#languageContainer {
+  padding: 0 25px;
+}
+
+.logout{
+  float: right;
+}
\ No newline at end of file