From 0131a3f265cae713392c4cf030192c4abdf7c8c1 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 08:48:24 -0700 Subject: [PATCH 01/12] Add option to skip reauthentication --- .../security/AuthenticationConfiguration.java | 2 ++ .../labkey/core/login/LoginController.java | 6 ++--- .../authentication/TestSsoConfiguration.java | 20 +++++++++++++++-- .../authentication/TestSsoController.java | 22 ++++++++++++++++++- .../authentication/TestSsoProvider.java | 1 + 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationConfiguration.java b/api/src/org/labkey/api/security/AuthenticationConfiguration.java index d308e98c9da..c11fb3903d2 100644 --- a/api/src/org/labkey/api/security/AuthenticationConfiguration.java +++ b/api/src/org/labkey/api/security/AuthenticationConfiguration.java @@ -124,6 +124,8 @@ interface SSOAuthenticationConfiguration */ boolean isAutoRedirect(); + boolean isReauthenticationSupported(); + @Override default void handleStartupProperties(Map map) { diff --git a/core/src/org/labkey/core/login/LoginController.java b/core/src/org/labkey/core/login/LoginController.java index 2a0d3ac1adc..9e46a3afd46 100644 --- a/core/src/org/labkey/core/login/LoginController.java +++ b/core/src/org/labkey/core/login/LoginController.java @@ -1657,10 +1657,10 @@ public Object execute(ReturnUrlForm form, BindException errors) JSONObject resp = new JSONObject(); resp.put("description", configuration.getDescription()); LoginUrls urls = urlProvider(LoginUrls.class); - ActionURL reauthUrl = configuration instanceof SSOAuthenticationConfiguration sso ? - urls.getSSOReauthURL(sso, form.getReturnActionURL()) : + @Nullable ActionURL reauthUrl = configuration instanceof SSOAuthenticationConfiguration sso ? + (sso.isReauthenticationSupported() ? urls.getSSOReauthURL(sso, form.getReturnActionURL()) : null) : urls.getForceReauthURL(getContainer(), true, form.getReturnActionURL()); - resp.put("reauthUrl", reauthUrl.getLocalURIString()); + resp.put("reauthUrl", reauthUrl != null ? reauthUrl.getLocalURIString() : null); return success(resp); } } diff --git a/devtools/src/org/labkey/devtools/authentication/TestSsoConfiguration.java b/devtools/src/org/labkey/devtools/authentication/TestSsoConfiguration.java index c409e6dd7d8..1f32a210b3a 100644 --- a/devtools/src/org/labkey/devtools/authentication/TestSsoConfiguration.java +++ b/devtools/src/org/labkey/devtools/authentication/TestSsoConfiguration.java @@ -23,18 +23,22 @@ import org.labkey.api.view.ActionURL; import org.labkey.api.view.ViewContext; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; public class TestSsoConfiguration extends BaseSSOAuthenticationConfiguration { + static final String SKIP_REAUTHENTICATION = "SkipReauthentication"; + private final String _domain; + private final boolean _skipReauthentication; private final LinkFactory _linkFactory = new LinkFactory(this); protected TestSsoConfiguration(TestSsoProvider provider, Map standardSettings, Map properties) { super(provider, standardSettings); _domain = (String)properties.get("domain"); + _skipReauthentication = Boolean.TRUE.equals(properties.get(SKIP_REAUTHENTICATION)); } @Override @@ -64,9 +68,21 @@ public LinkFactory getLinkFactory() return _linkFactory; } + @Override + public boolean isReauthenticationSupported() + { + return !_skipReauthentication; + } + @Override public @NotNull Map getCustomProperties() { - return null != _domain ? Map.of("domain", _domain) : Collections.emptyMap(); + Map map = new HashMap<>(); + map.put(SKIP_REAUTHENTICATION, _skipReauthentication); + + if (null != _domain) + map.put("domain", _domain); + + return map; } } diff --git a/devtools/src/org/labkey/devtools/authentication/TestSsoController.java b/devtools/src/org/labkey/devtools/authentication/TestSsoController.java index 4de5a08c8df..6a507474134 100644 --- a/devtools/src/org/labkey/devtools/authentication/TestSsoController.java +++ b/devtools/src/org/labkey/devtools/authentication/TestSsoController.java @@ -38,6 +38,7 @@ import org.springframework.validation.Errors; import org.springframework.web.servlet.ModelAndView; +import java.util.HashMap; import java.util.Map; public class TestSsoController extends SpringActionController @@ -124,16 +125,35 @@ public void validate(TestSsoSaveConfigurationForm form, Errors errors) public static class TestSsoSaveConfigurationForm extends SsoSaveConfigurationForm { + private boolean _skipReauthentication = false; + @Override public String getProvider() { return TestSsoProvider.NAME; } + public boolean isSkipReauthentication() + { + return _skipReauthentication; + } + + @SuppressWarnings("unused") + public void setSkipReauthentication(boolean skipReauthentication) + { + _skipReauthentication = skipReauthentication; + } + @Override public @NotNull Map getPropertyMap() { - return null != _domain ? Map.of("domain", _domain) : Map.of(); + Map map = new HashMap<>(); + map.put(TestSsoConfiguration.SKIP_REAUTHENTICATION, _skipReauthentication); + + if (null != _domain) + map.put("domain", _domain); + + return map; } } } diff --git a/devtools/src/org/labkey/devtools/authentication/TestSsoProvider.java b/devtools/src/org/labkey/devtools/authentication/TestSsoProvider.java index b23753d4e1a..253cb442fb5 100644 --- a/devtools/src/org/labkey/devtools/authentication/TestSsoProvider.java +++ b/devtools/src/org/labkey/devtools/authentication/TestSsoProvider.java @@ -66,6 +66,7 @@ public String getDescription() { return List.of( SettingsField.of("autoRedirect", SettingsField.FieldType.checkbox, "Default to this TestSSO configuration", "Redirects the login page directly to the TestSSO page instead of requiring the user to click on a logo.", false, false), + SettingsField.of(TestSsoConfiguration.SKIP_REAUTHENTICATION, SettingsField.FieldType.checkbox, "Skip Reauthentication", "Users who authenticate with this TestSSO configuration will not need to reauthenticate when signing electronically. This is not recommended since reauthentication is a requirement of 21 CFR Part 11.", false, false), SettingsField.getStandardDomainField() ); } From 8f13467b9756052f61f1bb6f6c15c6096a4a2746 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 09:27:16 -0700 Subject: [PATCH 02/12] Manually backport testReauth.jsp changes from develop --- .../src/org/labkey/devtools/view/testReauth.jsp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/devtools/src/org/labkey/devtools/view/testReauth.jsp b/devtools/src/org/labkey/devtools/view/testReauth.jsp index ecc40c615e4..58019bb6d12 100644 --- a/devtools/src/org/labkey/devtools/view/testReauth.jsp +++ b/devtools/src/org/labkey/devtools/view/testReauth.jsp @@ -34,19 +34,21 @@ success: function(response) { const needReauth = <%=form.reauthToken() == null%>; const data = JSON.parse(response.responseText).data; - document.getElementById("description").textContent = data.description; + document.getElementById("description").textContent = data.description; // Setting textContent HTML encodes the value if (needReauth) { document.getElementById("link").href = data.reauthUrl; } }, - failure: function() { - alert('Failed to retrieve configuration!'); - } + failure: LABKEY.Utils.getCallbackWrapper(function(errorInfo) { + document.getElementById("content").innerHTML = '' + LABKEY.Utils.encodeHtml(errorInfo.exception ?? 'Failed to retrieve configuration') + ''; + }, this, true) }); }); -You authenticated with:
+
+ + You authenticated with:
<% if (form.reauthToken() != null) @@ -79,3 +81,5 @@ You need to re-authenticate. <% } %> + +
From b1858a7188ba0d4b065c71dc56d0a22151845fd7 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 10:55:34 -0700 Subject: [PATCH 03/12] Test page supports skipping re-auth --- .../security/AuthenticationConfiguration.java | 3 +++ .../org/labkey/devtools/view/testReauth.jsp | 27 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationConfiguration.java b/api/src/org/labkey/api/security/AuthenticationConfiguration.java index c11fb3903d2..f35af7ff279 100644 --- a/api/src/org/labkey/api/security/AuthenticationConfiguration.java +++ b/api/src/org/labkey/api/security/AuthenticationConfiguration.java @@ -124,6 +124,9 @@ interface SSOAuthenticationConfiguration */ boolean isAutoRedirect(); + /** + * Currently SSO-only since SAML is the only production provider that needs the skip re-auth option + */ boolean isReauthenticationSupported(); @Override diff --git a/devtools/src/org/labkey/devtools/view/testReauth.jsp b/devtools/src/org/labkey/devtools/view/testReauth.jsp index 58019bb6d12..e7318c04a19 100644 --- a/devtools/src/org/labkey/devtools/view/testReauth.jsp +++ b/devtools/src/org/labkey/devtools/view/testReauth.jsp @@ -34,8 +34,15 @@ success: function(response) { const needReauth = <%=form.reauthToken() == null%>; const data = JSON.parse(response.responseText).data; - document.getElementById("description").textContent = data.description; // Setting textContent HTML encodes the value - if (needReauth) { + const skipReauth = (data.reauthUrl == null); + // Setting textContent HTML encodes the value + document.getElementById("description").textContent = data.description + (skipReauth ? ', but that configuration has disabled reauthentication' : ''); + if (skipReauth || !needReauth) { + document.getElementById("sign").style.display = "block"; + if (skipReauth) + document.getElementById("reauth").style.display = "none"; + } + else { document.getElementById("link").href = data.reauthUrl; } }, @@ -56,14 +63,13 @@ %> Looks like you successfully re-authenticated and received token: <%=h(form.reauthToken())%>
- - - - <% } else { +%> +
+<% if (form.errorMessage() != null) { %> @@ -78,8 +84,15 @@ You need to re-authenticate. } %> Click here + +
<% } %> - + From b8fbad7d1567cf7ae5b9c5ddbe475f708a6497b9 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 12:06:22 -0700 Subject: [PATCH 04/12] Verification now allows a configuration to skip re-auth --- .../api/security/AuthenticationManager.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index 9f35e3b36a1..63a033a92f2 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -1914,6 +1914,7 @@ private static Map getTokenMap(HttpServletRequest request /** * Retrieves and validates the re-auth context associated with the provided token. If the token has an associated * context that's not expired and (if requested) the context user matches the provided user, then return the user. + * If there's no token and the user's authentication configuration has disabled re-auth, also return the user. * @param request Request from which to retrieve the session * @param token The reauth token to validate * @param sessionUser If non-null, causes validation that this user matches the reauth user @@ -1921,11 +1922,11 @@ private static Map getTokenMap(HttpServletRequest request */ public static @Nullable User getAndClearReauthUser(HttpServletRequest request, @Nullable String token, @Nullable User sessionUser) { - if (token != null) - { - HttpSession session = request.getSession(false); + HttpSession session = request.getSession(false); - if (session != null) + if (session != null) + { + if (token != null) { @SuppressWarnings("unchecked") Map tokenMap = (Map) session.getAttribute(REAUTH_TOKEN_MAP_NAME); @@ -1944,6 +1945,14 @@ private static Map getTokenMap(HttpServletRequest request } } } + else + { + // Potential skip re-auth scenario. If we have a session but no token and the configuration has disabled + // re-auth, return the session user. + PrimaryAuthenticationConfiguration config = getConfiguration(session); + if (config instanceof AuthenticationConfiguration.SSOAuthenticationConfiguration sso && !sso.isReauthenticationSupported()) + return sessionUser; + } } return null; @@ -1968,7 +1977,7 @@ public void testReauthTokens() throws InterruptedException User admin = TestContext.get().getUser(); Map map = getTokenMap(request); clearExpiredTokens(map); - // Might have some unexpired tokens stashed away. Assume they won't expired during this test run. + // Might have some unexpired tokens stashed away. Assume they won't expire during this test run. int initialCount = map.size(); ActionURL url = new ActionURL("core", "begin.view", ContainerManager.getRoot()); @@ -1982,7 +1991,7 @@ public void testReauthTokens() throws InterruptedException assertEquals(admin, getAndClearReauthUser(request, token, admin)); assertEquals(initialCount, map.size()); - // Try same token again + // Try the same token again assertNull(getAndClearReauthUser(request, token, admin)); // Try a bogus token assertNull(getAndClearReauthUser(request, "xyz", admin)); From 3c0f9a8215acfd1aa1b91e8fedad67e17d1dc207 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 12:34:49 -0700 Subject: [PATCH 05/12] Handle empty --- api/src/org/labkey/api/security/AuthenticationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index 63a033a92f2..67c42e5567b 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -1926,7 +1926,7 @@ private static Map getTokenMap(HttpServletRequest request if (session != null) { - if (token != null) + if (StringUtils.isEmpty(token)) { @SuppressWarnings("unchecked") Map tokenMap = (Map) session.getAttribute(REAUTH_TOKEN_MAP_NAME); From d6ccd9e63642024ee778b93f7884e2a1cb9845c8 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 12:53:41 -0700 Subject: [PATCH 06/12] Invert --- api/src/org/labkey/api/security/AuthenticationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index 67c42e5567b..374e410cba1 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -1926,7 +1926,7 @@ private static Map getTokenMap(HttpServletRequest request if (session != null) { - if (StringUtils.isEmpty(token)) + if (!StringUtils.isEmpty(token)) { @SuppressWarnings("unchecked") Map tokenMap = (Map) session.getAttribute(REAUTH_TOKEN_MAP_NAME); From af1651d24cf25f202ec916f613701f6d0421f57f Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 12:59:38 -0700 Subject: [PATCH 07/12] Feedback --- api/src/org/labkey/api/security/AuthenticationManager.java | 4 ++-- devtools/src/org/labkey/devtools/view/testReauth.jsp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index 374e410cba1..a1b541593bb 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -1948,10 +1948,10 @@ private static Map getTokenMap(HttpServletRequest request else { // Potential skip re-auth scenario. If we have a session but no token and the configuration has disabled - // re-auth, return the session user. + // re-auth, just return the session user. PrimaryAuthenticationConfiguration config = getConfiguration(session); if (config instanceof AuthenticationConfiguration.SSOAuthenticationConfiguration sso && !sso.isReauthenticationSupported()) - return sessionUser; + return SecurityManager.getSessionUser(request); } } diff --git a/devtools/src/org/labkey/devtools/view/testReauth.jsp b/devtools/src/org/labkey/devtools/view/testReauth.jsp index e7318c04a19..b48e61fbe40 100644 --- a/devtools/src/org/labkey/devtools/view/testReauth.jsp +++ b/devtools/src/org/labkey/devtools/view/testReauth.jsp @@ -39,8 +39,9 @@ document.getElementById("description").textContent = data.description + (skipReauth ? ', but that configuration has disabled reauthentication' : ''); if (skipReauth || !needReauth) { document.getElementById("sign").style.display = "block"; - if (skipReauth) + if (skipReauth && needReauth) { document.getElementById("reauth").style.display = "none"; + } } else { document.getElementById("link").href = data.reauthUrl; From 9a4a38c8887ee60cdcb9d8ab69fb9bb0b8e78dc6 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Fri, 31 Jul 2026 13:20:39 -0700 Subject: [PATCH 08/12] Clarify null sessionUser behavior --- .../labkey/api/security/AuthenticationManager.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index a1b541593bb..7ec965f8562 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -1914,11 +1914,15 @@ private static Map getTokenMap(HttpServletRequest request /** * Retrieves and validates the re-auth context associated with the provided token. If the token has an associated * context that's not expired and (if requested) the context user matches the provided user, then return the user. - * If there's no token and the user's authentication configuration has disabled re-auth, also return the user. + * If there's no token, sessionUser is non-null, and the user's authentication configuration has disabled re-auth, + * return the session user. * @param request Request from which to retrieve the session - * @param token The reauth token to validate - * @param sessionUser If non-null, causes validation that this user matches the reauth user - * @return The re-auth user, if token is valid and session user check passes. Otherwise, null. + * @param token Re-auth token to validate + * @param sessionUser If non-null, causes validation that this user matches the reauth user. A null value also + * suppresses the skip-reauthentication exemption described above, so callers that require + * proof of an actual reauthentication (e.g. the CAS server's "renew" handling) must pass null. + * @return The re-auth user, if the token is valid and the session user check passes, or the session + * user if reauthentication is disabled for the user's configuration. Otherwise, null. */ public static @Nullable User getAndClearReauthUser(HttpServletRequest request, @Nullable String token, @Nullable User sessionUser) { @@ -1945,7 +1949,7 @@ private static Map getTokenMap(HttpServletRequest request } } } - else + else if (sessionUser != null) // Skip the CAS IdP case where sessionUser is null { // Potential skip re-auth scenario. If we have a session but no token and the configuration has disabled // re-auth, just return the session user. From c7151ce1eddb359c4dc192d1139ec875dfade5e3 Mon Sep 17 00:00:00 2001 From: Binal Patel Date: Sat, 1 Aug 2026 12:10:08 -0600 Subject: [PATCH 09/12] Distinguish a failed reauthentication from bad credentials --- .../labkey/api/security/AuthenticationManager.java | 14 +++++++++++++- .../api/security/AuthenticationProvider.java | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index 7ec965f8562..b41ea79ccb5 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -563,7 +563,7 @@ private ModelAndView getAuthView(AuthenticationResponse response, BindException if (errors.hasErrors() || !response.isAuthenticated()) { if (!errors.hasErrors()) - errors.addError(new LabKeyError("Bad credentials")); + errors.addError(new LabKeyError(getFailureMessage(response))); } else { @@ -595,6 +595,18 @@ private ModelAndView getAuthView(AuthenticationResponse response, BindException return new SimpleErrorView(errors, false); } + // Most failures can't distinguish a bad password from an unknown user, so they share a deliberately vague + // message. reauthNotConfirmed is different: the user's credentials were never in question, so say what actually + // went wrong and who can fix it. + private String getFailureMessage(AuthenticationResponse response) + { + return FailureReason.reauthNotConfirmed == response.getFailureReason() ? + "Reauthentication failed: your identity provider did not re-verify your credentials. Please contact your administrator. " + + "The identity provider may not support the ForceAuthn option that electronic signatures require; if it does not, " + + "the \"Skip Reauthentication\" option can be enabled in this SSO configuration." : + "Bad credentials"; + } + // Reauthentication case for electronic signing and other sensitive operations. Check that reauthentication // was successful and re-auth user matches session user. Not currently verifying that the same authentication // configuration was used to reauthenticate. diff --git a/api/src/org/labkey/api/security/AuthenticationProvider.java b/api/src/org/labkey/api/security/AuthenticationProvider.java index b293a5ddf02..636ffe1e45f 100644 --- a/api/src/org/labkey/api/security/AuthenticationProvider.java +++ b/api/src/org/labkey/api/security/AuthenticationProvider.java @@ -468,6 +468,7 @@ enum FailureReason userDoesNotExist(ReportType.onFailure, "user does not exist", null), badPassword(ReportType.onFailure, "incorrect password", null), badCredentials(ReportType.onFailure, "invalid credentials", null), // Use for cases where we can't distinguish between userDoesNotExist and badPassword + reauthNotConfirmed(ReportType.onFailure, "identity provider did not reauthenticate the user", null), // Credentials were fine; the IdP declined to reauthenticate (e.g. ignored SAML ForceAuthn) complexity(ReportType.onFailure, "password does not meet the complexity requirements", AuthenticationStatus.Complexity), expired(ReportType.onFailure, "password has expired", AuthenticationStatus.PasswordExpired), configurationError(ReportType.always, "configuration problem", null), From ae6d645f47afe1dc06a3b87ec3b7278bccb1ba73 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 3 Aug 2026 09:33:06 -0700 Subject: [PATCH 10/12] Less detailed message to end users --- api/src/org/labkey/api/security/AuthenticationManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/org/labkey/api/security/AuthenticationManager.java b/api/src/org/labkey/api/security/AuthenticationManager.java index b41ea79ccb5..ba60877c838 100644 --- a/api/src/org/labkey/api/security/AuthenticationManager.java +++ b/api/src/org/labkey/api/security/AuthenticationManager.java @@ -601,9 +601,7 @@ private ModelAndView getAuthView(AuthenticationResponse response, BindException private String getFailureMessage(AuthenticationResponse response) { return FailureReason.reauthNotConfirmed == response.getFailureReason() ? - "Reauthentication failed: your identity provider did not re-verify your credentials. Please contact your administrator. " + - "The identity provider may not support the ForceAuthn option that electronic signatures require; if it does not, " + - "the \"Skip Reauthentication\" option can be enabled in this SSO configuration." : + "Reauthentication failed: your identity provider did not re-verify your credentials. Please contact your administrator." : "Bad credentials"; } From 85354da41f9829a5a0f21b5f4e56360880db2a3c Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 3 Aug 2026 15:04:12 -0500 Subject: [PATCH 11/12] TestSsoProvider: Add setDefaultConfiguration, setSkipReauthentication --- .../test/params/devtools/TestSsoProvider.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/devtools/test/src/org/labkey/test/params/devtools/TestSsoProvider.java b/devtools/test/src/org/labkey/test/params/devtools/TestSsoProvider.java index 82189134588..cf574307b7e 100644 --- a/devtools/test/src/org/labkey/test/params/devtools/TestSsoProvider.java +++ b/devtools/test/src/org/labkey/test/params/devtools/TestSsoProvider.java @@ -15,6 +15,8 @@ */ package org.labkey.test.params.devtools; +import org.labkey.test.Locator; +import org.labkey.test.components.html.Checkbox; import org.labkey.test.pages.core.login.LoginConfigRow; import org.labkey.test.pages.core.login.SsoAuthDialogBase; import org.labkey.test.params.login.AuthenticationProvider; @@ -58,11 +60,48 @@ public TestSsoConfigureDialog(WebDriver driver) super(TestSsoProvider.this, driver); } + /** + * Sets "Default to this TestSSO configuration", which redirects the login page straight to the TestSSO page + * instead of requiring the user to click on a logo. + */ + public TestSsoConfigureDialog setDefaultConfiguration(boolean isDefault) + { + elementCache().autoRedirectCheckbox.set(isDefault); + return this; + } + + /** + * Sets "Skip Reauthentication", which exempts users who authenticated with this configuration from + * reauthenticating when signing electronically. + */ + public TestSsoConfigureDialog setSkipReauthentication(boolean skipReauthentication) + { + elementCache().skipReauthenticationCheckbox.set(skipReauthentication); + return this; + } + @Override protected TestSsoConfigureDialog getThis() { return this; } + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + @Override + protected ElementCache elementCache() + { + return (ElementCache) super.elementCache(); + } + + protected class ElementCache extends SsoAuthDialogBase.ElementCache + { + Checkbox autoRedirectCheckbox = new Checkbox(Locator.tagWithId("input", "autoRedirect").findWhenNeeded(this)); + Checkbox skipReauthenticationCheckbox = new Checkbox(Locator.tagWithId("input", "SkipReauthentication").findWhenNeeded(this)); + } } } From 927b7170f287582df8b5bddc83b86c3c40c63e7b Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 3 Aug 2026 17:17:29 -0500 Subject: [PATCH 12/12] Add TestSsoPage --- .../test/pages/devtools/TestSsoPage.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 devtools/test/src/org/labkey/test/pages/devtools/TestSsoPage.java diff --git a/devtools/test/src/org/labkey/test/pages/devtools/TestSsoPage.java b/devtools/test/src/org/labkey/test/pages/devtools/TestSsoPage.java new file mode 100644 index 00000000000..0e45d0fdc04 --- /dev/null +++ b/devtools/test/src/org/labkey/test/pages/devtools/TestSsoPage.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * 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 org.labkey.test.pages.devtools; + +import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; +import org.labkey.test.components.html.Input; +import org.labkey.test.pages.LabKeyPage; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +/** + * The devtools TestSSO provider's stand-in for an identity provider: type an email address to "authenticate" (or, when + * the browser was sent here to reauthenticate, "reauthenticate") as that user. No password required. + */ +public class TestSsoPage extends LabKeyPage +{ + public TestSsoPage(WebDriver driver) + { + super(driver); + } + + /** + * Navigates to the sign-in page, which redirects to the TestSSO page when a TestSSO configuration has + * "Default to this TestSSO configuration" enabled. + */ + public static TestSsoPage beginAtLogin(WebDriverWrapper webDriverWrapper) + { + webDriverWrapper.beginAt(WebTestHelper.buildURL("login", "login")); + return new TestSsoPage(webDriverWrapper.getDriver()); + } + + @Override + protected void waitForPage() + { + waitFor(() -> Locators.emailInput.existsIn(getDriver()), "TestSSO page did not load in time.", WAIT_FOR_PAGE); + } + + /** + * The form's label, which identifies whether the browser was sent here to authenticate or to reauthenticate. + */ + public String getLabel() + { + return elementCache().label.getText(); + } + + public TestSsoPage setEmail(String email) + { + elementCache().emailInput.set(email); + return this; + } + + /** + * Authenticates as the given user, which leaves the browser wherever LabKey sends the user after signing in. + */ + public void authenticate(String email) + { + setEmail(email); + clickAndWait(elementCache().authenticateButton); + clearCache(); + } + + /** + * Reauthenticates as the given user, which returns the browser to the page that requested reauthentication. Only + * available when the browser arrived here through a reauthentication redirect. + */ + public void reauthenticate(String email) + { + setEmail(email); + clickAndWait(elementCache().reauthenticateButton); + clearCache(); + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends LabKeyPage.ElementCache + { + final WebElement label = Locator.tagWithClass("label", "control-label").findWhenNeeded(this); + final Input emailInput = new Input(Locators.emailInput.findWhenNeeded(this), getDriver()); + final WebElement authenticateButton = Locator.lkButton("Authenticate").findWhenNeeded(this); + final WebElement reauthenticateButton = Locator.lkButton("Reauthenticate").findWhenNeeded(this); + } + + private static class Locators + { + static final Locator emailInput = Locator.name("email"); + } +}