[SYNCOPE-1019] Added dynamic templating feature to Enduser app

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

Branch: refs/heads/2_1_X
Commit: ac909f2ec079969b3860e31fc62470b493ea90eb
Parents: 524c968
Author: skylark17 <matteo.alessandr...@tirasa.net>
Authored: Tue Aug 28 10:11:47 2018 +0200
Committer: skylark17 <matteo.alessandr...@tirasa.net>
Committed: Mon Sep 10 17:12:44 2018 +0200

----------------------------------------------------------------------
 .gitignore                                      |   1 +
 archetype/pom.xml                               |   7 +-
 .../archetype-resources/enduser/pom.xml         |   6 +-
 .../syncope/client/console/pages/BasePage.html  |   2 +-
 .../syncope/client/console/pages/Login.html     |   2 +-
 .../console/pages/MustChangePassword.html       |   2 +-
 client/enduser/pom.xml                          |   4 +-
 .../enduser/SyncopeEnduserApplication.java      | 130 +++++--
 .../enduser/adapters/PlatformInfoAdapter.java   |   4 +-
 .../enduser/model/CustomAttributesInfo.java     |  15 -
 .../client/enduser/model/CustomTemplate.java    |  77 ++++
 .../enduser/model/CustomTemplateInfo.java       |  72 ++++
 .../client/enduser/model/CustomTemplateUrl.java |  37 ++
 .../enduser/model/CustomTemplateWizard.java     |  49 +++
 .../enduser/model/PlatformInfoRequest.java      |  10 +-
 .../resources/DynamicTemplateResource.java      |  79 ++++
 .../resources/ExternalResourceResource.java     |  14 +-
 .../client/enduser/resources/GroupResource.java |  43 ++-
 .../client/enduser/resources/InfoResource.java  |   8 +-
 .../enduser/resources/SchemaResource.java       |  45 ++-
 .../resources/UserSelfCreateResource.java       |   3 +-
 .../enduser/resources/UserSelfReadResource.java |  15 +-
 .../resources/UserSelfUpdateResource.java       |   7 +-
 .../enduser/util/UserRequestValidator.java      |  29 +-
 .../META-INF/resources/app/css/app.css          | 107 +++---
 .../resources/app/css/customSpinner.css         |  49 +++
 .../META-INF/resources/app/css/editUser.css     | 369 +++++++++++--------
 .../META-INF/resources/app/css/login.css        |  41 +--
 .../META-INF/resources/app/css/notification.css |  28 ++
 .../resources/app/css/passwordReset.css         |  37 ++
 .../app/css/templates/dark/editUser.css         |  95 +++++
 .../resources/app/css/templates/dark/login.css  |  78 ++++
 .../resources/META-INF/resources/app/index.html |  27 +-
 .../resources/META-INF/resources/app/js/app.js  | 158 +++++---
 .../app/js/controllers/LoginController.js       |  11 +-
 .../app/js/controllers/OIDCClientController.js  |   4 +-
 .../app/js/controllers/SAML2SPController.js     |   4 +-
 .../app/js/controllers/UserController.js        |  42 ++-
 .../app/js/directives/dynamicPlainAttribute.js  |  10 +-
 .../app/js/directives/dynamicTemplateItem.js    |  79 ++++
 .../js/directives/dynamicVirtualAttribute.js    |  22 +-
 .../js/directives/dynamicVirtualAttributes.js   |   3 +-
 .../resources/app/js/directives/fileInput.js    |   4 +-
 .../app/js/directives/navigationButtons.js      |  71 ----
 .../js/directives/navigationButtonsPartial.js   |  71 ++++
 .../app/js/services/dynamicTemplateService.js   |  67 ++++
 .../resources/app/js/util/assetsManager.js      | 105 ++++++
 .../META-INF/resources/app/views/captcha.html   |  14 +-
 .../app/views/confirmpasswordreset.html         |  28 +-
 .../app/views/dynamicDerivedAttributes.html     |  19 +-
 .../app/views/dynamicPlainAttribute.html        | 139 ++++---
 .../app/views/dynamicPlainAttributes.html       |  14 +-
 .../app/views/dynamicVirtualAttributes.html     |  18 +-
 .../META-INF/resources/app/views/editUser.html  |  31 +-
 .../resources/app/views/mustchangepassword.html |  22 +-
 .../resources/app/views/navigationButtons.html  |  26 --
 .../app/views/navigationButtonsPartial.html     |  29 ++
 .../resources/app/views/passwordreset.html      |  55 +--
 .../META-INF/resources/app/views/self.html      |  60 +--
 .../app/views/templates/editUserTemplate.html   |  57 +++
 .../onlyPlainAttrsDetails/editUserTemplate.html |  57 +++
 .../views/templates/passwordresetTemplate.html  |  79 ++++
 .../app/views/templates/selfTemplate.html       |  77 ++++
 .../resources/app/views/user-credentials.html   |  62 ++--
 .../app/views/user-derived-schemas.html         |  33 +-
 .../resources/app/views/user-form-finish.html   |  29 +-
 .../resources/app/views/user-groups.html        |  47 ++-
 .../resources/app/views/user-plain-schemas.html |  35 +-
 .../resources/app/views/user-resources.html     |  37 +-
 .../app/views/user-virtual-schemas.html         |  33 +-
 .../enduser/src/main/resources/customForm.json  |   1 -
 .../main/resources/customFormAttributes.json    |   1 +
 .../src/main/resources/customTemplate.json      |  65 ++++
 .../enduser/util/UserRequestValidatorTest.java  |  40 +-
 .../enduser/src/test/resources/customForm.json  |  50 ---
 .../test/resources/customFormAttributes.json    |  44 +++
 .../src/test/resources/customTemplate.json      |  56 +++
 deb/enduser/pom.xml                             |   3 +-
 deb/enduser/src/deb/control/conffiles           |   3 +-
 fit/enduser-reference/pom.xml                   |  23 --
 .../src/main/resources/customForm.json          |   1 -
 .../main/resources/customFormAttributes.json    |   1 +
 .../src/main/resources/customTemplate.json      |  65 ++++
 .../src/main/resources/package.json             |   1 +
 .../src/test/resources/customForm.json          |   1 -
 .../test/resources/customFormAttributes.json    |   1 +
 .../src/test/resources/customTemplate.json      |  65 ++++
 .../src/test/resources/protractor-conf.js       |   8 +-
 .../src/test/resources/tests/abstract.js        |  11 +-
 .../src/test/resources/tests/edit.js            |   5 +
 .../src/test/resources/tests/passwordreset.js   |   1 +
 pom.xml                                         |  48 +--
 .../workingwithapachesyncope/customization.adoc | 207 ++++++++++-
 93 files changed, 2770 insertions(+), 1075 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 3388d02..d479649 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/lib/
 ide/eclipse/bundles/org.apache.syncope.ide.eclipse.tests/bin/
 ide/eclipse/bundles/org.apache.syncope.ide.eclipse.tests/screenshots/
 *nb-configuration.xml
