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

syncope git commit: [SYNCOPE-719] added last wizard page with captcha validation, implemented also captcha backend

Repository: syncope
Updated Branches:
  refs/heads/master f93b48951 -> 57a120244


[SYNCOPE-719] added last wizard page with captcha validation, implemented also captcha backend


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

Branch: refs/heads/master
Commit: 57a120244baeb9d8dd38e9f242b0f54631b7083a
Parents: f93b489
Author: Andrea Patricelli <an...@tirasa.net>
Authored: Tue Dec 29 12:38:45 2015 +0100
Committer: Andrea Patricelli <an...@tirasa.net>
Committed: Tue Dec 29 12:38:45 2015 +0100

----------------------------------------------------------------------
 .../enduser/SyncopeEnduserApplication.java      |  24 +++++
 .../client/enduser/SyncopeEnduserConstants.java |  29 +++++
 .../client/enduser/model/CaptchaRequest.java    |  49 +++++++++
 .../enduser/resources/CaptchaResource.java      |  61 +++++++++++
 .../resources/CaptchaValidateResource.java      |  83 ++++++++++++++
 .../resources/META-INF/resources/app/index.html |   4 +-
 .../resources/META-INF/resources/app/js/app.js  |  13 +++
 .../app/js/controllers/UserController.js        | 108 +++++++++++--------
 .../resources/app/js/directives/captcha.js      |  42 ++++++++
 .../resources/app/js/services/captchaService.js |  54 ++++++++++
 .../app/js/services/userSelfService.js          |   2 +-
 .../META-INF/resources/app/views/captcha.html   |  17 +++
 .../META-INF/resources/app/views/editUser.html  |   2 +-
 .../resources/app/views/user-credentials.html   |   2 +-
 .../resources/app/views/user-form-finish.html   |  32 ++++++
 .../resources/app/views/user-groups.html        |   2 +-
 .../resources/app/views/user-plain-schemas.html |   2 +-
 .../resources/app/views/user-resources.html     |   5 +-
 .../src/main/resources/log4j2.xml               |   4 +-
 19 files changed, 479 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
index 23ee026..5a565a4 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
@@ -27,6 +27,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
 import org.apache.commons.io.FileUtils;
+import org.apache.syncope.client.enduser.resources.CaptchaResource;
+import org.apache.syncope.client.enduser.resources.CaptchaValidateResource;
 import org.apache.syncope.client.enduser.resources.InfoResource;
 import org.apache.syncope.client.enduser.resources.LoginResource;
 import org.apache.syncope.client.enduser.resources.LogoutResource;
@@ -200,6 +202,28 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali
             }
         });
 
