You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by mm...@apache.org on 2020/10/28 10:12:54 UTC

[syncope] branch master updated: SYNCOPE-1595: Improve WA support for client app themes (#223)

This is an automated email from the ASF dual-hosted git repository.

mmoayyed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 879d919  SYNCOPE-1595: Improve WA support for client app themes (#223)
879d919 is described below

commit 879d919fdfd751210194ed319d426ddeb66ea693
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Wed Oct 28 14:12:44 2020 +0400

    SYNCOPE-1595: Improve WA support for client app themes (#223)
---
 .../syncope/common/lib/to/client/ClientAppTO.java  |  12 ++
 .../persistence/api/entity/auth/ClientApp.java     |   4 +
 .../jpa/entity/auth/AbstractClientApp.java         |  13 ++
 .../core/persistence/jpa/inner/SAML2SPTest.java    |   1 -
 .../java/data/ClientAppDataBinderImpl.java         | 199 +++++--------------
 .../resources/cas-theme-default.properties}        |  12 +-
 .../thymeleaf/templates/fragments/header.html      | 217 ---------------------
 .../main/resources/thymeleaf/templates/layout.html |  61 ------
 .../src/test/resources/debug/keymaster.properties  |   2 +-
 wa/starter/src/test/resources/debug/wa.properties  |   5 +
 10 files changed, 89 insertions(+), 437 deletions(-)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
index 8700c8c..60f220c 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
@@ -53,6 +53,8 @@ public abstract class ClientAppTO implements EntityTO {
 
     private String attrReleasePolicy;
 
+    private String theme;
+
     @Schema(name = "_class", required = true)
     public abstract String getDiscriminator();
 
@@ -84,6 +86,14 @@ public abstract class ClientAppTO implements EntityTO {
         this.authPolicy = authPolicy;
     }
 
+    public String getTheme() {
+        return theme;
+    }
+
+    public void setTheme(final String theme) {
+        this.theme = theme;
+    }
+
     @Override
     public String getKey() {
         return key;
@@ -130,6 +140,7 @@ public abstract class ClientAppTO implements EntityTO {
                 .append(authPolicy)
                 .append(accessPolicy)
                 .append(attrReleasePolicy)
+                .append(theme)
                 .toHashCode();
     }
 
@@ -154,6 +165,7 @@ public abstract class ClientAppTO implements EntityTO {
                 .append(this.authPolicy, rhs.authPolicy)
                 .append(this.accessPolicy, rhs.accessPolicy)
                 .append(this.attrReleasePolicy, rhs.attrReleasePolicy)
+                .append(this.theme, rhs.theme)
                 .isEquals();
     }
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
index 70bf7b7..31d38e7 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
@@ -53,4 +53,8 @@ public interface ClientApp extends Entity {
     Realm getRealm();
 
     void setRealm(Realm realm);
+
+    void setTheme(String name);
+
+    String getTheme();
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
index ef09634..611f203 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
@@ -47,6 +47,9 @@ public class AbstractClientApp extends AbstractGeneratedKeyEntity implements Cli
     @Column
     private String description;
 
+    @Column
+    private String theme;
+
     @ManyToOne(fetch = FetchType.EAGER)
     private JPARealm realm;
 
@@ -132,4 +135,14 @@ public class AbstractClientApp extends AbstractGeneratedKeyEntity implements Cli
         checkType(realm, JPARealm.class);
         this.realm = (JPARealm) realm;
     }
+
+    @Override
+    public String getTheme() {
+        return theme;
+    }
+
+    @Override
+    public void setTheme(final String theme) {
+        this.theme = theme;
+    }
 }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java
index 6df8cea..f2a083a 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.core.persistence.jpa.inner;
 
-import org.apache.openjpa.jdbc.kernel.exps.Lit;
 import org.apache.syncope.common.lib.XmlSecAlgorithms;
 import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
index 71f60fc..9b51674 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
@@ -78,14 +78,15 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
     @SuppressWarnings("unchecked")
     public <T extends ClientAppTO> T getClientAppTO(final ClientApp clientApp) {
         if (clientApp instanceof SAML2SP) {
-            return (T) getClientAppTO((SAML2SP) clientApp);
-        } else if (clientApp instanceof OIDCRP) {
-            return (T) getClientAppTO((OIDCRP) clientApp);
-        } else if (clientApp instanceof CASSP) {
-            return (T) getClientAppTO((CASSP) clientApp);
-        } else {
-            throw new IllegalArgumentException("Unsupported client app: " + clientApp.getClass().getName());
+            return (T) getSAMLClientAppTO((SAML2SP) clientApp);
+        } 
+        if (clientApp instanceof OIDCRP) {
+            return (T) getOIDCClientAppTO((OIDCRP) clientApp);
+        } 
+        if (clientApp instanceof CASSP) {
+            return (T) getCASClientAppTO((CASSP) clientApp);
         }
+        throw new IllegalArgumentException("Unsupported client app: " + clientApp.getClass().getName());
     }
 
     private SAML2SP doCreate(final SAML2SPTO clientAppTO) {
@@ -101,9 +102,8 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
     }
 
     private void doUpdate(final SAML2SP clientApp, final SAML2SPTO clientAppTO) {
-        clientApp.setDescription(clientAppTO.getDescription());
-        clientApp.setName(clientAppTO.getName());
-        clientApp.setClientAppId(clientAppTO.getClientAppId());
+        doUpdateCommon(clientApp, clientAppTO);
+
         clientApp.setEntityId(clientAppTO.getEntityId());
         clientApp.setMetadataLocation(clientAppTO.getMetadataLocation());
         clientApp.setMetadataSignatureLocation(clientAppTO.getMetadataSignatureLocation());
@@ -125,57 +125,12 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
 
         clientApp.setSigningSignatureBlackListedAlgorithms(clientAppTO.getSigningSignatureBlackListedAlgorithms());
         clientApp.setEncryptionBlackListedAlgorithms(clientAppTO.getEncryptionBlackListedAlgorithms());
-
-        if (clientAppTO.getAuthPolicy() == null) {
-            clientApp.setAuthPolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAuthPolicy());
-            if (policy instanceof AuthPolicy) {
-                clientApp.setAuthPolicy((AuthPolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
-                        + ", found " + policy.getClass().getSimpleName());
-                throw sce;
-            }
-        }
-
-        if (clientAppTO.getAccessPolicy() == null) {
-            clientApp.setAccessPolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAccessPolicy());
-            if (policy instanceof AccessPolicy) {
-                clientApp.setAccessPolicy((AccessPolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
-                        + ", found " + policy.getClass().getSimpleName());
-                throw sce;
-            }
-        }
-
-        if (clientAppTO.getAttrReleasePolicy() == null) {
-            clientApp.setAttrReleasePolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAttrReleasePolicy());
-            if (policy instanceof AttrReleasePolicy) {
-                clientApp.setAttrReleasePolicy((AttrReleasePolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
-                        + ", found " + policy.getClass().getSimpleName());
-                throw sce;
-            }
-        }
     }
 
-    private static SAML2SPTO getClientAppTO(final SAML2SP clientApp) {
+    private static SAML2SPTO getSAMLClientAppTO(final SAML2SP clientApp) {
         SAML2SPTO clientAppTO = new SAML2SPTO();
+        updateCommonClientAppTO(clientApp, clientAppTO);
 
-        clientAppTO.setName(clientApp.getName());
-        clientAppTO.setKey(clientApp.getKey());
-        clientAppTO.setDescription(clientApp.getDescription());
-        clientAppTO.setClientAppId(clientApp.getClientAppId());
         clientAppTO.setEntityId(clientApp.getEntityId());
         clientAppTO.setMetadataLocation(clientApp.getMetadataLocation());
         clientAppTO.setMetadataSignatureLocation(clientApp.getMetadataSignatureLocation());
@@ -198,13 +153,6 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         clientAppTO.setSigningSignatureBlackListedAlgorithms(clientApp.getSigningSignatureBlackListedAlgorithms());
         clientAppTO.setEncryptionBlackListedAlgorithms(clientApp.getEncryptionBlackListedAlgorithms());
 
-        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
-                ? null : clientApp.getAuthPolicy().getKey());
-        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
-                ? null : clientApp.getAccessPolicy().getKey());
-        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
-                ? null : clientApp.getAttrReleasePolicy().getKey());
-
         return clientAppTO;
     }
 
@@ -215,9 +163,8 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
     }
 
     private void doUpdate(final OIDCRP clientApp, final OIDCRPTO clientAppTO) {
-        clientApp.setName(clientAppTO.getName());
-        clientApp.setClientAppId(clientAppTO.getClientAppId());
-        clientApp.setDescription(clientAppTO.getDescription());
+        doUpdateCommon(clientApp, clientAppTO);
+
         clientApp.setClientSecret(clientAppTO.getClientSecret());
         clientApp.setClientId(clientAppTO.getClientId());
         clientApp.setSignIdToken(clientAppTO.isSignIdToken());
@@ -226,70 +173,13 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         clientApp.getSupportedGrantTypes().addAll(clientAppTO.getSupportedGrantTypes());
         clientApp.getSupportedResponseTypes().addAll(clientAppTO.getSupportedResponseTypes());
 
-        if (clientAppTO.getAuthPolicy() == null) {
-            clientApp.setAuthPolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAuthPolicy());
-            if (policy instanceof AuthPolicy) {
-                clientApp.setAuthPolicy((AuthPolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                if (policy == null) {
-                    sce.getElements().add("Policy " + clientAppTO.getAuthPolicy() + " not found");
-                } else {
-                    sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
-                            + ", found " + policy.getClass().getSimpleName());
-                }
-                throw sce;
-            }
-        }
-
-        if (clientAppTO.getAccessPolicy() == null) {
-            clientApp.setAccessPolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAccessPolicy());
-            if (policy instanceof AccessPolicy) {
-                clientApp.setAccessPolicy((AccessPolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                if (policy == null) {
-                    sce.getElements().add("Policy " + clientAppTO.getAccessPolicy() + " not found");
-                } else {
-                    sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
-                            + ", found " + policy.getClass().getSimpleName());
-                }
-                throw sce;
-            }
-        }
-
-        if (clientAppTO.getAttrReleasePolicy() == null) {
-            clientApp.setAttrReleasePolicy(null);
-        } else {
-            Policy policy = policyDAO.find(clientAppTO.getAttrReleasePolicy());
-            if (policy instanceof AttrReleasePolicy) {
-                clientApp.setAttrReleasePolicy((AttrReleasePolicy) policy);
-            } else {
-                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
-                if (policy == null) {
-                    sce.getElements().add("Policy " + clientAppTO.getAttrReleasePolicy() + " not found");
-                } else {
-                    sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
-                            + ", found " + policy.getClass().getSimpleName());
-                }
-                throw sce;
-            }
-        }
-
         clientApp.setLogoutUri(clientAppTO.getLogoutUri());
     }
 