+node_modules

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/archetype/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/pom.xml b/archetype/pom.xml
index 70f296a..25d1ce8 100644
--- a/archetype/pom.xml
+++ b/archetype/pom.xml
@@ -276,7 +276,8 @@ under the License.
         
<targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/resources</targetPath>
         <includes>
           <include>enduser.properties</include>
-          <include>customForm.json</include>
+          <include>customFormAttributes.json</include>
+          <include>customTemplate.json</include>
         </includes>
       </resource>
       <resource>
@@ -310,7 +311,7 @@ under the License.
         <includes>
           <include>enduser.properties</include>
           <include>saml2sp-agent.properties</include>
-          <include>customForm.json</include>
+          <include>customFormAttributes.json</include>
         </includes>
       </resource>
       <resource>
@@ -319,7 +320,7 @@ under the License.
         <includes>
           <include>enduser.properties</include>
           <include>oidcclient-agent.properties</include>
-          <include>customForm.json</include>
+          <include>customFormAttributes.json</include>
         </includes>
       </resource>
       <resource>

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/archetype/src/main/resources/archetype-resources/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/archetype/src/main/resources/archetype-resources/enduser/pom.xml 
b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
index 5f98d31..0c03dec 100644
--- a/archetype/src/main/resources/archetype-resources/enduser/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
@@ -247,7 +247,11 @@ under the License.
                           
todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
                           overwrite="true"/>
                     
-                    <copy 
file="${project.build.directory}/test-classes/customForm.json" 
+                    <copy 
file="${project.build.directory}/test-classes/customFormAttributes.json" 
+                          
todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
+                          overwrite="true"/>
+                    
+                    <copy 
file="${project.build.directory}/test-classes/customTemplate.json" 
                           
todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
                           overwrite="true"/>
                   </target>

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
index 169e2d6..d477213 100644
--- 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
+++ 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/BasePage.html
@@ -26,7 +26,7 @@ under the License.
 
     <link rel="shortcut icon" href="img/favicon.png" type="image/png"/>
 
-    <link 
href="webjars/font-awesome/${font-awesome.version}/css/font-awesome.min.css" 
rel="stylesheet" type="text/css"/>
+    <link 
href="webjars/font-awesome/${font-awesome.version}/css/${font-awesome.filename}"
 rel="stylesheet" type="text/css"/>
     <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" 
rel="stylesheet" type="text/css"/>
     <link href="css/fonts.css" rel="stylesheet" type="text/css"/>
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
index bb5cd3a..0340b83 100644
--- 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
+++ 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
@@ -27,7 +27,7 @@ under the License.
 
     <link rel="shortcut icon" href="img/favicon.png" type="image/png"/>
 
-    <link 
href="webjars/font-awesome/${font-awesome.version}/css/font-awesome.min.css" 
rel="stylesheet" type="text/css" />
+    <link 
href="webjars/font-awesome/${font-awesome.version}/css/${font-awesome.filename}"
 rel="stylesheet" type="text/css" />
     <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" 
rel="stylesheet" type="text/css" />
     <link href="css/fonts.css" rel="stylesheet" type="text/css"/>
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/console/src/main/resources/org/apache/syncope/client/console/pages/MustChangePassword.html
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/MustChangePassword.html
 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/MustChangePassword.html
index 4aef153..037ea71 100644
--- 
a/client/console/src/main/resources/org/apache/syncope/client/console/pages/MustChangePassword.html
+++ 
b/client/console/src/main/resources/org/apache/syncope/client/console/pages/MustChangePassword.html
@@ -27,7 +27,7 @@ under the License.
 
     <link rel="shortcut icon" href="img/favicon.png" type="image/png"/>
 
-    <link 
href="webjars/font-awesome/${font-awesome.version}/css/font-awesome.min.css" 
rel="stylesheet" type="text/css" />
+    <link 
href="webjars/font-awesome/${font-awesome.version}/css/${font-awesome.filename}"
 rel="stylesheet" type="text/css" />
     <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" 
rel="stylesheet" type="text/css" />
     <link href="css/fonts.css" rel="stylesheet" type="text/css"/>
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/pom.xml
----------------------------------------------------------------------
diff --git a/client/enduser/pom.xml b/client/enduser/pom.xml
index d0d8d42..b8a824a 100644
--- a/client/enduser/pom.xml
+++ b/client/enduser/pom.xml
@@ -137,8 +137,8 @@ under the License.
       <artifactId>ionicons</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.webjars</groupId>
-      <artifactId>angular-ui-bootstrap</artifactId>
+      <groupId>org.webjars.npm</groupId>
+      <artifactId>ui-bootstrap4</artifactId>
     </dependency>
     <dependency>
       <groupId>org.webjars</groupId>

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/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 98e723f..be67966 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
@@ -39,6 +39,7 @@ import org.apache.syncope.client.enduser.annotations.Resource;
 import 
org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.enduser.init.EnduserInitializer;
 import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
 import org.apache.syncope.client.enduser.resources.CaptchaResource;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.common.lib.PropertyUtils;
@@ -64,7 +65,9 @@ public class SyncopeEnduserApplication extends WebApplication 
implements Seriali
 
     private static final String ENDUSER_PROPERTIES = "enduser.properties";
 
