[java-identity-provider] branch main updated: IDP-2233 - Evaluate complex logic in views for possible abstraction
Scott Cantor
cantor.2 at osu.edu
Wed Feb 7 17:06:59 UTC 2024
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=a57fa06f64049d4ae11eedf335b2a966f50b4c66
The following commit(s) were added to refs/heads/main by this push:
new a57fa06f6 IDP-2233 - Evaluate complex logic in views for possible abstraction
a57fa06f6 is described below
commit a57fa06f64049d4ae11eedf335b2a966f50b4c66
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 7 12:06:56 2024 -0500
IDP-2233 - Evaluate complex logic in views for possible abstraction
https://shibboleth.atlassian.net/browse/IDP-2233
Factor out password error handling into Java and add configurability.
---
.../PasswordErrorMessageLookupFunction.java | 114 +++++++++++++++++++++
.../idp/flows/authn/password-authn-beans.xml | 6 ++
.../idp/flows/authn/password-authn-flow.xml | 1 +
.../idp/module/authn/impl/module.properties | 18 ++--
.../idp/module/conf/authn/authn.properties | 3 +
.../net/shibboleth/idp/module/views/login-error.vm | 24 -----
.../net/shibboleth/idp/module/views/login.vm | 8 +-
7 files changed, 138 insertions(+), 36 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PasswordErrorMessageLookupFunction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PasswordErrorMessageLookupFunction.java
new file mode 100644
index 000000000..90973ade0
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/navigate/PasswordErrorMessageLookupFunction.java
@@ -0,0 +1,114 @@
+/*
+ * 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.context.navigate;
+
+import java.util.Collection;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.context.support.ApplicationObjectSupport;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A function that examines the state of a request and produces an appropriate error message for
+ * the Password login flow.
+ *
+ * <p>NOTE: The result of this function is <strong>NOT</strong> HTML-encoded in any way and must
+ * be encoded for safety if used.</p>
+ *
+ * <p>This implements the pre-existing default behavior in Velocity for determining an error to
+ * display.</p>
+ *
+ * @since 5.1.0
+ */
+public class PasswordErrorMessageLookupFunction extends ApplicationObjectSupport
+ implements ContextDataLookupFunction<ProfileRequestContext,String> {
+
+ /** Message ID to use for generic, unclassified errors or exceptions. */
+ private String genericMessageID;
+
+ /**
+ * Sets whether non-message-based error messages should be exposed or turned into a more
+ * generic value.
+ *
+ * @param id message ID
+ */
+ public void setGenericMessageID(@Nullable final String id) {
+ genericMessageID = StringSupport.trimOrNull(id);
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+
+ final MessageSourceAccessor messageSource = getMessageSourceAccessor();
+ if (messageSource == null) {
+ return null;
+ }
+
+ final AuthenticationContext authCtx = input != null ? input.getSubcontext(AuthenticationContext.class) : null;
+ final AuthenticationErrorContext errorCtx =
+ authCtx != null ? authCtx.getSubcontext(AuthenticationErrorContext.class) : null;
+
+ if (errorCtx == null) {
+ return null;
+ }
+
+ final Collection<String> classifiedErrors = errorCtx.getClassifiedErrors();
+ if (!classifiedErrors.isEmpty() && !classifiedErrors.contains(AuthnEventIds.AUTHN_EXCEPTION)) {
+ return getClassifiedMessage(messageSource, classifiedErrors.iterator().next());
+ } else if (!errorCtx.getExceptions().isEmpty()) {
+ return getExceptionMessage(messageSource, errorCtx.getExceptions().get(0));
+ }
+
+ return null;
+ }
+
+ @Nullable private String getClassifiedMessage(@Nonnull final MessageSourceAccessor messageSource,
+ @Nonnull final String classifiedError) {
+
+ if (!AuthnEventIds.RESELECT_FLOW.equals(classifiedError)) {
+ final String eventKey = messageSource.getMessage(classifiedError,
+ genericMessageID != null ? genericMessageID : "authn");
+ if (eventKey != null) {
+ return messageSource.getMessage(eventKey + ".message", "Login Failure: " + classifiedError);
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable private String getExceptionMessage(@Nonnull final MessageSourceAccessor messageSource,
+ @Nonnull final Exception e) {
+
+ if (genericMessageID != null) {
+ return messageSource.getMessage(genericMessageID, "Login was not successful.");
+ }
+
+ if (e.getMessage() != null) {
+ return "Login Failure: " + e.getMessage();
+ } else {
+ return e.toString();
+ }
+ }
+
+}
\ No newline at end of file
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 a87ab207a..e2a137b9e 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
@@ -37,7 +37,13 @@
<bean id="shibboleth.authn.JAAS.LoginConfigNames" parent="shibboleth.CommaDelimStringArray"
c:_0="#{'%{idp.authn.JAAS.loginConfigNames:ShibUserPassAuth}'.trim()}" />
+ <!-- Default function to produce error message for login form. -->
+ <bean id="DefaultPasswordErrorFunction" class="net.shibboleth.idp.authn.context.navigate.PasswordErrorMessageLookupFunction" lazy-init="true"
+ p:genericMessageID="%{idp.authn.Password.genericMessageID:}" />
+
<import resource="conditional:%{idp.home}/conf/authn/password-authn-config.xml" />
+
+ <alias alias="PasswordErrorFunction" name="%{idp.authn.Password.errorMessageFunction:DefaultPasswordErrorFunction}" />
<bean id="ExtractUsernamePasswordFromBasicAuth"
class="net.shibboleth.idp.authn.impl.ExtractUsernamePasswordFromBasicAuth" 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 c80eaf694..ef43d4f3a 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
@@ -40,6 +40,7 @@
<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.ensureSubcontext(T(net.shibboleth.idp.authn.context.UsernamePasswordContext)).getUsername()" result="viewScope.username" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('PasswordErrorFunction')" result="viewScope.errorMessageFunction" />
<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" />
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..530679aff 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
@@ -27,16 +27,14 @@ idp.authn.Password.1.src = /net/shibboleth/idp/module/conf/authn/password-authn-
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.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/conf/authn/authn.properties b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/authn/authn.properties
index 42a454344..143f526da 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/authn/authn.properties
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/authn/authn.properties
@@ -75,6 +75,9 @@
#idp.authn.Password.usernameFieldName = j_username
#idp.authn.Password.passwordFieldName = j_password
#idp.authn.Password.ssoBypassFieldName = donotcache
+# Default error message handling
+#idp.authn.Password.errorMessageFunction = DefaultPasswordErrorFunction
+#idp.authn.Password.genericMessageID = authn
# Unset if using customized Principals per validator
#idp.authn.Password.addDefaultPrincipals = true
# The Principal collection below is the typical default if not otherwise noted.
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.vm b/idp-conf-impl/src/main/resources/net/shibboleth/idp/module/views/login.vm
index 838eb5e2c..446c3ad6e 100644
--- 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
@@ -11,6 +11,7 @@
## authenticationWarningContext - context with login warning state
## ldapResponseContext - context with LDAP state (if using native LDAP)
## username - username from previous rendering of form
+## errorMessageFunction - function to produce error message for form
## rpContext - the context with information about the relying party (SP)
## rpUIContext - the context with SP UI information from the metadata
## encoder - HTMLEncoder class
@@ -68,8 +69,11 @@ $response.addHeader("Content-Security-Policy", "script-src-attr 'unsafe-hashes'
#if ($desc)
<p>$encoder.encodeForHTML($desc)</p>
#end
-
- #parse("login-error.vm")
+
+ #set ($errorMessage = $errorMessageFunction.apply($profileRequestContext))
+ #if ($errorMessage)
+ <p class="output-message output--error">$encoder.encodeForHTML($errorMessage)</p>
+ #end
<label for="username">#springMessageText("idp.login.username", "Username")</label>
<input type="text" id="username" name="j_username"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list