-    private static OIDCRPTO getClientAppTO(final OIDCRP clientApp) {
+    private static OIDCRPTO getOIDCClientAppTO(final OIDCRP clientApp) {
         OIDCRPTO clientAppTO = new OIDCRPTO();
+        updateCommonClientAppTO(clientApp, clientAppTO);
 
-        clientAppTO.setName(clientApp.getName());
-        clientAppTO.setKey(clientApp.getKey());
-        clientAppTO.setDescription(clientApp.getDescription());
-        clientAppTO.setClientAppId(clientApp.getClientAppId());
         clientAppTO.setClientId(clientApp.getClientId());
         clientAppTO.setClientSecret(clientApp.getClientSecret());
         clientAppTO.setSignIdToken(clientApp.isSignIdToken());
@@ -297,24 +187,44 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         clientAppTO.getRedirectUris().addAll(clientApp.getRedirectUris());
         clientAppTO.getSupportedGrantTypes().addAll(clientApp.getSupportedGrantTypes());
         clientAppTO.getSupportedResponseTypes().addAll(clientApp.getSupportedResponseTypes());
-
-        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
-                ? null : clientApp.getAuthPolicy().getKey());
-        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
-                ? null : clientApp.getAccessPolicy().getKey());
-        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
-                ? null : clientApp.getAttrReleasePolicy().getKey());
-
         clientAppTO.setLogoutUri(clientApp.getLogoutUri());
 
         return clientAppTO;
     }
 
     private void doUpdate(final CASSP clientApp, final CASSPTO clientAppTO) {
+        doUpdateCommon(clientApp, clientAppTO);
+
+        clientApp.setServiceId(clientAppTO.getServiceId());
+    }
+
+    private static CASSPTO getCASClientAppTO(final CASSP clientApp) {
+        CASSPTO clientAppTO = new CASSPTO();
+        updateCommonClientAppTO(clientApp, clientAppTO);
+        clientAppTO.setServiceId(clientApp.getServiceId());
+        return clientAppTO;
+    }
+
+    private static void updateCommonClientAppTO(final ClientApp clientApp, final ClientAppTO clientAppTO) {
+        clientAppTO.setName(clientApp.getName());
+        clientAppTO.setKey(clientApp.getKey());
+        clientAppTO.setDescription(clientApp.getDescription());
+        clientAppTO.setClientAppId(clientApp.getClientAppId());
+        clientAppTO.setTheme(clientApp.getTheme());
+
+        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
+            ? null : clientApp.getAuthPolicy().getKey());
+        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
+            ? null : clientApp.getAccessPolicy().getKey());
+        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
+            ? null : clientApp.getAttrReleasePolicy().getKey());
+    }
+
+    private void doUpdateCommon(final ClientApp clientApp, final ClientAppTO clientAppTO) {
         clientApp.setName(clientAppTO.getName());
         clientApp.setClientAppId(clientAppTO.getClientAppId());
         clientApp.setDescription(clientAppTO.getDescription());
-        clientApp.setServiceId(clientAppTO.getServiceId());
+        clientApp.setTheme(clientAppTO.getTheme());
 
         if (clientAppTO.getAuthPolicy() == null) {
             clientApp.setAuthPolicy(null);
@@ -358,23 +268,4 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
             }
         }
     }