-    private static final String CUSTOM_FORM_FILE = "customForm.json";
+    private static final String CUSTOM_FORM_ATTRIBUTES_FILE = 
"customFormAttributes.json";
+
+    private static final String CUSTOM_TEMPLATE_FILE = "customTemplate.json";
 
     public static SyncopeEnduserApplication get() {
         return (SyncopeEnduserApplication) WebApplication.get();
@@ -86,7 +89,9 @@ public class SyncopeEnduserApplication extends WebApplication 
implements Seriali
 
     private SyncopeClientFactoryBean clientFactory;
 
-    private Map<String, CustomAttributesInfo> customForm;
+    private Map<String, CustomAttributesInfo> customFormAttributes;
+
+    private CustomTemplateInfo customTemplate;
 
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
@@ -130,26 +135,29 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
                 setContentType(SyncopeClientFactoryBean.ContentType.JSON).
                 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
 
-        // read customForm.json
-        try (InputStream is = getClass().getResourceAsStream("/" + 
CUSTOM_FORM_FILE)) {
-            customForm = MAPPER.readValue(is,
+        // read customFormAttributes.json
+        File enduserDir;
+        try (InputStream is = getClass().getResourceAsStream("/" + 
CUSTOM_FORM_ATTRIBUTES_FILE)) {
+            customFormAttributes = MAPPER.readValue(is,
                     new TypeReference<HashMap<String, CustomAttributesInfo>>() 
{
             });
-            File enduserDir = new File(props.getProperty("enduser.directory"));
+            enduserDir = new File(props.getProperty("enduser.directory"));
             boolean existsEnduserDir = enduserDir.exists() && 
enduserDir.canRead() && enduserDir.isDirectory();
             if (existsEnduserDir) {
-                File customFormFile = FileUtils.getFile(enduserDir, 
CUSTOM_FORM_FILE);
-                if (customFormFile.exists() && customFormFile.canRead() && 
customFormFile.isFile()) {
-                    customForm = 
MAPPER.readValue(FileUtils.openInputStream(customFormFile),
+                File customFormAttributesFile = FileUtils.getFile(enduserDir, 
CUSTOM_FORM_ATTRIBUTES_FILE);
+                if (customFormAttributesFile.exists()
+                        && customFormAttributesFile.canRead()
+                        && customFormAttributesFile.isFile()) {
+                    customFormAttributes = 
MAPPER.readValue(FileUtils.openInputStream(customFormAttributesFile),
                             new TypeReference<HashMap<String, 
CustomAttributesInfo>>() {
                     });
                 }
             }
             FileAlterationObserver observer = existsEnduserDir
                     ? new FileAlterationObserver(enduserDir,
-                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE))
-                    : new FileAlterationObserver(getClass().getResource("/" + 
CUSTOM_FORM_FILE).getFile(),
-                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE));
+                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_FORM_ATTRIBUTES_FILE))
+                    : new FileAlterationObserver(getClass().getResource("/" + 
CUSTOM_FORM_ATTRIBUTES_FILE).getFile(),
+                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_FORM_ATTRIBUTES_FILE));
 
             FileAlterationMonitor monitor = new FileAlterationMonitor(5000);
 
@@ -158,8 +166,9 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
                 @Override
                 public void onFileChange(final File file) {
                     try {
-                        LOG.trace("{} has changed. Reloading form 
customization configuration.", CUSTOM_FORM_FILE);
-                        customForm = 
MAPPER.readValue(FileUtils.openInputStream(file),
+                        LOG.trace("{} has changed. Reloading form attributes 
customization configuration.",
+                                CUSTOM_FORM_ATTRIBUTES_FILE);
+                        customFormAttributes = 
MAPPER.readValue(FileUtils.openInputStream(file),
                                 new TypeReference<HashMap<String, 
CustomAttributesInfo>>() {
                         });
                     } catch (IOException e) {
@@ -170,8 +179,9 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
                 @Override
                 public void onFileCreate(final File file) {
                     try {
-                        LOG.trace("{} has been created. Loading form 
customization configuration.", CUSTOM_FORM_FILE);
-                        customForm = 
MAPPER.readValue(FileUtils.openInputStream(file),
+                        LOG.trace("{} has been created. Loading form 
attributes customization configuration.",
+                                CUSTOM_FORM_ATTRIBUTES_FILE);
+                        customFormAttributes = 
MAPPER.readValue(FileUtils.openInputStream(file),
                                 new TypeReference<HashMap<String, 
CustomAttributesInfo>>() {
                         });
                     } catch (IOException e) {
@@ -181,8 +191,72 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
 
                 @Override
                 public void onFileDelete(final File file) {
-                    LOG.trace("{} has been deleted. Resetting form 
customization configuration.", CUSTOM_FORM_FILE);
-                    customForm = null;
+                    LOG.trace("{} has been deleted. Resetting form attributes 
customization configuration.",
+                            CUSTOM_FORM_ATTRIBUTES_FILE);
+                    customFormAttributes = null;
+                }
+            };
+
+            observer.addListener(listener);
+            monitor.addObserver(observer);
+            monitor.start();
+        } catch (Exception e) {
+            throw new WicketRuntimeException("Could not read " + 
CUSTOM_FORM_ATTRIBUTES_FILE, e);
+        }
+
+        // read customTemplate.json
+        try (InputStream is = getClass().getResourceAsStream("/" + 
CUSTOM_TEMPLATE_FILE)) {
+            customTemplate = MAPPER.readValue(is, CustomTemplateInfo.class);
+            enduserDir = new File(props.getProperty("enduser.directory"));
+            boolean existsEnduserDir = enduserDir.exists() && 
enduserDir.canRead() && enduserDir.isDirectory();
+            if (existsEnduserDir) {
+                File customTemplateFile = FileUtils.getFile(enduserDir, 
CUSTOM_TEMPLATE_FILE);
+                if (customTemplateFile.exists()
+                        && customTemplateFile.canRead()
+                        && customTemplateFile.isFile()) {
+                    customTemplate = 
MAPPER.readValue(FileUtils.openInputStream(customTemplateFile),
+                            CustomTemplateInfo.class);
+                }
+            }
+            FileAlterationObserver observer = existsEnduserDir
+                    ? new FileAlterationObserver(enduserDir,
+                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_TEMPLATE_FILE))
+                    : new FileAlterationObserver(getClass().getResource("/" + 
CUSTOM_TEMPLATE_FILE).getFile(),
+                            pathname -> 
StringUtils.contains(pathname.getPath(), CUSTOM_TEMPLATE_FILE));
+
+            FileAlterationMonitor monitor = new FileAlterationMonitor(5000);
+
+            FileAlterationListener listener = new 
FileAlterationListenerAdaptor() {
+
+                @Override
+                public void onFileChange(final File file) {
+                    try {
+                        LOG.trace("{} has changed. Reloading app customization 
configuration.",
+                                CUSTOM_TEMPLATE_FILE);
+                        customTemplate = 
MAPPER.readValue(FileUtils.openInputStream(file),
+                                CustomTemplateInfo.class);
+                    } catch (IOException e) {
+                        e.printStackTrace(System.err);
+                    }
+                }
+
+                @Override
+                public void onFileCreate(final File file) {
+                    try {
+                        LOG.trace("{} has been created. Loading app 
customization configuration.",
+                                CUSTOM_TEMPLATE_FILE);
+                        customTemplate = 
MAPPER.readValue(FileUtils.openInputStream(file),
+                                CustomTemplateInfo.class);
+                    } catch (IOException e) {
+                        e.printStackTrace(System.err);
+                    }
+                }
+
+                @Override
+                public void onFileDelete(final File file) {
+                    LOG.trace("{} has been deleted. Resetting app 
customization configuration.",
+                            CUSTOM_TEMPLATE_FILE);
+                    customTemplate = null;
                 }
             };
 
@@ -190,7 +264,7 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
             monitor.addObserver(observer);
             monitor.start();
         } catch (Exception e) {
-            throw new WicketRuntimeException("Could not read " + 
CUSTOM_FORM_FILE, e);
+            throw new WicketRuntimeException("Could not read " + 
CUSTOM_TEMPLATE_FILE, e);
         }
 
         // mount resources
@@ -275,13 +349,21 @@ public class SyncopeEnduserApplication extends 
WebApplication implements Seriali
         return maxUploadFileSizeMB;
     }
 
-    public Map<String, CustomAttributesInfo> getCustomForm() {
-        return customForm;
+    public Map<String, CustomAttributesInfo> getCustomFormAttributes() {
+        return customFormAttributes;
+    }
+
+    public void setCustomFormAttributes(final Map<String, 
CustomAttributesInfo> customFormAttributes) {
+        this.customFormAttributes.clear();
+        this.customFormAttributes.putAll(customFormAttributes);
+    }
+
+    public void setCustomTemplate(final CustomTemplateInfo customTemplate) {
+        this.customTemplate = customTemplate;
     }
 
-    public void setCustomForm(final Map<String, CustomAttributesInfo> 
customForm) {
-        this.customForm.clear();
-        this.customForm.putAll(customForm);
+    public CustomTemplateInfo getCustomTemplate() {
+        return customTemplate;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java
index a9fcf3c..e194d6b 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java
@@ -27,7 +27,7 @@ import org.apache.syncope.common.lib.info.PlatformInfo;
 public final class PlatformInfoAdapter {
 
     public static PlatformInfoRequest toPlatformInfoRequest(final PlatformInfo 
platformInfo,
-            final Map<String, CustomAttributesInfo> customForm) {
+            final Map<String, CustomAttributesInfo> customFormAttributes) {
         PlatformInfoRequest request = new PlatformInfoRequest();
         request.setPwdResetAllowed(platformInfo.isPwdResetAllowed());
         request.setSelfRegAllowed(platformInfo.isSelfRegAllowed());
@@ -37,7 +37,7 @@ public final class PlatformInfoAdapter {
         if (SyncopeEnduserApplication.get().getMaxUploadFileSizeMB() != null) {
             
request.setMaxUploadFileSizeMB(SyncopeEnduserApplication.get().getMaxUploadFileSizeMB());
         }
-        request.setCustomForm(customForm);
+        request.setCustomFormAttributes(customFormAttributes);
 
         return request;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
index 63eaa0b..c49253c 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java
@@ -26,21 +26,11 @@ public class CustomAttributesInfo implements Serializable {
 
     private static final long serialVersionUID = 878444785696091916L;
 
-    private Boolean show = Boolean.TRUE;
-
     private Map<String, CustomAttribute> attributes = new LinkedHashMap<>();
 
     public CustomAttributesInfo() {
     }
 
-    public Boolean isShow() {
-        return show;
-    }
-
-    public void setShow(final Boolean show) {
-        this.show = show;
-    }
-
     public Map<String, CustomAttribute> getAttributes() {
         return attributes;
     }
@@ -49,11 +39,6 @@ public class CustomAttributesInfo implements Serializable {
         this.attributes = attributes;
     }
 
-    public CustomAttributesInfo show(final Boolean value) {
-        this.show = value;
-        return this;
-    }
-
     public CustomAttributesInfo attributes(final Map<String, CustomAttribute> 
value) {
         this.attributes = value;
         return this;

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplate.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplate.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplate.java
new file mode 100644
index 0000000..19d4a5b
--- /dev/null
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplate.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+public class CustomTemplate implements Serializable {
+
+    private static final long serialVersionUID = -3870675034923683299L;
+
+    private String templateUrl;
+
+    private List<String> css = new ArrayList<>();
+
+    private List<String> js = new ArrayList<>();
+
+    public CustomTemplate() {
+    }
+
+    public String getTemplateUrl() {
+        return templateUrl;
+    }
+
+    public void setTemplateUrl(final String templateUrl) {
+        this.templateUrl = templateUrl;
+    }
+
+    public List<String> getCss() {
+        return css;
+    }
+
+    public void setCss(final List<String> css) {
+        this.css = css;
+    }
+
+    public List<String> getJs() {
+        return js;
+    }
+
+    public void setJs(final List<String> js) {
+        this.js = js;
+    }
+
+    public CustomTemplate templateUrl(final String value) {
+        this.templateUrl = value;
+        return this;
+    }
+
+    public CustomTemplate css(final List<String> value) {
+        this.css = value;
+        return this;
+    }
+
+    public CustomTemplate js(final List<String> value) {
+        this.js = value;
+        return this;
+    }
+
+}

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

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateUrl.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateUrl.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateUrl.java
new file mode 100644
index 0000000..40bde34
--- /dev/null
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateUrl.java
@@ -0,0 +1,37 @@
+/*
+ * 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 CustomTemplateUrl implements Serializable {
+
+    private static final long serialVersionUID = 3971593691907398343L;
+
+    private String url;
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(final String url) {
+        this.url = url;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateWizard.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateWizard.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateWizard.java
new file mode 100644
index 0000000..3952f9b
--- /dev/null
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomTemplateWizard.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;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomTemplateWizard implements Serializable {
+
+    private static final long serialVersionUID = -4290154059045309105L;
+
+    private String firstStep;
+
+    private Map<String, CustomTemplateUrl> steps = new HashMap<>();
+
+    public String getFirstStep() {
+        return firstStep;
+    }
+
+    public void setFirstStep(final String firstStep) {
+        this.firstStep = firstStep;
+    }
+
+    public Map<String, CustomTemplateUrl> getSteps() {
+        return steps;
+    }
+
+    public void setSteps(final Map<String, CustomTemplateUrl> steps) {
+        this.steps = steps;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java
index c4daa44..bd80359 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java
@@ -37,7 +37,7 @@ public class PlatformInfoRequest implements Serializable {
 
     private int maxUploadFileSizeMB;
 
-    private Map<String, CustomAttributesInfo> customForm;
+    private Map<String, CustomAttributesInfo> customFormAttributes;
 
     public PlatformInfoRequest() {
     }
@@ -90,12 +90,12 @@ public class PlatformInfoRequest implements Serializable {
         this.maxUploadFileSizeMB = maxUploadFileSizeMB;
     }
 
-    public Map<String, CustomAttributesInfo> getCustomForm() {
-        return customForm;
+    public Map<String, CustomAttributesInfo> getCustomFormAttributes() {
+        return customFormAttributes;
     }
 
-    public void setCustomForm(final Map<String, CustomAttributesInfo> 
customForm) {
-        this.customForm = customForm;
+    public void setCustomFormAttributes(final Map<String, 
CustomAttributesInfo> customFormAttributes) {
+        this.customFormAttributes = customFormAttributes;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
new file mode 100644
index 0000000..86b19bd
--- /dev/null
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.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.resources;
+
+import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
+import org.apache.syncope.client.enduser.util.SaltGenerator;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.util.cookies.CookieUtils;
+
+@Resource(key = "info", path = "/api/dynamicTemplate")
+public class DynamicTemplateResource extends BaseResource {
+
+    private static final long serialVersionUID = 7181372091437530936L;
+
+    @Override
+    protected ResourceResponse newResourceResponse(final IResource.Attributes 
attributes) {
+        ResourceResponse response = new ResourceResponse();
+        response.setContentType(MediaType.APPLICATION_JSON);
+
+        try {
+            final CookieUtils sessionCookieUtils = 
SyncopeEnduserSession.get().getCookieUtils();
+            // set XSRF_TOKEN cookie
+            if (!SyncopeEnduserSession.get().isXsrfTokenGenerated() && 
(sessionCookieUtils.getCookie(
+                    SyncopeEnduserConstants.XSRF_COOKIE) == null || 
StringUtils.isBlank(
+                            
sessionCookieUtils.getCookie(SyncopeEnduserConstants.XSRF_COOKIE).getValue()))) 
{
+                LOG.debug("Set XSRF-TOKEN cookie");
+                SyncopeEnduserSession.get().setXsrfTokenGenerated(true);
+                sessionCookieUtils.save(SyncopeEnduserConstants.XSRF_COOKIE,
+                        
SaltGenerator.generate(SyncopeEnduserSession.get().getId()));
+            }
+            response.setTextEncoding(StandardCharsets.UTF_8.name());
+            response.setWriteCallback(new WriteCallback() {
+
+                @Override
+                public void writeData(final IResource.Attributes attributes) 
throws IOException {
+                    CustomTemplateInfo customTemplate = 
SyncopeEnduserApplication.get().getCustomTemplate();
+                    
attributes.getResponse().write(MAPPER.writeValueAsString(customTemplate));
+                }
+            });
+            response.setStatusCode(Response.Status.OK.getStatusCode());
+        } catch (Exception e) {
+            LOG.error("Error retrieving syncope custom dynamic template", e);
+            
response.setError(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), new 
StringBuilder()
+                    .append("ErrorMessage{{ ")
+                    .append(e.getMessage())
+                    .append(" }}")
+                    .toString());
+        }
+
+        return response;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ExternalResourceResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ExternalResourceResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ExternalResourceResource.java
index d354f56..2d7736c 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ExternalResourceResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/ExternalResourceResource.java
@@ -20,12 +20,15 @@ package org.apache.syncope.client.enduser.resources;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.syncope.client.enduser.SyncopeEnduserApplication;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.request.resource.IResource;
@@ -49,10 +52,13 @@ public class ExternalResourceResource extends BaseResource {
                 return response;
             }
 
-            final List<String> resources = SyncopeEnduserSession.get().
-                    getService(SyncopeService.class).platform().getResources();
+            CustomTemplateInfo customTemplate =
+                    SyncopeEnduserApplication.get().getCustomTemplate();
+            final List<String> resources = 
customTemplate.getWizard().getSteps().containsKey("groups")
+                    ? SyncopeEnduserSession.get().
+                            
getService(SyncopeService.class).platform().getResources()
+                    : Collections.<String>emptyList();
 
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
             response.setWriteCallback(new AbstractResource.WriteCallback() {
 
                 @Override
@@ -60,6 +66,8 @@ public class ExternalResourceResource extends BaseResource {
                     
attributes.getResponse().write(MAPPER.writeValueAsString(resources));
                 }
             });
+
+            response.setTextEncoding(StandardCharsets.UTF_8.name());
             response.setStatusCode(Response.Status.OK.getStatusCode());
         } catch (Exception e) {
             LOG.error("Error retrieving available resources", e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/GroupResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/GroupResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/GroupResource.java
index 66ae312..14fb790 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/GroupResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/GroupResource.java
@@ -22,14 +22,17 @@ import java.io.IOException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.syncope.client.enduser.SyncopeEnduserApplication;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
@@ -55,24 +58,31 @@ public class GroupResource extends BaseResource {
                 return response;
             }
 
-            String realm = 
URLDecoder.decode(attributes.getParameters().get("realm").
-                    toString(SyncopeConstants.ROOT_REALM), "UTF-8");
-            StringValue term = attributes.getParameters().get("term");
-
+            CustomTemplateInfo customTemplate =
+                    SyncopeEnduserApplication.get().getCustomTemplate();
             final GroupResponse groupResponse = new GroupResponse();
-            final int totGroups = SyncopeEnduserSession.get().
-                    
getService(SyncopeService.class).numbers().getTotalGroups();
-            final List<GroupTO> groupTOs = SyncopeEnduserSession.get().
-                    getService(SyncopeService.class).searchAssignableGroups(
-                    realm,
-                    term.isNull() || term.isEmpty() ? null : 
URLDecoder.decode(term.toString(), "UTF-8"),
-                    1,
-                    30).getResult();
-            groupResponse.setTotGroups(totGroups);
-            groupResponse.setGroupTOs(groupTOs.stream().collect(
-                    Collectors.toMap(GroupTO::getKey, GroupTO::getName)));
+            if (customTemplate.getWizard().getSteps().containsKey("groups")) {
+                String realm = 
URLDecoder.decode(attributes.getParameters().get("realm").
+                        toString(SyncopeConstants.ROOT_REALM), "UTF-8");
+                StringValue term = attributes.getParameters().get("term");
+
+                final int totGroups = SyncopeEnduserSession.get().
+                        
getService(SyncopeService.class).numbers().getTotalGroups();
+                final List<GroupTO> groupTOs = SyncopeEnduserSession.get().
+                        
getService(SyncopeService.class).searchAssignableGroups(
+                        realm,
+                        term.isNull() || term.isEmpty() ? null : 
URLDecoder.decode(term.toString(), "UTF-8"),
+                        1,
+                        30).getResult();
+                groupResponse.setTotGroups(totGroups);
+                groupResponse.setGroupTOs(groupTOs.stream().collect(
+                        Collectors.toMap(GroupTO::getKey, GroupTO::getName)));
+            } else {
+                groupResponse.setTotGroups(0);
+                Map<String, String> groups = new HashMap<>();
+                groupResponse.setGroupTOs(groups);
+            }
 
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
             response.setWriteCallback(new AbstractResource.WriteCallback() {
 
                 @Override
@@ -80,6 +90,7 @@ public class GroupResource extends BaseResource {
                     
attributes.getResponse().write(MAPPER.writeValueAsString(groupResponse));
                 }
             });
+            response.setTextEncoding(StandardCharsets.UTF_8.name());
             response.setStatusCode(Response.Status.OK.getStatusCode());
         } catch (Exception e) {
             LOG.error("Error retrieving available groups", e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java
index 58bd238..9b990a0 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java
@@ -57,18 +57,20 @@ public class InfoResource extends BaseResource {
                         
SaltGenerator.generate(SyncopeEnduserSession.get().getId()));
             }
             response.setTextEncoding(StandardCharsets.UTF_8.name());
+
             response.setWriteCallback(new WriteCallback() {
 
                 @Override
                 public void writeData(final IResource.Attributes attributes) 
throws IOException {
-                    Map<String, CustomAttributesInfo> customForm = 
SyncopeEnduserApplication.get().getCustomForm();
+                    Map<String, CustomAttributesInfo> customFormAttributes =
+                            
SyncopeEnduserApplication.get().getCustomFormAttributes();
                     attributes.getResponse().write(
                             MAPPER.writeValueAsString(
                                     PlatformInfoAdapter.toPlatformInfoRequest(
                                             
SyncopeEnduserSession.get().getPlatformInfo(),
-                                            customForm == null
+                                            customFormAttributes == null
                                                     ? new HashMap<>()
-                                                    : customForm)));
+                                                    : customFormAttributes)));
                 }
             });
             response.setStatusCode(Response.Status.OK.getStatusCode());

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
index 3559bdb..b6c57e8 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
@@ -35,6 +35,7 @@ import 
org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.annotations.Resource;
 import org.apache.syncope.client.enduser.model.CustomAttribute;
 import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
 import org.apache.syncope.client.enduser.model.SchemaResponse;
 import org.apache.syncope.common.lib.to.SchemaTO;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
@@ -88,38 +89,47 @@ public class SchemaResource extends BaseResource {
             }
 
             // USER from customization, if empty or null ignore it, use it to 
filter attributes otherwise
-            Map<String, CustomAttributesInfo> customForm = 
SyncopeEnduserApplication.get().getCustomForm();
+            Map<String, CustomAttributesInfo> customFormAttributes =
+                    SyncopeEnduserApplication.get().getCustomFormAttributes();
+            CustomTemplateInfo customTemplate =
+                    SyncopeEnduserApplication.get().getCustomTemplate();
 
             SchemaService schemaService = 
SyncopeEnduserSession.get().getService(SchemaService.class);
             final List<SchemaTO> plainSchemas = classes.isEmpty()
                     ? Collections.<SchemaTO>emptyList()
-                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.PLAIN.name()) == null
+                    : customFormAttributes == null
+                    || customFormAttributes.isEmpty()
+                    || customFormAttributes.get(SchemaType.PLAIN.name()) == 
null
                     ? schemaService.search(
                             new 
SchemaQuery.Builder().type(SchemaType.PLAIN).anyTypeClasses(classes).build())
-                    : customForm.get(SchemaType.PLAIN.name()).isShow()
+                    : 
customTemplate.getWizard().getSteps().containsKey("plainSchemas")
                     ? customizeSchemas(schemaService.search(new 
SchemaQuery.Builder().type(SchemaType.PLAIN).
-                            anyTypeClasses(classes).build()), group, 
customForm.get(SchemaType.PLAIN.name()).
-                            getAttributes())
+                            anyTypeClasses(classes).build()), group,
+                            
customFormAttributes.get(SchemaType.PLAIN.name()).getAttributes())
                     : Collections.<SchemaTO>emptyList();
             final List<SchemaTO> derSchemas = classes.isEmpty()
                     ? Collections.<SchemaTO>emptyList()
-                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.DERIVED.name()) == null
+                    : customFormAttributes == null
+                    || customFormAttributes.isEmpty()
+                    || customFormAttributes.get(SchemaType.DERIVED.name()) == 
null
                     ? schemaService.search(
                             new 
SchemaQuery.Builder().type(SchemaType.DERIVED).anyTypeClasses(classes).build())
-                    : customForm.get(SchemaType.DERIVED.name()).isShow()
+                    : 
customTemplate.getWizard().getSteps().containsKey("derivedSchemas")
                     ? customizeSchemas(schemaService.search(new 
SchemaQuery.Builder().type(SchemaType.DERIVED).
-                            anyTypeClasses(classes).build()), group, 
customForm.get(SchemaType.DERIVED.name()).
-                            getAttributes())
+                            anyTypeClasses(classes).build()), group,
+                            
customFormAttributes.get(SchemaType.DERIVED.name()).getAttributes())
                     : Collections.<SchemaTO>emptyList();
             final List<SchemaTO> virSchemas = classes.isEmpty()
                     ? Collections.<SchemaTO>emptyList()
-                    : customForm == null || customForm.isEmpty() || 
customForm.get(SchemaType.VIRTUAL.name()) == null
+                    : customFormAttributes == null
+                    || customFormAttributes.isEmpty()
+                    || customFormAttributes.get(SchemaType.VIRTUAL.name()) == 
null
                     ? schemaService.search(
                             new 
SchemaQuery.Builder().type(SchemaType.VIRTUAL).anyTypeClasses(classes).build())
-                    : customForm.get(SchemaType.VIRTUAL.name()).isShow()
+                    : 
customTemplate.getWizard().getSteps().containsKey("virtualSchemas")
                     ? customizeSchemas(schemaService.search(new 
SchemaQuery.Builder().type(SchemaType.VIRTUAL).
-                            anyTypeClasses(classes).build()), group, 
customForm.get(SchemaType.VIRTUAL.name()).
-                            getAttributes())
+                            anyTypeClasses(classes).build()), group,
+                            
customFormAttributes.get(SchemaType.VIRTUAL.name()).getAttributes())
                     : Collections.<SchemaTO>emptyList();
 
             if (group != null) {
@@ -161,21 +171,20 @@ public class SchemaResource extends BaseResource {
     private List<SchemaTO> customizeSchemas(
             final List<SchemaTO> schemaTOs,
             final String groupParam,
-            final Map<String, CustomAttribute> customForm) {
-
-        if (customForm.isEmpty()) {
+            final Map<String, CustomAttribute> customFormAttributes) {
+        if (customFormAttributes.isEmpty()) {
             return schemaTOs;
         }
         final boolean isGroupBlank = StringUtils.isBlank(groupParam);
 
         schemaTOs.removeAll(schemaTOs.stream().
-                filter(schema -> !customForm.containsKey(isGroupBlank
+                filter(schema -> !customFormAttributes.containsKey(isGroupBlank
                 ? schema.getKey()
                 : compositeSchemaKey(groupParam, schema.getKey()))).
                 collect(Collectors.toSet()));
 
         Collections.sort(schemaTOs, (schemaTO1, schemaTO2) -> {
-            List<String> order = new ArrayList<>(customForm.keySet());
+            List<String> order = new 
ArrayList<>(customFormAttributes.keySet());
             return order.indexOf(isGroupBlank
                     ? schemaTO1.getKey()
                     : compositeSchemaKey(groupParam, schemaTO1.getKey()))

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
index 9d8d7d5..e017d45 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
@@ -81,7 +81,8 @@ public class UserSelfCreateResource extends 
BaseUserSelfResource {
                 LOG.trace("Request is [{}]", userTO);
 
                 // check if request is compliant with customization form rules
-                if (UserRequestValidator.compliant(userTO, 
SyncopeEnduserApplication.get().getCustomForm(), true)) {
+                if (UserRequestValidator.compliant(userTO,
+                        
SyncopeEnduserApplication.get().getCustomFormAttributes(), true)) {
 
                     // 1. membership attributes management
                     Set<AttrTO> membAttrs = new HashSet<>();

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/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
index 255449a..3e8e2ce 100644
--- 
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
@@ -97,7 +97,7 @@ public class UserSelfReadResource extends 
BaseUserSelfResource {
                 membership.getVirAttrs().clear();
             }
             // USER from customization, if empty or null ignore it, use it to 
filter attributes otherwise
-            applyFromCustomization(userTO, 
SyncopeEnduserApplication.get().getCustomForm());
+            applyFromCustomization(userTO, 
SyncopeEnduserApplication.get().getCustomFormAttributes());
 
             // 1.1 Date -> millis conversion for PLAIN attributes of USER
             for (PlainSchemaTO plainSchema : 
SyncopeEnduserSession.get().getDatePlainSchemas()) {
@@ -127,26 +127,25 @@ public class UserSelfReadResource extends 
BaseUserSelfResource {
         return response;
     }
 
-    private void applyFromCustomization(final UserTO userTO, final Map<String, 
CustomAttributesInfo> customForm) {
-        if (customForm != null && !customForm.isEmpty()) {
+    private void applyFromCustomization(final UserTO userTO,
+            final Map<String, CustomAttributesInfo> customFormAttributes) {
+        if (customFormAttributes != null && !customFormAttributes.isEmpty()) {
             // filter PLAIN attributes
-            customizeAttrTOs(userTO.getPlainAttrs(), 
customForm.get(SchemaType.PLAIN.name()));
+            customizeAttrTOs(userTO.getPlainAttrs(), 
customFormAttributes.get(SchemaType.PLAIN.name()));
             // filter DERIVED attributes
-            customizeAttrTOs(userTO.getDerAttrs(), 
customForm.get(SchemaType.DERIVED.name()));
+            customizeAttrTOs(userTO.getDerAttrs(), 
customFormAttributes.get(SchemaType.DERIVED.name()));
             // filter VIRTUAL attributes
-            customizeAttrTOs(userTO.getVirAttrs(), 
customForm.get(SchemaType.VIRTUAL.name()));
+            customizeAttrTOs(userTO.getVirAttrs(), 
customFormAttributes.get(SchemaType.VIRTUAL.name()));
         }
     }
 
     private void customizeAttrTOs(final Set<AttrTO> attrs, final 
CustomAttributesInfo customAttributesInfo) {
         if (customAttributesInfo != null
-                && customAttributesInfo.isShow()
                 && !customAttributesInfo.getAttributes().isEmpty()) {
 
             attrs.removeAll(attrs.stream().
                     filter(attr -> 
!customAttributesInfo.getAttributes().containsKey(attr.getSchema())).
                     collect(Collectors.toList()));
-        } else if (customAttributesInfo != null && 
!customAttributesInfo.isShow()) {
             attrs.clear();
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
index 26bad61..3cccb8f 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
@@ -66,10 +66,11 @@ public class UserSelfUpdateResource extends 
BaseUserSelfResource {
             }
 
             UserTO userTO = MAPPER.readValue(request.getReader().readLine(), 
UserTO.class);
-            Map<String, CustomAttributesInfo> customForm = 
SyncopeEnduserApplication.get().getCustomForm();
+            Map<String, CustomAttributesInfo> customFormAttributes =
+                    SyncopeEnduserApplication.get().getCustomFormAttributes();
 
             // check if request is compliant with customization form rules
-            if (UserRequestValidator.compliant(userTO, customForm, false)) {
+            if (UserRequestValidator.compliant(userTO, customFormAttributes, 
false)) {
                 // 1. membership attributes management
                 Set<AttrTO> membAttrs = new HashSet<>();
                 userTO.getPlainAttrs().stream().
@@ -146,7 +147,7 @@ public class UserSelfUpdateResource extends 
BaseUserSelfResource {
                 // get old user object from session
                 UserTO selfTO = SyncopeEnduserSession.get().getSelfTO();
                 // align "userTO" and "selfTO" objects
-                if (customForm != null && !customForm.isEmpty()) {
+                if (customFormAttributes != null && 
!customFormAttributes.isEmpty()) {
                     completeUserObject(userTO, selfTO);
                 }
                 // create diff patch

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java
 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java
index c5d348b..7772fcb 100644
--- 
a/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java
+++ 
b/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java
@@ -19,8 +19,10 @@
 package org.apache.syncope.client.enduser.util;
 
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.model.CustomAttribute;
 import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
+import org.apache.syncope.client.enduser.model.CustomTemplateInfo;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -35,26 +37,26 @@ public final class UserRequestValidator {
     private UserRequestValidator() {
     }
 
-    public static boolean compliant(final UserTO userTO, final Map<String, 
CustomAttributesInfo> customForm,
+    public static boolean compliant(final UserTO userTO, final Map<String, 
CustomAttributesInfo> customFormAttributes,
             final boolean checkDefaultValues) {
 
-        if (customForm == null || customForm.isEmpty()) {
+        if (customFormAttributes == null || customFormAttributes.isEmpty()) {
             return true;
         }
 
         return 
validateAttributes(EntityTOUtils.buildAttrMap(userTO.getPlainAttrs()),
-                customForm.get(SchemaType.PLAIN.name()), checkDefaultValues)
+                customFormAttributes.get(SchemaType.PLAIN.name()), 
checkDefaultValues)
                 && 
validateAttributes(EntityTOUtils.buildAttrMap(userTO.getDerAttrs()),
-                        customForm.get(SchemaType.DERIVED.name()), 
checkDefaultValues)
+                        customFormAttributes.get(SchemaType.DERIVED.name()), 
checkDefaultValues)
                 && 
validateAttributes(EntityTOUtils.buildAttrMap(userTO.getVirAttrs()),
-                        customForm.get(SchemaType.VIRTUAL.name()), 
checkDefaultValues);
+                        customFormAttributes.get(SchemaType.VIRTUAL.name()), 
checkDefaultValues);
     }
 
     private static boolean validateAttributes(final Map<String, AttrTO> 
attrMap,
             final CustomAttributesInfo customAttrInfo, final boolean 
checkDefaultValues) {
 
         return customAttrInfo == null
-                || (customAttrInfo.getAttributes().isEmpty() && 
customAttrInfo.isShow())
+                || customAttrInfo.getAttributes().isEmpty()
                 || attrMap.entrySet().stream().allMatch(entry -> {
                     String schemaKey = entry.getKey();
                     AttrTO attrTO = entry.getValue();
@@ -69,6 +71,21 @@ public final class UserRequestValidator {
 
     }
 
+    public static boolean validateSteps(final CustomTemplateInfo 
customTemplateInfo) {
+        return customTemplateInfo != null
+                && 
StringUtils.isNotBlank(customTemplateInfo.getWizard().getFirstStep())
+                && !customTemplateInfo.getWizard().getSteps().isEmpty();
+
+    }
+
+    public static boolean validateStep(final String stepName, final 
CustomTemplateInfo customTemplateInfo) {
+        return customTemplateInfo != null
+                && !customTemplateInfo.getWizard().getSteps().isEmpty()
+                && 
customTemplateInfo.getWizard().getSteps().containsKey(stepName)
+                && 
StringUtils.isNotBlank(customTemplateInfo.getWizard().getSteps().get(stepName).getUrl());
+
+    }
+
     private static boolean isValid(final AttrTO attrTO, final CustomAttribute 
customAttribute) {
         return customAttribute.isReadonly()
                 ? attrTO.getValues().stream().allMatch(value -> 
customAttribute.getDefaultValues().contains(value))

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/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
index a91d764..494fb66 100644
--- 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
@@ -17,7 +17,8 @@ specific language governing permissions and limitations
 under the License.
 */
 
-/* app general css stylesheet */
+/* App general css stylesheet
+============================================================================= 
*/
 
 .growl-container > .growl-item.ng-enter,
 .growl-container > .growl-item.ng-leave {
@@ -27,73 +28,73 @@ under the License.
   transition:1s linear all;
 }
 
-.k-notification-wrap{
-  white-space: normal !important;
-  word-wrap: break-word !important;
-  font-size: 12px;
-
+.disable-link {
+  pointer-events: none;
+  cursor: default;
 }
 
-.k-notification{
-  width : 320px;
-  font-size: 12px;
+.form-control:disabled, 
+.form-control[readonly] {
+  cursor: not-allowed;
 }
 
-.suggestions{
-  font-size: 10px;
-  display: inline-block;
-  margin-bottom: 5px;
+.card-container.card {
+  width: 350px;
+  padding: 40px 40px 0px;
 }
 
-#resetpassword{
-  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-left: 5px;
-  /*width: 15%;*/
-}
-#resetpassword:hover {
-  background: #658D5D;
-}
-#captchaImg{
-  display: block;
-  margin: 0 auto;
+.card {
+  background-color: #F7F7F7;
+  /* just in case there no content*/
+  padding: 20px 25px 30px;
+  margin: 0 auto 25px;
+  margin-top: 50px;
+  /* shadows and rounded borders */
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  border-radius: 2px;
+  -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);
 }
 
-.disable-link{
-  pointer-events: none;
-  cursor: default;
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.42857;
+  color: #555;
+  background-color: #FFF;
+  /*background-image: none;*/
+  border: 1px solid #CCC;
+  border-radius: 4px;
+  box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset;
+  transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 
0s;
 }
 
-.treasure-overlay-spinner-container{
-  z-index : 10001;
+#captchaButtons {
+  margin-top: 5%;
+  margin-bottom: 10px;
 }
 
-treasure-overlay-spinner .treasure-overlay-spinner-content {
-  height: 100%;
-}
-treasure-overlay-spinner {
-  height: 100%;
-  top: 0;
-  bottom: 0;
-  position: fixed;
-  right: 0;
-  left: 0;
-  overflow-y: auto;
+/* <!-- Useless with Bootstrap > 3 */
+.p-0 {
+  padding-left: 0 !important;
+  padding-right: 0 !important;
 }
 
-treasure-overlay-spinner .treasure-overlay-spinner {
-  position: fixed;
+.float-left {
+  float: left !important;
 }
-
-treasure-overlay-spinner .treasure-overlay-spinner-container {
-  position: fixed;
-  background: rgba(0, 0, 0, 0.5490196078431373);
+.float-right {
+  float: right !important;
 }
 
-treasure-overlay-spinner.treasure-overlay-spinner-active-remove{
-  transition: all 150ms ease-in 0s
+.btn-default {
+  color: #333;
+  background-color: #fff;
+  border-color: #ccc;
 }
+/* Useless with Bootstrap > 3 --> */
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/ac909f2e/client/enduser/src/main/resources/META-INF/resources/app/css/customSpinner.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/customSpinner.css
 
b/client/enduser/src/main/resources/META-INF/resources/app/css/customSpinner.css
new file mode 100644
index 0000000..bbfe1de
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/customSpinner.css
@@ -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.
+*/
+
+.treasure-overlay-spinner-container{
+  z-index : 10001;
+}
+
+treasure-overlay-spinner .treasure-overlay-spinner-content {
+  height: 100%;
+}
+treasure-overlay-spinner {
+  height: 100%;
+  top: 0;
+  bottom: 0;
+  position: fixed;
+  right: 0;
+  left: 0;
+  overflow-y: auto;
+}
+
+treasure-overlay-spinner .treasure-overlay-spinner {
+  position: fixed;
+  pointer-events: none;
+}
+
+treasure-overlay-spinner .treasure-overlay-spinner-container {
+  position: fixed;
+  background: rgba(0, 0, 0, 0.5490196078431373);
+}
+
+treasure-overlay-spinner.treasure-overlay-spinner-active-remove {
+  transition: all 150ms ease-in 0s;
+}
\ No newline at end of file

Reply via email to