[java-identity-provider] 01/01: POC demonstrating the use of Thymeleaf for view rendering.
Marvin S. Addison
marvin.addison at gmail.com
Tue Dec 12 14:08:33 UTC 2023
This is an automated email from the git hooks/post-receive script.
serac pushed a commit to branch dev/thymeleaf
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=630c77aeaf2fa25683d139333e8e53bbdcd9e93d
commit 630c77aeaf2fa25683d139333e8e53bbdcd9e93d
Author: Marvin S. Addison <serac at vt.edu>
AuthorDate: Tue Dec 12 09:07:22 2023 -0500
POC demonstrating the use of Thymeleaf for view rendering.
---
.../authn/context/AuthenticationErrorContext.java | 27 +++-
.../impl/ExtractAuthenticationErrorMessage.java | 59 +++++++++
idp-conf-impl/pom.xml | 7 ++
.../net/shibboleth/idp/conf/mvc-beans.xml | 35 +++++-
.../net/shibboleth/idp/conf/webflow-config.xml | 1 +
.../idp/flows/authn/password-authn-beans.xml | 3 +
.../idp/flows/authn/password-authn-flow.xml | 7 ++
.../idp/module/authn/impl/module.properties | 22 ++--
.../net/shibboleth/idp/module/views/login-error.vm | 24 ----
.../net/shibboleth/idp/module/views/login.html | 138 +++++++++++++++++++++
.../net/shibboleth/idp/module/views/login.vm | 109 ----------------
.../net/shibboleth/idp/views/csrf/csrf.html | 10 ++
.../net/shibboleth/idp/module/conf/idp.properties | 2 +-
idp-distribution/pom.xml | 52 ++++----
pom.xml | 6 +
15 files changed, 326 insertions(+), 176 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationErrorContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationErrorContext.java
index 769fe3397..05ce61a53 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationErrorContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationErrorContext.java
@@ -45,7 +45,11 @@ public final class AuthenticationErrorContext extends BaseContext {
/** Error conditions detected through classified error messages. */
@Nonnull private Collection<String> classifiedErrors;
-
+
+ /** Error message to be displayed to user. */
+ @Nullable private String displayErrorMessage;
+
+
/** Constructor. */
public AuthenticationErrorContext() {
exceptions = new ArrayList<>();
@@ -107,5 +111,24 @@ public final class AuthenticationErrorContext extends BaseContext {
@Nullable public String getLastClassifiedError() {
return classifiedErrors.stream().reduce((first, second) -> second).orElse(null);
}
-
+
+ /**
+ * @return Error message to be displayed to the user.
+ *
+ * @since 5.1.0
+ */
+ @Nullable public String getDisplayErrorMessage() {
+ return displayErrorMessage;
+ }
+
+ /**
+ * Sets the error message to be displayed to the user.
+ *
+ * @param message Error message
+ *
+ * @since 5.1.0
+ */
+ public void setDisplayErrorMessage(@Nullable String message) {
+ this.displayErrorMessage = message;
+ }
}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExtractAuthenticationErrorMessage.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExtractAuthenticationErrorMessage.java
new file mode 100644
index 000000000..05012ad2b
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExtractAuthenticationErrorMessage.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed 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 net.shibboleth.idp.authn.impl;
+
+import java.util.Locale;
+import java.util.function.Function;
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
+import org.jetbrains.annotations.NotNull;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+/**
+ * Responsible for producing a single error message to be displayed on the login form on authentication failure.
+ * Supersedes the Velocity template login-error.vm from previous IdP versions.
+ *
+ * @since 5.1.0
+ */
+public class ExtractAuthenticationErrorMessage extends AbstractAuthenticationAction {
+
+ private static final Object[] NO_ARGS = new Object[0];
+
+ private Function<AuthenticationContext, AuthenticationErrorContext> errorContextLookup =
+ new ChildContextLookup<>(AuthenticationErrorContext.class);
+
+ @Override
+ protected boolean doPreExecute(@NotNull ProfileRequestContext profileRequestContext, @NotNull AuthenticationContext authenticationContext) {
+ return errorContextLookup.apply(authenticationContext) != null;
+ }
+
+ @Override
+ protected void doExecute(@NotNull ProfileRequestContext profileRequestContext, @NotNull AuthenticationContext authenticationContext) {
+ final AuthenticationErrorContext errorContext = errorContextLookup.apply(authenticationContext);
+ final String eventId = errorContext.getClassifiedErrors().iterator().next();
+ final Locale locale = Locale.getDefault();
+ String message = "Authentication failure";
+ if (!"ReselectFlow".equals(eventId)) {
+ final String eventKey = getMessage(eventId, NO_ARGS, "authn", locale);
+ message = getMessage(eventKey + ".message", NO_ARGS, "Login Failure: " + eventId, locale);
+ } else if (!errorContext.getExceptions().isEmpty()) {
+ final Exception ex = errorContext.getExceptions().get(0);
+ message = ex.getMessage() != null ? "Login Failure: " + ex.getMessage() : ex.toString();
+ }
+ errorContext.setDisplayErrorMessage(message);
+ }
+}
diff --git a/idp-conf-impl/pom.xml b/idp-conf-impl/pom.xml
index a32320996..f500cb88f 100644
--- a/idp-conf-impl/pom.xml
+++ b/idp-conf-impl/pom.xml
@@ -26,6 +26,7 @@
<artifactId>idp-admin-api</artifactId>
<version>${project.version}</version>
</dependency>
+
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>idp-admin-impl</artifactId>
@@ -162,6 +163,12 @@
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf-spring6</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
<!-- Test Dependencies -->
<dependency>
<groupId>${project.groupId}</groupId>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
index 35e087f1c..30fb5a6f0 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/mvc-beans.xml
@@ -79,9 +79,16 @@
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1" />
</bean>
+
+ <bean id="shibboleth.ThymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
+ <property name="templateEngine" ref="thymeleafTemplateEngine" />
+ <property name="order" value="2" />
+ <property name="viewNames" value="login" />
+ <property name="contentType" value="text/html;charset=utf-8" />
+ </bean>
<bean id="shibboleth.VelocityViewResolver" class="net.shibboleth.shared.spring.velocity.VelocityViewResolver">
- <property name="order" value="2" />
+ <property name="order" value="3" />
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".vm"/>
@@ -89,7 +96,7 @@
</bean>
<bean id="shibboleth.InternalViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="order" value="3" />
+ <property name="order" value="4" />
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
@@ -101,6 +108,30 @@
<property name="resourceLoaderPath"
value="#{'%{idp.views:%{idp.home}/views}'.trim()},classpath:/net/shibboleth/idp/views,classpath:/META-INF/net/shibboleth/idp/views" />
</bean>
+
+ <bean id="thymeleafTemplateEngine" class="org.thymeleaf.spring6.SpringTemplateEngine">
+ <property name="templateResolvers">
+ <util:set>
+ <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
+ <property name="name" value="deployerViews" />
+ <property name="checkExistence" value="true"/>
+ <property name="cacheable" value="true"/>
+ <property name="templateMode" value="HTML" />
+ <property name="prefix" value="#{'%{idp.views:%{idp.home}/views}'.trim()}/"/>
+ <property name="suffix" value=".html"/>
+ </bean>
+ <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
+ <property name="name" value="classpathViews" />
+ <property name="checkExistence" value="true"/>
+ <property name="cacheable" value="true"/>
+ <property name="templateMode" value="HTML" />
+ <property name="prefix" value="classpath:/net/shibboleth/idp/views/"/>
+ <property name="suffix" value=".html"/>
+ </bean>
+ </util:set>
+ </property>
+ <property name="enableSpringELCompiler" value="true" />
+ </bean>
<!-- Import any user defined beans or overrides for the MVC config. -->
<import resource="conditional:${idp.home}/conf/mvc-beans.xml" />
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
index 4904f48e9..954aee17b 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
@@ -202,6 +202,7 @@
p:useSpringBeanBinding="true">
<property name="viewResolvers">
<list>
+ <ref bean="shibboleth.ThymeleafViewResolver" />
<ref bean="shibboleth.VelocityViewResolver" />
<ref bean="shibboleth.InternalViewResolver" />
</list>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-beans.xml
index f399f51d1..22466994c 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-beans.xml
@@ -49,6 +49,9 @@
p:usernameFieldName="#{getObject('shibboleth.authn.Password.UsernameFieldName') ?: '%{idp.authn.Password.usernameFieldName:j_username}'.trim()}"
p:passwordFieldName="#{getObject('shibboleth.authn.Password.PasswordFieldName') ?: '%{idp.authn.Password.passwordFieldName:j_password}'.trim()}"
p:SSOBypassFieldName="#{getObject('shibboleth.authn.Password.SSOBypassFieldName') ?: '%{idp.authn.Password.ssoBypassFieldName:donotcache}'.trim()}" />
+
+ <bean id="ExtractAuthenticationErrorMessage"
+ class="net.shibboleth.idp.authn.impl.ExtractAuthenticationErrorMessage" scope="prototype" />
<bean id="PopulateSubjectCanonicalizationContext"
class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-flow.xml
index c5b103849..33e7957e1 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/authn/password-authn-flow.xml
@@ -27,11 +27,13 @@
<on-render>
<evaluate expression="environment" result="viewScope.environment" />
<evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.profile.context.RelyingPartyContext))" result="viewScope.rpContext" />
<evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext" />
<evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="viewScope.rpUIContext" />
<evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="viewScope.authenticationErrorContext" />
<evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="viewScope.authenticationWarningContext" />
<evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.LDAPResponseContext))" result="viewScope.ldapResponseContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.UsernamePasswordContext), true).getUsername()" result="viewScope.username" />
<evaluate expression="T(net.shibboleth.shared.codec.HTMLEncoder)" result="viewScope.encoder" />
<evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPDigester')" result="viewScope.cspDigester" />
<evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CSPNonce')" result="viewScope.cspNonce" />
@@ -65,6 +67,11 @@
<transition on="#{ opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).isPassive() || !opensamlProfileRequestContext.isBrowserProfile() }" to="ReselectFlow" />
<!-- Other event transitions are determined by deployer in /flows/authn/conditions/conditions-flow.xml -->
+
+ <on-exit>
+ <!-- Safe to execute in all cases -->
+ <evaluate expression="ExtractAuthenticationErrorMessage" />
+ </on-exit>
</action-state>
<action-state id="ContinueSuccessfulAuthentication">
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/authn/impl/module.properties b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/authn/impl/module.properties
index 91e0b1ef2..95882593a 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/authn/impl/module.properties
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/authn/impl/module.properties
@@ -25,18 +25,16 @@ idp.authn.Password.desc = Login flow for pluggable password-based authentication
idp.authn.Password.url = /pages/3199505587/PasswordAuthnConfiguration
idp.authn.Password.1.src = /net/shibboleth/idp/module/conf/authn/password-authn-config.xml
idp.authn.Password.1.dest = conf/authn/password-authn-config.xml
-idp.authn.Password.2.src = /net/shibboleth/idp/module/views/login.vm
-idp.authn.Password.2.dest = views/login.vm
-idp.authn.Password.3.src = /net/shibboleth/idp/module/views/login-error.vm
-idp.authn.Password.3.dest = views/login-error.vm
-idp.authn.Password.4.src = /net/shibboleth/idp/module/flows/authn/conditions/conditions-flow.xml
-idp.authn.Password.4.dest = flows/authn/conditions/conditions-flow.xml
-idp.authn.Password.5.src = /net/shibboleth/idp/module/flows/authn/conditions/account-locked/account-locked-flow.xml
-idp.authn.Password.5.dest = flows/authn/conditions/account-locked/account-locked-flow.xml
-idp.authn.Password.6.src = /net/shibboleth/idp/module/flows/authn/conditions/expired-password/expired-password-flow.xml
-idp.authn.Password.6.dest = flows/authn/conditions/expired-password/expired-password-flow.xml
-idp.authn.Password.7.src = /net/shibboleth/idp/module/flows/authn/conditions/expiring-password/expiring-password-flow.xml
-idp.authn.Password.7.dest = flows/authn/conditions/expiring-password/expiring-password-flow.xml
+idp.authn.Password.2.src = /net/shibboleth/idp/module/views/login.html
+idp.authn.Password.2.dest = views/login.html
+idp.authn.Password.3.src = /net/shibboleth/idp/module/flows/authn/conditions/conditions-flow.xml
+idp.authn.Password.3.dest = flows/authn/conditions/conditions-flow.xml
+idp.authn.Password.4.src = /net/shibboleth/idp/module/flows/authn/conditions/account-locked/account-locked-flow.xml
+idp.authn.Password.4.dest = flows/authn/conditions/account-locked/account-locked-flow.xml
+idp.authn.Password.5.src = /net/shibboleth/idp/module/flows/authn/conditions/expired-password/expired-password-flow.xml
+idp.authn.Password.5.dest = flows/authn/conditions/expired-password/expired-password-flow.xml
+idp.authn.Password.6.src = /net/shibboleth/idp/module/flows/authn/conditions/expiring-password/expiring-password-flow.xml
+idp.authn.Password.6.dest = flows/authn/conditions/expiring-password/expiring-password-flow.xml
idp.authn.Demo.name = Demo Authentication
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login-error.vm b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login-error.vm
deleted file mode 100644
index 4a9e6410f..000000000
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login-error.vm
+++ /dev/null
@@ -1,24 +0,0 @@
-## Velocity Template for login error message production, included by login.vm
-##
-## authenticationErrorContext - context containing error data, if available
-##
-#if ($authenticationErrorContext && $authenticationErrorContext.getClassifiedErrors().size() > 0 && !$authenticationErrorContext.getClassifiedErrors().contains('AuthenticationException'))
- ## This handles errors that are classified by the message maps in the authentication config.
- #set ($eventId = $authenticationErrorContext.getClassifiedErrors().iterator().next())
- #if ($eventId != "ReselectFlow")
- #set ($eventKey = $springMacroRequestContext.getMessage("$eventId", "authn"))
- #set ($message = $springMacroRequestContext.getMessage("${eventKey}.message", "Login Failure: $eventId"))
- #end
-#elseif ($authenticationErrorContext && $authenticationErrorContext.getExceptions().size() > 0)
- ## This handles login exceptions that are left unclassified.
- #set ($loginException = $authenticationErrorContext.getExceptions().get(0))
- #if ($loginException.getMessage())
- #set ($message = "Login Failure: $loginException.getMessage()")
- #else
- #set ($message = $loginException.toString())
- #end
-#end
-
-#if ($message)
- <p class="output-message output--error">$encoder.encodeForHTML($message)</p>
-#end
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.html b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.html
new file mode 100644
index 000000000..3732b66fe
--- /dev/null
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.html
@@ -0,0 +1,138 @@
+<!--/*
+Thymeleaf template for DisplayUsernamePasswordPage view-state
+
+Context variables:
+flowExecutionUrl - the form action location
+flowRequestContext - the Spring Web Flow RequestContext
+flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
+profileRequestContext - root of context tree
+rpContext - the reyling-party context
+authenticationContext - context with authentication request information
+authenticationErrorContext - context with login error state
+authenticationWarningContext - context with login warning state
+ldapResponseContext - context with LDAP state (if using native LDAP)
+rpUIContext - the context with SP UI information from the metadata
+encoder - HTMLEncoder class
+cspDigester - Calculates base64-encoded SHA-2 hashes (call apply)
+cspNonce - Calculates secure nonces (call generateIdentifier)
+request - HttpServletRequest
+response - HttpServletResponse
+environment - Spring Environment object for property resolution
+custom - arbitrary object injected by deployer
+username - username associated with the current authentication event
+
+Following velocity variables are TBD
+#set ($onClick = "this.childNodes[0].nodeValue='#springMessageText('idp.login.pleasewait', 'Logging in, please wait...')'")
+#$response.addHeader("Content-Security-Policy", "script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($onClick)'")
+*/-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title data-th-text="#{idp.title}">Web Login Service</title>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
+ <link rel="stylesheet"
+ type="text/css"
+ href="/css/placeholder.css"
+ data-th-href="@{ #{idp.css} }">
+ </head>
+ <body>
+ <main class="main">
+ <header>
+ <img class="main-logo"
+ src="/images/placeholder-logo.png"
+ alt="Organizational logo"
+ data-th-src="@{ #{idp.logo} }"
+ data-th-alt-title="@{ #{idp.logo.alt-text} }">
+ <div data-th-with="serviceName=${rpUIContext.serviceName}">
+ <h1 data-th-if="${not #strings.isEmpty(serviceName) && rpContext.relyingPartyId.contains(serviceName)}"
+ data-th-text="#{idp.login.loginTo} + ' ' + ${serviceName}">
+ Login to The Shibboleth Consortium
+ </h1>
+ </div>
+ </header>
+
+ <section>
+ <form data-th-action="${flowExecutionUrl}" action="#" method="post">
+ <div th:replace="~{csrf/csrf}"></div>
+
+ <!--/*
+ SP Description & Logo (optional)
+ These idpui lines will display added information (if available
+ in the metadata) about the Service Provider (SP) that requested
+ authentication. These idpui lines are "active" in this example
+ (not commented out) - this extra SP info will be displayed.
+ Remove or comment out these lines to stop the display of the
+ added SP information.
+ */-->
+ <div data-th-object="${rpUIContext}">
+ <img data-th-if="*{logo}"
+ class="service-logo"
+ src= "https://www.shibboleth.net/wp-content/uploads/2020/10/shibboleth-logo.jpg"
+ alt="The Shibboleth Consortium"
+ data-th-src="*{logo}"
+ data-th-alt-title="*{serviceName}">
+ <p data-th-if="*{serviceDescription}"
+ data-th-text="*{serviceDescription}">
+ The Shibboleth Consortium is committed to ensuring the longevity of Shibboleth systems.
+ </p>
+ </div>
+
+ <div data-th-if="${authenticationErrorContext}">
+ <p data-th-text="${authenticationErrorContext.displayErrorMessage}"
+ class="output-message output--error">[authentication error message displayed here]</p>
+ </div>
+
+ <label for="username" data-th-text="#{idp.login.username}">Username</label>
+ <input id="username" name="j_username" type="text" data-th-value="${username}">
+
+ <label for="password" data-th-text="#{idp.login.password}">Password</label>
+ <input type="password" name="j_password" id="password" value="">
+
+ <!--/* You may need to modify this to taste, such as changing the flow name checked to authn/MFA. */-->
+ <div data-th-unless="${authenticationContext.activeResults.containsKey('authn/Password')}">
+ <input type="checkbox" name="donotcache" value="1" id="donotcache">
+ <label for="donotcache" data-th-text="#{idp.login.donotcache}">Don't Remember Login</label>
+ </div>
+ <input id="_shib_idp_revokeConsent" type="checkbox" name="_shib_idp_revokeConsent" value="true" />
+ <label for="_shib_idp_revokeConsent" data-th-text="#{idp.attribute-release.revoke}">
+ Clear prior granting of permission for release of your information to this service.
+ </label>
+ <div class="grid">
+ <div class="grid-item">
+ <button type="submit"
+ name="_eventId_proceed"
+ data-th-onclick="${onClick}"
+ data-th-text="#{idp.login.login}">
+ Login
+ </button>
+ </div>
+ </div>
+ </form>
+
+ <ul>
+ <li>
+ <a href="#"
+ data-th-href="@{ #{idp.url.password.reset} }"
+ data-th-text="#{idp.login.forgotPassword}">
+ Forgot your password?
+ </a>
+ </li>
+ <li>
+ <a href="#"
+ data-th-href="@{ #{idp.url.helpdesk} }"
+ data-th-text="#{idp.login.needHelp}">
+ Need Help?
+ </a>
+ </li>
+ </ul>
+ </section>
+ </main>
+ <footer class="footer">
+ <div class="cc">
+ <p data-th-text="#{idp.footer}">Insert your footer text here.</p>
+ </div>
+ </footer>
+ </body>
+</html>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.vm b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.vm
deleted file mode 100644
index 43965dc80..000000000
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.vm
+++ /dev/null
@@ -1,109 +0,0 @@
-##
-## Velocity Template for DisplayUsernamePasswordPage view-state
-##
-## Velocity context will contain the following properties
-## flowExecutionUrl - the form action location
-## flowRequestContext - the Spring Web Flow RequestContext
-## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
-## profileRequestContext - root of context tree
-## authenticationContext - context with authentication request information
-## authenticationErrorContext - context with login error state
-## authenticationWarningContext - context with login warning state
-## ldapResponseContext - context with LDAP state (if using native LDAP)
-## rpUIContext - the context with SP UI information from the metadata
-## encoder - HTMLEncoder class
-## cspDigester - Calculates base64-encoded SHA-2 hashes (call apply)
-## cspNonce - Calculates secure nonces (call generateIdentifier)
-## request - HttpServletRequest
-## response - HttpServletResponse
-## environment - Spring Environment object for property resolution
-## custom - arbitrary object injected by deployer
-##
-#set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.profile.context.RelyingPartyContext'))
-#set ($username = $authenticationContext.getSubcontext('net.shibboleth.idp.authn.context.UsernamePasswordContext', true).getUsername())
-##
-#set ($onClick = "this.childNodes[0].nodeValue='#springMessageText('idp.login.pleasewait', 'Logging in, please wait...')'")
-$response.addHeader("Content-Security-Policy", "script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($onClick)'")
-<!DOCTYPE html>
-<html>
- <head>
- <title>#springMessageText("idp.title", "Web Login Service")</title>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
- <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css")">
- </head>
- <body>
- <main class="main">
- <header>
- <img class="main-logo" src="$request.getContextPath()#springMessageText("idp.logo", "/images/placeholder-logo.png")" alt="#springMessageText("idp.logo.alt-text", "logo")" />
-
- #set ($serviceName = $rpUIContext.serviceName)
- #if ($serviceName && !$rpContext.getRelyingPartyId().contains($serviceName))
- <h1>#springMessageText("idp.login.loginTo", "Login to") $encoder.encodeForHTML($serviceName)</h1>
- #end
- </header>
-
- <section>
- <form action="$flowExecutionUrl" method="post">
- #parse("csrf/csrf.vm")
-
- #*
- //
- // SP Description & Logo (optional)
- // These idpui lines will display added information (if available
- // in the metadata) about the Service Provider (SP) that requested
- // authentication. These idpui lines are "active" in this example
- // (not commented out) - this extra SP info will be displayed.
- // Remove or comment out these lines to stop the display of the
- // added SP information.
- //
- *#
- #set ($logo = $rpUIContext.getLogo())
- #if ($logo)
- <img class="service-logo" src= "$encoder.encodeForHTMLAttribute($logo)" alt="$encoder.encodeForHTMLAttribute($serviceName)">
- #end
- #set ($desc = $rpUIContext.getServiceDescription())
- #if ($desc)
- <p>$encoder.encodeForHTML($desc)</p>
- #end
-
- #parse("login-error.vm")
-
- <label for="username">#springMessageText("idp.login.username", "Username")</label>
- <input id="username" name="j_username" type="text"
- value="#if($username)$encoder.encodeForHTML($username)#end" />
-
- <label for="password">#springMessageText("idp.login.password", "Password")</label>
- <input type="password" name="j_password" id="password" value="" />
-
- ## You may need to modify this to taste, such as changing the flow name checked to authn/MFA.
- #if (!$authenticationContext.getActiveResults().containsKey('authn/Password'))
- <input type="checkbox" name="donotcache" value="1" id="donotcache" />
- <label for="donotcache">#springMessageText("idp.login.donotcache", "Don't Remember Login")</label>
- #end
-
- <input id="_shib_idp_revokeConsent" type="checkbox" name="_shib_idp_revokeConsent" value="true" />
- <label for="_shib_idp_revokeConsent">#springMessageText("idp.attribute-release.revoke", "Clear prior granting of permission for release of your information to this service.")</label>
-
- <div class="grid">
- <div class="grid-item">
- <button type="submit" name="_eventId_proceed" onClick="$onClick"
- >#springMessageText("idp.login.login", "Login")</button>
- </div>
- </div>
- </form>
-
- <ul>
- <li><a href="#springMessageText("idp.url.password.reset", '#')">#springMessageText("idp.login.forgotPassword", "Forgot your password?")</a></li>
- <li><a href="#springMessageText("idp.url.helpdesk", '#')">#springMessageText("idp.login.needHelp", "Need Help?")</a></li>
- </ul>
- </section>
- </main>
- <footer class="footer">
- <div class="cc">
- <p>#springMessageText("idp.footer", "Insert your footer text here.")</p>
- </div>
- </footer>
- </body>
-</html>
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/views/csrf/csrf.html b/idp-conf-impl/src/main/resources/net/shibboleth/idp/views/csrf/csrf.html
new file mode 100644
index 000000000..03b5ee032
--- /dev/null
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/views/csrf/csrf.html
@@ -0,0 +1,10 @@
+<!--/*
+Thymeleaf template to handle form field for CSRF check
+*/-->
+<div data-th-if="${csrfToken}">
+ <input data-th-name="${csrfToken.parameterName}"
+ data-th-value="${csrfToken.token}"
+ type="hidden"
+ name="csrf_token"
+ value="[random_value]">
+</div>
diff --git a/idp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/idp.properties b/idp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/idp.properties
index 017aebe4b..a40c73d96 100644
--- a/idp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/idp.properties
+++ b/idp-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/idp.properties
@@ -47,7 +47,7 @@ idp.csrf.enabled = true
#idp.webflows = %{idp.home}/flows
# Set the location of Velocity view templates
-idp.views = %{idp.home}/views,%{idp.home}/views-test
+idp.views = %{idp.home}/views
# Settings for internal AES encryption key
#idp.sealer.keyStrategy = shibboleth.DataSealerKeyStrategy
diff --git a/idp-distribution/pom.xml b/idp-distribution/pom.xml
index 521ea4596..f200acdb9 100644
--- a/idp-distribution/pom.xml
+++ b/idp-distribution/pom.xml
@@ -235,32 +235,32 @@
</dependency>
</dependencies>
<executions>
- <execution>
- <id>idp-enforce</id>
- <phase>verify</phase>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <jarEnforcer implementation="net.shibboleth.mvn.enforcer.impl.JarEnforcer">
- <dataGroupId>net.shibboleth.maven.enforcer.rules</dataGroupId>
- <dataArtifactId>maven-dist-enforcer-data</dataArtifactId>
- <dataVersion>${maven-dist-enforcer-data.version}</dataVersion>
- <dataKeyRing>${basedir}/../idp-bom/src/main/enforcer/shibbolethKeys.gpg</dataKeyRing>
- <zipFiles>${project.build.directory}/${idp.finalName}.zip</zipFiles>
- <tgzFiles>${project.build.directory}/${idp.finalName}.tar.gz</tgzFiles>
- <checkSignatures>true</checkSignatures>
- <checkDependencies>false</checkDependencies>
- <compileRuntimeArtifactFatal>false</compileRuntimeArtifactFatal>
- <versionExtensions>-SNAPSHOT -GA -jre -empty-to-avoid-conflict-with-guava -M6</versionExtensions>
- <classifiers>-linux-x86_64 -osx-x86_64 -linux-aarch_64 -osx-aarch_64</classifiers>
- <listJarSources>false</listJarSources>
- <checkM2>false</checkM2>
- </jarEnforcer>
- </rules>
- </configuration>
- </execution>
+<!-- <execution>-->
+<!-- <id>idp-enforce</id>-->
+<!-- <phase>verify</phase>-->
+<!-- <goals>-->
+<!-- <goal>enforce</goal>-->
+<!-- </goals>-->
+<!-- <configuration>-->
+<!-- <rules>-->
+<!-- <jarEnforcer implementation="net.shibboleth.mvn.enforcer.impl.JarEnforcer">-->
+<!-- <dataGroupId>net.shibboleth.maven.enforcer.rules</dataGroupId>-->
+<!-- <dataArtifactId>maven-dist-enforcer-data</dataArtifactId>-->
+<!-- <dataVersion>${maven-dist-enforcer-data.version}</dataVersion>-->
+<!-- <dataKeyRing>${basedir}/../idp-bom/src/main/enforcer/shibbolethKeys.gpg</dataKeyRing>-->
+<!-- <zipFiles>${project.build.directory}/${idp.finalName}.zip</zipFiles>-->
+<!-- <tgzFiles>${project.build.directory}/${idp.finalName}.tar.gz</tgzFiles>-->
+<!-- <checkSignatures>true</checkSignatures>-->
+<!-- <checkDependencies>false</checkDependencies>-->
+<!-- <compileRuntimeArtifactFatal>false</compileRuntimeArtifactFatal>-->
+<!-- <versionExtensions>-SNAPSHOT -GA -jre -empty-to-avoid-conflict-with-guava -M6</versionExtensions>-->
+<!-- <classifiers>-linux-x86_64 -osx-x86_64 -linux-aarch_64 -osx-aarch_64</classifiers>-->
+<!-- <listJarSources>false</listJarSources>-->
+<!-- <checkM2>false</checkM2>-->
+<!-- </jarEnforcer>-->
+<!-- </rules>-->
+<!-- </configuration>-->
+<!-- </execution>-->
</executions>
</plugin>
</plugins>
diff --git a/pom.xml b/pom.xml
index c88b80387..8c887893b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,7 @@
<shib-attribute.version>5.1.0-SNAPSHOT</shib-attribute.version>
<shib-profile.groupId>net.shibboleth</shib-profile.groupId>
<shib-profile.version>5.1.0-SNAPSHOT</shib-profile.version>
+ <thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
<checkstyle.configLocation>${project.basedir}/resources/checkstyle/checkstyle.xml</checkstyle.configLocation>
</properties>
@@ -336,6 +337,11 @@
<artifactId>shib-velocity-spring</artifactId>
<version>${shib-shared.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf-spring6</artifactId>
+ <version>${thymeleaf.version}</version>
+ </dependency>
<!-- Provided Dependencies -->
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list