[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