-
-    private static CASSPTO getClientAppTO(final CASSP clientApp) {
-        CASSPTO clientAppTO = new CASSPTO();
-
-        clientAppTO.setName(clientApp.getName());
-        clientAppTO.setKey(clientApp.getKey());
-        clientAppTO.setDescription(clientApp.getDescription());
-        clientAppTO.setClientAppId(clientApp.getClientAppId());
-        clientAppTO.setServiceId(clientApp.getServiceId());
-
-        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
-            ? null : clientApp.getAuthPolicy().getKey());
-        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
-            ? null : clientApp.getAccessPolicy().getKey());
-        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
-            ? null : clientApp.getAttrReleasePolicy().getKey());
-
-        return clientAppTO;
-    }
 }
diff --git a/wa/starter/src/test/resources/debug/keymaster.properties b/wa/starter/src/main/resources/cas-theme-default.properties
similarity index 79%
copy from wa/starter/src/test/resources/debug/keymaster.properties
copy to wa/starter/src/main/resources/cas-theme-default.properties
index 4642f9b..8b48e4d 100644
--- a/wa/starter/src/test/resources/debug/keymaster.properties
+++ b/wa/starter/src/main/resources/cas-theme-default.properties
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,6 +15,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-keymaster.address=http://localhost:9081/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
+#
+
+cas.theme.name=Apache Syncope
+cas.standard.css.file=/css/cas.css
+cas.standard.js.file=/js/cas.js
+cas.logo.file=/images/logo.png
+cas.favicon.file=/images/favicon.png
+cas.drawer-menu.enabled=false
diff --git a/wa/starter/src/main/resources/thymeleaf/templates/fragments/header.html b/wa/starter/src/main/resources/thymeleaf/templates/fragments/header.html
deleted file mode 100644
index f128dc9..0000000
--- a/wa/starter/src/main/resources/thymeleaf/templates/fragments/header.html
+++ /dev/null
@@ -1,217 +0,0 @@
-<!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.
--->
-<html lang="en">
-
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-
-    <title>Header Fragment</title>
-    <link href="../../static/css/cas.css" rel="stylesheet" th:remove="tag" />
-</head>
-
-<body>
-    <div th:fragment="header">
-        <header id="app-bar" class="mdc-top-app-bar mdc-top-app-bar--fixed mdc-elevation--z4">
-            <nav class="mdc-top-app-bar__row">
-                <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
-                    <!--<button class="mdc-icon-button mdc-top-app-bar__navigation-icon">
-                        <span class="mdi mdi-menu"></span>
-                        <span class="sr-only">menu</span>
-                    </button>-->
-                </section>
-                <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-center">
-                    <span class="cas-brand mx-auto">
-                        <span class="sr-only">Apache Syncope</span>
-                        <img th:src="@{'/images/logo.png'}" />
-                    </span>
-                </section>
-                <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end">
-                    <button id="cas-notifications-menu"
-                        class="mdc-icon-button mdc-top-app-bar__action-item cas-notification-toggle"
-                        aria-label="Bookmark this page">
-                        <span class="mdi mdi-bell-alert"></span>
-                        <span class="sr-only">notifications</span>
-                        <i id="notifications-count" class="notifications-count count">2</i>
-                    </button>
-                </section>
-            </nav>
-
-        </header>
-        <aside id="app-drawer" class="mdc-drawer mdc-drawer--dismissible mdc-drawer--modal">
-            <div class="mdc-drawer__header">
-                <h3 class="mdc-drawer__title">CAS</h3>
-                <h6 class="mdc-drawer__subtitle">Central Authentication Service</h6>
-            </div>
-            <div class="mdc-drawer__content">
-                <nav class="mdc-list">
-                    <a th:href="@{/actuator}" class="mdc-list-item">
-                        <i class="mdi mdi-cogs"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.endpoints}]]</span>
-                    </a>
-                    <hr class="mdc-list-divider" />
-                    <a class="mdc-list-item" href="https://apereo.github.io/cas">
-                        <i class="mdi mdi-file-code-outline"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.wiki}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://github.com/apereo/cas/pulls">
-                        <i class="mdi mdi-call-merge"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.pulls}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://apereo.github.io/cas/developer/Contributor-Guidelines.html">
-                        <i class="mdi mdi-information"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.contribguide}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://apereo.github.io/cas/Support.html">
-                        <i class="mdi mdi-help-circle"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.support}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://apereo.github.io/cas/Mailing-Lists.html">
-                        <i class="mdi mdi-email-newsletter"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.mailinglist}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://gitter.im/apereo/cas">
-                        <i class="mdi mdi-message-text"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.chat}]]</span>
-                    </a>
-                    <a class="mdc-list-item" href="https://apereo.github.io/">
-                        <i class="mdi mdi-post"></i>&nbsp;
-                        <span class="mdc-list-item__text">[[#{cas.login.resources.blog}]]</span>
-                    </a>
-                </nav>
-            </div>
-        </aside>
-
-        <script>var countMessages = 0;</script>
-        <div class="mdc-dialog cas-notification-dialog" id="cas-notification-dialog" role="alertdialog"
-            aria-modal="true" aria-labelledby="notif-dialog-title" aria-describedby="notif-dialog-content">
-            <div class="mdc-dialog__container">
-                <div class="mdc-dialog__surface">
-                    <h2 class="mdc-dialog__title mt-2" id="notif-dialog-title">
-                        Notifications
-                    </h2>
-                    <div class="mdc-dialog__content" id="notif-dialog-content">
-                        <div class="cas-notification-message mdc-typography--body1" th:if="${staticAuthentication}">
-                            <script>countMessages++;</script>
-                            <h5 class="mdc-typography--headline6" th:utext="#{screen.defaultauthn.title}" />
-                            <p class="text text-wrap small" th:utext="#{screen.defaultauthn.heading}">
-                                Static AuthN is ONLY useful for demo purposes. It is recommended that you connect CAS to
-                                LDAP,
-                                JDBC, etc
-                                instead.
-                            </p>
-                        </div>
-                        <div class="cas-notification-message mdc-typography--body1"
-                            th:if="${not #httpServletRequest.secure}">
-                            <h5 class="mdc-typography--headline6" th:utext="#{screen.nonsecure.title}" />
-                            <script>countMessages++;</script>
-                            <p class="text-wrap small" th:utext="#{screen.nonsecure.message}">
-                                Unsure Connection
-                            </p>
-                        </div>
-                    </div>
-                    <footer class="mdc-dialog__actions">
-                        <button type="button" class="mdc-button mdc-dialog__button" data-mdc-dialog-action="accept"
-                            data-mdc-dialog-button-default>
-                            <span class="mdc-button__label">OK</span>
-                        </button>
-                    </footer>
-                </div>
-            </div>
-            <div class="mdc-dialog__scrim"></div>
-        </div>
-        <script type="text/javascript">
-            
-            (function (material) {
-                var header = {
-                    init: function () {
-                        header.attachTopbar();
-                        material.autoInit();
-                    },
-                    attachDrawer: function () {
-                        var elm = document.getElementById('app-drawer');
-                        var drawer = material.drawer.MDCDrawer.attachTo(elm);
-                        var closeDrawer = function (evt) {
-                            drawer.open = false;
-                        };
-                        drawer.foundation_.handleScrimClick = closeDrawer;
-                        document.onkeydown = function (evt) {
-                            evt = evt || window.event;
-                            if (evt.keyCode == 27) {
-                                closeDrawer();
-                            }
-                        };
-
-                        header.drawer = drawer;
-
-                        return drawer;
-                    },
-                    attachTopbar: function (drawer) {
-                        var drawer = header.attachDrawer();
-                        var dialog = header.attachNotificationDialog();
-                        header.attachDrawerToggle(drawer);
-                        header.attachNotificationToggle(dialog);
-                    },
-                    checkCaps: function (ev) {
-                        var s = String.fromCharCode(ev.which);
-                        if (s.toUpperCase() === s && s.toLowerCase() !== s && !ev.shiftKey) {
-                            ev.target.parentElement.classList.add('caps-on');
-                        } else {
-                            ev.target.parentElement.classList.remove('caps-on');
-                        }
-                    },
-                    attachDrawerToggle: function (drawer) {
-                        var topAppBar = material.topAppBar.MDCTopAppBar.attachTo(document.getElementById('app-bar'));
-                        topAppBar.setScrollTarget(document.getElementById('main-content'));
-                        topAppBar.listen('MDCTopAppBar:nav', function () {
-                            drawer.open = !drawer.open;
-                        });
-                        return topAppBar;
-                    },
-                    attachNotificationDialog: function () {
-                        var element = document.getElementById('cas-notification-dialog');
-                        var dialog = material.dialog.MDCDialog.attachTo(element);
-                        return dialog;
-                    },
-                    attachNotificationToggle: function (dialog) {
-                        var btn = document.getElementById('cas-notifications-menu');
-                        btn.addEventListener('click', () => {
-                            dialog.open();
-                        });
-                    }
-                }
-
-
-                document.addEventListener('DOMContentLoaded', function () {
-                    header.init();
-
-                    if (countMessages == 0) {
-                        window.jQuery('#notifications-count').remove();
-                    } else {
-                        window.jQuery('#notifications-count').text("(" + countMessages + ")")
-                    }
-                });
-            })(mdc);
-        </script>
-    </div>
-</body>
-
-</html>
diff --git a/wa/starter/src/main/resources/thymeleaf/templates/layout.html b/wa/starter/src/main/resources/thymeleaf/templates/layout.html
deleted file mode 100644
index ad83fbc..0000000
--- a/wa/starter/src/main/resources/thymeleaf/templates/layout.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!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.
--->
-<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
-
-<head>
-    <meta charset="UTF-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-
-    <title layout:title-pattern="$LAYOUT_TITLE">Apache Syncope</title>
-
-    <!--/* Core CAS CSS */-->
-    <link rel="stylesheet" type="text/css" href="../static/css/normalize.css" th:href="@{#{webjars.normalize.css}}" />
-    <link rel="stylesheet" type="text/css" href="../static/css/bootstrap-grid.min.css" th:href="@{#{webjars.bootstrap-grid.css}}" />
-    <link rel="stylesheet" type="text/css" href="../static/css/material-components-web.min.css" th:href="@{#{webjars.material-components.css}}" />
-    <link rel="stylesheet" type="text/css" href="../static/css/mdi-font.css" th:href="@{#{webjars.mdi-font.css}}" />
-    <link rel="stylesheet" type="text/css" href="../static/css/cas.css" th:href="@{${#themes.code('cas.standard.css.file')}}"/>
-
-    <link rel="shortcut icon" th:href="@{'/images/favicon.png'}" type="image/png"/>
-</head>
-
-<body>
-<script th:replace="fragments/scripts" />
-
-<div th:replace="fragments/header :: header">
-    <a href="fragments/header.html">Header</a> fragment will go here
-</div>
-
-<div class="mdc-drawer-scrim"></div>
-
-<div class="mdc-drawer-app-content mdc-top-app-bar--fixed-adjust d-flex justify-content-center">
-    <main role="main" id="main-content" class="container-lg py-4">
-        <div layout:fragment="content" id="content">
-            CAS content will go here
-        </div>
-    </main>
-</div>
-
-<div th:replace="fragments/footer :: footer">
-    <a href="fragments/footer.html">Footer</a> fragment will go here
-</div>
-
-</body>
-</html>
diff --git a/wa/starter/src/test/resources/debug/keymaster.properties b/wa/starter/src/test/resources/debug/keymaster.properties
index 4642f9b..033fe3b 100644
--- a/wa/starter/src/test/resources/debug/keymaster.properties
+++ b/wa/starter/src/test/resources/debug/keymaster.properties
@@ -14,6 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-keymaster.address=http://localhost:9081/syncope/rest/keymaster
+keymaster.address=http://localhost:9080/syncope/rest/keymaster
 keymaster.username=${anonymousUser}
 keymaster.password=${anonymousKey}
diff --git a/wa/starter/src/test/resources/debug/wa.properties b/wa/starter/src/test/resources/debug/wa.properties
index adac573..33d19df 100644
--- a/wa/starter/src/test/resources/debug/wa.properties
+++ b/wa/starter/src/test/resources/debug/wa.properties
@@ -60,3 +60,8 @@ springdoc.show-actuator=true
 springdoc.model-and-view-allowed=true
 springdoc.writer-with-default-pretty-printer=true
 springdoc.swagger-ui.displayRequestDuration=true
+
+cas.authn.mfa.web-authn.allowed-origins=http://localhost:8080
+cas.authn.mfa.web-authn.application-id=https://localhost:8443
+cas.authn.mfa.web-authn.relying-party-name=Syncope 
+cas.authn.mfa.web-authn.relying-party-id=syncope.apache.org