+        // resource to get a fresh captcha image
+        mountResource("/api/captcha", new ResourceReference("captcha") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new CaptchaResource();
+            }
+        });
+
+        // resource to validate current captcha
+        mountResource("/api/captcha/validate", new ResourceReference("captchaValidate") {
+
+            private static final long serialVersionUID = -128426276529456602L;
+
+            @Override
+            public IResource getResource() {
+                return new CaptchaValidateResource();
+            }
+        });
+
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java
new file mode 100644
index 0000000..daa0e15
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+public final class SyncopeEnduserConstants {
+
+    public static final String CAPTCHA_SESSION_KEY  = "captcha";
+
+    private SyncopeEnduserConstants() {
+        // private constructor for utility class
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CaptchaRequest.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CaptchaRequest.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CaptchaRequest.java
new file mode 100644
index 0000000..3c05d34
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CaptchaRequest.java
@@ -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.
+ */
+package org.apache.syncope.client.enduser.model;
+
+import java.io.Serializable;
+
+public class CaptchaRequest implements Serializable {
+
+    private static final long serialVersionUID = -8608459099587628110L;
+
+    private String value;
+
+    public CaptchaRequest() {
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder toStringBuilder;
+        toStringBuilder = new StringBuilder("CaptchaRequest{");
+        toStringBuilder.append("value=").append(this.value);
+        toStringBuilder.append('}');
+        return toStringBuilder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java
new file mode 100644
index 0000000..ad71a85
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java
@@ -0,0 +1,61 @@
+/*
+ * 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.servlet.http.HttpServletRequest;
+import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
+import org.apache.wicket.extensions.markup.html.captcha.CaptchaImageResource;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CaptchaResource extends CaptchaImageResource {
+
+    private static final long serialVersionUID = 8293404296348102926L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(CaptchaResource.class);
+
+    public static int randomInt(final int min, final int max) {
+        return (int) (Math.random() * (max - min) + min);
+    }
+
+    public static String randomString(final int min, final int max) {
+        int num = randomInt(min, max);
+        byte[] b = new byte[num];
+        for (int i = 0; i < num; i++) {
+            b[i] = (byte) randomInt('a', 'z');
+        }
+        return new String(b);
+    }
+
+    @Override
+    protected byte[] render() {
+        LOG.info("Generate captcha");
+        final String captcha = randomString(6, 8);
+        Request request = RequestCycle.get().getRequest();
+        HttpServletRequest httpRequest = ((HttpServletRequest) request.getContainerRequest());
+        // store the captcha in the current session
+        httpRequest.getSession().setAttribute(SyncopeEnduserConstants.CAPTCHA_SESSION_KEY, captcha);
+        
+        getChallengeIdModel().setObject(captcha);
+        return super.render();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaValidateResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaValidateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaValidateResource.java
new file mode 100644
index 0000000..5ae6312
--- /dev/null
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaValidateResource.java
@@ -0,0 +1,83 @@
+/*
+ * 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 javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
+import org.apache.syncope.client.enduser.model.CaptchaRequest;
+import org.apache.syncope.core.misc.serialization.POJOHelper;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CaptchaValidateResource extends AbstractBaseResource {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CaptchaValidateResource.class);
+
+    private static final long serialVersionUID = 6453101466981543020L;
+
+    @Override
+    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
+
+        LOG.debug("Validate captcha request");
+
+        ResourceResponse response = new ResourceResponse();
+        try {
+            HttpServletRequest currentRequest = (HttpServletRequest) attributes.getRequest().getContainerRequest();
+            final CaptchaRequest enteredCaptcha = POJOHelper.deserialize(IOUtils.toString(currentRequest.
+                    getInputStream()), CaptchaRequest.class);
+
+            final String currentCaptcha = currentRequest.getSession().getAttribute(
+                    SyncopeEnduserConstants.CAPTCHA_SESSION_KEY) == null
+                            ? null
+                            : currentRequest.getSession().getAttribute(SyncopeEnduserConstants.CAPTCHA_SESSION_KEY).
+                            toString();
+
+            if (StringUtils.isBlank(currentCaptcha) || enteredCaptcha == null) {
+                LOG.info("Could not validate captcha: current session captcha or inserted captcha are empty or null");
+                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder().append(
+                        "ErrorMessage{{ Could not validate captcha: current session captcha or entered captcha are empty or null }}").
+                        toString());
+            } else {
+                LOG.info("Is entered captcha equal to current session captcha? {}", enteredCaptcha.getValue().equals(
+                        currentCaptcha));
+
+                response.setWriteCallback(new WriteCallback() {
+
+                    @Override
+                    public void writeData(final IResource.Attributes attributes) throws IOException {
+                        attributes.getResponse().
+                                write(String.valueOf(enteredCaptcha.getValue().equals(currentCaptcha)));
+                    }
+                });
+                response.setStatusCode(Response.Status.OK.getStatusCode());
+            }
+        } catch (Exception e) {
+            LOG.error("Could not validate captcha", e);
+            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder().append(
+                    "ErrorMessage{{ Could not validate captcha ")
+                    .append(e.getMessage()).append(" }}").toString());
+        }
+        return response;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/index.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/index.html b/client/enduser/src/main/resources/META-INF/resources/app/index.html
index 94ff7bf..117796b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/index.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -82,6 +82,7 @@ under the License.
     <script src="js/services/realmService.js"></script>
     <script src="js/services/securityQuestionService.js"></script>
     <script src="js/services/infoService.js"></script>
+    <script src="js/services/captchaService.js"></script>
     <!--controllers-->
     <script src="js/controllers/HomeController.js"></script>
     <script src="js/controllers/LoginController.js"></script>
@@ -95,6 +96,7 @@ under the License.
     <script src="js/directives/navigationButtons.js"></script>
     <script src="js/directives/loader.js"></script>
     <script src="js/directives/equals.js"></script>
+    <script src="js/directives/captcha.js"></script>
     <!--filters-->
     <script src="js/filters/propsFilter.js"></script>
 
@@ -108,7 +110,7 @@ under the License.
     <link href="../webjars/ionicons/${ionicons.version}/css/ionicons.min.css" rel="stylesheet" type="text/css" />
     <link href="../webjars/angular-ui-select/${angular-ui-select.version}/select.css" rel="stylesheet" type="text/css"/>
     <link href="../webjars/angular-growl-2/${angular-growl-2.version}/angular-growl.css" rel="stylesheet" type="text/css"/>
-    <link href="../webjars/select2/${select2.version}/select2.css" rel="stylesheet" type="text/css"/>
+    <link href="../webjars/select2/${select2.version}/select2.css" rel="stylesheet" />
     <link href="css/app.css" rel="stylesheet" type="text/css" />
     <link href="css/login.css" rel="stylesheet" type="text/css" />
     <link href="css/editUser.css" rel="stylesheet" type="text/css" />

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
index 387115c..43ea447 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -96,6 +96,10 @@ app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'growlProvi
               url: '/resources',
               templateUrl: 'views/user-resources.html'
             })
+            .state('create.finish', {
+              url: '/finish',
+              templateUrl: 'views/user-form-finish.html'
+            })
             .state('update', {
               url: '/self/update',
               templateUrl: 'views/editUser.html',
@@ -162,6 +166,15 @@ app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'growlProvi
                   return AuthenticationHelper.authenticated();
                 }
               }
+            })
+            .state('update.finish', {
+              url: '/finish',
+              templateUrl: 'views/user-form-finish.html',
+              resolve: {
+                'authenticated': function (AuthenticationHelper) {
+                  return AuthenticationHelper.authenticated();
+                }
+              }
             });
 
     // catch all other routes

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
index 5f8953b..86ff297 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -20,8 +20,9 @@
 'use strict';
 
 angular.module("self").controller("UserController", ['$scope', '$rootScope', '$location', '$compile', 'AuthService',
-  'UserSelfService', 'SchemaService', 'RealmService', 'SecurityQuestionService', 'growl', function ($scope, $rootScope,
-          $location, $compile, AuthService, UserSelfService, SchemaService, RealmService, SecurityQuestionService, growl) {
+  'UserSelfService', 'SchemaService', 'RealmService', 'SecurityQuestionService', 'CaptchaService', 'growl', function ($scope,
+          $rootScope, $location, $compile, AuthService, UserSelfService, SchemaService, RealmService, SecurityQuestionService,
+          CaptchaService, growl) {
 
     $scope.user = {};
     $scope.confirmPassword = {
@@ -34,6 +35,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
     $scope.availableSecurityQuestions = [];
 
     $scope.initialSecurityQuestion = undefined;
+    $scope.captchaInput = {
+      value: ""
+    };
 
     $scope.initUser = function () {
 
@@ -47,7 +51,6 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         attributeTable: {}
       };
 
-
       var initSchemas = function () {
         // initialization is done here synchronously to have all schema fields populated correctly
         SchemaService.getUserSchemas().then(function (schemas) {
@@ -178,58 +181,73 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
 
       }
 
+
+
       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);
-          var errorMessage;
-          // parse error response 
-          if (response !== undefined) {
-            errorMessage = response.split("ErrorMessage{{")[1];
-            errorMessage = errorMessage.split("}}")[0];
-          }
-          growl.error("Error: " + (errorMessage || response), {referenceId: 2});
-        });
-
-      } else {
-
-        UserSelfService.update(user).then(function (response) {
-          console.log("Updated user: ", response);
-          AuthService.logout().then(function (response) {
-            console.log("LOGOUT SUCCESS: ", response);
+      // validate captcha and then save user
+      CaptchaService.validate($scope.captchaInput).then(function (response) {
+        if (!(response === 'true')) {
+          growl.error("Captcha inserted is not valid, please digit the correct captcha", {referenceId: 2});
+          return;
+        }
+
+        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');
-            growl.success("User " + $scope.user.username + " successfully updated", {referenceId: 1});
-          }, function () {
-            console.log("LOGOUT FAILED");
+          }, function (response) {
+            console.log("Error during user creation: ", response);
+            var errorMessage;
+            // parse error response 
+            if (response !== undefined) {
+              errorMessage = response.split("ErrorMessage{{")[1];
+              errorMessage = errorMessage.split("}}")[0];
+            }
+            growl.error("Error: " + (errorMessage || response), {referenceId: 2});
           });
-        }, function (response) {
-          console.log("Error during user update: ", response);
-          var errorMessage;
-          // parse error response 
-          if (response !== undefined) {
-            errorMessage = response.split("ErrorMessage{{")[1];
-            errorMessage = errorMessage.split("}}")[0];
-          }
-          growl.error("Error: " + (errorMessage || response), {referenceId: 2});
-        });
-      }
-    };
-
-
 
+        } else {
+
+          UserSelfService.update(user).then(function (response) {
+            console.log("Updated user: ", response);
+            AuthService.logout().then(function (response) {
+              console.log("LOGOUT SUCCESS: ", response);
+              $location.path('/self');
+              growl.success("User " + $scope.user.username + " successfully updated", {referenceId: 1});
+            }, function () {
+              console.log("LOGOUT FAILED");
+            });
+          }, function (response) {
+            console.log("Error during user update: ", response);
+            var errorMessage;
+            // parse error response 
+            if (response !== undefined) {
+              errorMessage = response.split("ErrorMessage{{")[1];
+              errorMessage = errorMessage.split("}}")[0];
+            }
+            growl.error("Error: " + (errorMessage || response), {referenceId: 2});
+          });
+        }
+      }, function (response) {
+        console.log("Error during validate captcha ", response);
+        var errorMessage;
+        // parse error response 
+        if (response !== undefined) {
+          errorMessage = response.split("ErrorMessage{{")[1];
+          errorMessage = errorMessage.split("}}")[0];
+        }
+        growl.error("Error: " + (errorMessage || response), {referenceId: 2});
+        return;
+      });
+    };
   }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/js/directives/captcha.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/captcha.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/captcha.js
new file mode 100644
index 0000000..c00a4f2
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/captcha.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')
+        .directive('captcha', function (CaptchaService) {
+          return {
+            restrict: 'E',
+            templateUrl: 'views/captcha.html',
+            scope: {
+              input: "=",
+            },
+            controller: function ($scope) {
+              $scope.captchaUrl = '';
+              
+              //initialize captcha
+              $scope.refreshCaptcha = function () {
+                console.log("REFRESH CAPTCHA")
+                $scope.captchaUrl = '/syncope-enduser/api/captcha' + '?' + new Date();
+              };
+
+              // initialize captcha
+              $scope.refreshCaptcha();
+            }
+          };
+        });

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/js/services/captchaService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/captchaService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/captchaService.js
new file mode 100644
index 0000000..6361d92
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/captchaService.js
@@ -0,0 +1,54 @@
+/* 
+ * 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('CaptchaService', ['$resource', '$q', '$http', function ($resource, $q, $http) {
+
+            var captchaService = {};
+
+            captchaService.getCaptcha = function () {
+              return $http
+                      .get('/syncope-enduser/api/captcha')
+                      .then(function (response) {
+                        return response;
+                      }, function (response) {
+                        console.log("Something went wrong during generation of chaptcha, exit with status: ",
+                                response.statusText);
+                      });
+            };
+
+            captchaService.validate = function (captcha) {
+              return $http
+                      .post('/syncope-enduser/api/captcha/validate', captcha)
+                      .then(function (response) {
+                        return response.data;
+                      }, function (response) {
+                        console.log("Something went wrong during validation of captcha: , exit with status: ", captcha,
+                                response.statusText);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
+
+            return captchaService;
+
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/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
index 3a99e7f..2f713a7 100644
--- 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
@@ -41,7 +41,7 @@ angular.module('login')
               return $http
                       .post('/syncope-enduser/api/self/create', user)
                       .then(function (response) {
-                        console.log("response save: ", response)
+                        console.log("response save: ", response);
                         var username = response;
                       }, function (response) {
                         console.log("Something went wrong during user self creation, exit with status: ", response);

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
new file mode 100644
index 0000000..3613778
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
@@ -0,0 +1,17 @@
+<div style="padding-left: 29%">
+  <nav class="navbar">
+    <div class="container-fluid">
+      <div class="navbar-header">
+        <img alt="captcha" ng-src="{{captchaUrl}}'"/>
+        <div style="margin-top: 5%">
+          <button id="refresh" type="button" class="btn btn-default btn-xs glyphicon glyphicon-refresh" ng-click="refreshCaptcha()" title="Refresh Captcha"></button>
+          <a id="refresh" class="btn btn-default btn-xs glyphicon glyphicon-question-sign" title="What is?" href="https://it.wikipedia.org/wiki/CAPTCHA"/>
+        </div>
+        <input class="form-control" style="margin-top: 5%" type="text" ng-model="input.value"/>
+      </div>
+      <div class="navbar-header">
+        <span class="help-block">Please enter the code displayed within the image.</span>
+      </div>
+    </div>
+  </nav>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/editUser.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/editUser.html b/client/enduser/src/main/resources/META-INF/resources/app/views/editUser.html
index 53c0130..3048010 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/editUser.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/editUser.html
@@ -22,7 +22,6 @@ under the License.
 
     <div id="form-container" class="col-md-6 col-md-offset-3" style="box-sizing: border-box; background-color: #F7F7F7;">
 
-
       <div>
         <div class="page-header" style="text-align: left; font-weight: 700;">
           <span ng-show="createMode">New User</span>
@@ -44,6 +43,7 @@ under the License.
               <!--<a ui-sref-active="active" ui-sref=".derivedSchemas" class="btn btn-default">Derived Schemas</a>
               <a ui-sref-active="active" ui-sref=".virtualSchemas" class="btn btn-default">Virtual Schemas</a>-->              
               <a ui-sref-active="active" ui-sref=".resources" class="btn btn-default">Resources</a>
+              <a ui-sref-active="active" ui-sref=".finish" class="btn btn-default">Finish</a>
             </div>
           </div>
         </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
index 1dfade1..31bc00d 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-credentials.html
@@ -55,6 +55,6 @@ under the License.
   <navigation-buttons ng-show="createMode" next="create.groups" previous="none"></navigation-buttons>
   <navigation-buttons ng-show="!createMode" next="update.groups" previous="none"></navigation-buttons>
   <div class="pull-left">
-    <a id="cancel" href="#/self" class="btn btn-default pull-left">Cancel</a>
+    <a id="cancel" href="#/self" class="btn btn-danger pull-left">Cancel</a>
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/user-form-finish.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-form-finish.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-form-finish.html
new file mode 100644
index 0000000..a786707
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-form-finish.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<div id="attribute" class="form-group row">
+  <!--captcha-->
+  <div class="form-group row">
+    <captcha input="captchaInput"></captcha>
+  </div>
+  <button id="save" type="submit" class="btn btn-default pull-right">Save</button>
+  <navigation-buttons ng-show="createMode" next="none" previous="create.resources"></navigation-buttons>
+  <navigation-buttons ng-show="!createMode" next="none" previous="update.resources"></navigation-buttons>
+  <div class="pull-left">
+    <a id="cancel" href="#/self" class="btn btn-danger">Cancel</a>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/user-groups.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-groups.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-groups.html
index a7e9edc..efca039 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-groups.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-groups.html
@@ -32,6 +32,6 @@ under the License.
   <navigation-buttons ng-show="createMode" next="create.plainSchemas" previous="create.credentials"></navigation-buttons>
   <navigation-buttons ng-show="!createMode" next="update.plainSchemas" previous="update.credentials"></navigation-buttons>
   <div class="pull-left">
-    <a id="cancel" href="#/self" class="btn btn-default pull-left">Cancel</a>
+    <a id="cancel" href="#/self" class="btn btn-danger pull-left">Cancel</a>
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/user-plain-schemas.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-plain-schemas.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-plain-schemas.html
index 7ebfd3a..a81b3c3 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-plain-schemas.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-plain-schemas.html
@@ -32,6 +32,6 @@ under the License.
   <navigation-buttons ng-show="createMode" next="create.resources" previous="create.groups"></navigation-buttons>
   <navigation-buttons ng-show="!createMode" next="update.resources" previous="update.groups"></navigation-buttons>
   <div class="pull-left">
-    <a id="cancel" href="#/self" class="btn btn-default pull-left">Cancel</a>
+    <a id="cancel" href="#/self" class="btn btn-danger pull-left">Cancel</a>
   </div>
 </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
index b5c5bee..30b79c5 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-resources.html
@@ -19,9 +19,8 @@ under the License.
 -->
 
 <div id="attribute" class="form-group row">
-  <button id="save" type="submit" class="btn btn-default pull-right">Save</button>
-  <navigation-buttons ng-show="createMode" next="none" previous="create.plainSchemas"></navigation-buttons>
-  <navigation-buttons ng-show="!createMode" next="none" previous="update.plainSchemas"></navigation-buttons>
+  <navigation-buttons ng-show="createMode" next="create.finish" previous="create.plainSchemas"></navigation-buttons>
+  <navigation-buttons ng-show="!createMode" next="update.finish" previous="update.plainSchemas"></navigation-buttons>
   <div class="pull-left">
     <a id="cancel" href="#/self" class="btn btn-danger">Cancel</a>
   </div>

http://git-wip-us.apache.org/repos/asf/syncope/blob/57a12024/fit/enduser-reference/src/main/resources/log4j2.xml
----------------------------------------------------------------------
diff --git a/fit/enduser-reference/src/main/resources/log4j2.xml b/fit/enduser-reference/src/main/resources/log4j2.xml
index 0aa3572..5226fb0 100644
--- a/fit/enduser-reference/src/main/resources/log4j2.xml
+++ b/fit/enduser-reference/src/main/resources/log4j2.xml
@@ -41,7 +41,7 @@ under the License.
       <appender-ref ref="main"/>
     </asyncLogger>
 
-    <asyncLogger name="org.apache.syncope.client.enduser" additivity="false" level="ERROR">
+    <asyncLogger name="org.apache.syncope.client.enduser" additivity="false" level="INFO">
       <appender-ref ref="main"/>
     </asyncLogger>
 
@@ -53,7 +53,7 @@ under the License.
       <appender-ref ref="main"/>
     </asyncLogger>
     
-    <root level="ERROR">
+    <root level="INFO">
       <appender-ref ref="main"/>
     </root>