Skip to content
Open
2 changes: 0 additions & 2 deletions api/src/org/labkey/api/action/SimpleErrorView.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@

/**
* View that renders an error collection.
* User: adam
* Date: Sep 26, 2007
*/
public class SimpleErrorView extends JspView<Boolean>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ interface SSOAuthenticationConfiguration<AP extends SSOAuthenticationProvider<?>
{
LinkFactory getLinkFactory();
URLHelper getUrl(ViewContext ctx);
URLHelper getReauthUrl(ViewContext ctx);

/**
* Allows an SSO auth configuration to specify that it should be used automatically instead of showing the standard
Expand Down
70 changes: 68 additions & 2 deletions api/src/org/labkey/api/security/AuthenticationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import org.labkey.api.usageMetrics.UsageMetricsService;
import org.labkey.api.util.DateUtil;
import org.labkey.api.util.ExceptionUtil;
import org.labkey.api.util.GUID;
import org.labkey.api.util.HeartBeat;
import org.labkey.api.util.HtmlString;
import org.labkey.api.util.HtmlStringBuilder;
Expand Down Expand Up @@ -549,7 +550,12 @@ public static abstract class BaseSsoValidateAction<FORM extends AuthenticationCo
public ModelAndView getView(FORM form, BindException errors) throws Exception
{
AuthenticationResponse response = validateAuthentication(form, errors);
return response.isReauth() ? getReauthView(response, errors) : getAuthView(response, errors);
}

// Normal primary authentication case
private ModelAndView getAuthView(AuthenticationResponse response, BindException errors)
{
// Show validation error(s), if any
if (errors.hasErrors() || !response.isAuthenticated())
{
Expand Down Expand Up @@ -586,6 +592,36 @@ public ModelAndView getView(FORM form, BindException errors) throws Exception
return new SimpleErrorView(errors, false);
}

// 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.
private ModelAndView getReauthView(AuthenticationResponse response, BindException errors)
{
String errorMessage = null;

if (!response.isAuthenticated())
{
errorMessage = errors.hasErrors() ? errors.getMessage() : "Reauthentication failed";
}

LoginReturnProperties properties = getLoginReturnProperties(getViewContext().getRequestOrThrow());

// We lost the return URL. Could happen on local session timeout.
if (properties == null)
throw new NotFoundException("Reauthentication failed");

URLHelper url = properties.getReturnUrl();

if (null == url)
throw new NotFoundException("Reauthentication failed: no return URL");

@Nullable User reauthUser = response.isAuthenticated() ? UserManager.getUser(response.getValidEmail()) : null;

AuthenticationManager.setReauthUser(reauthUser, getUser(), getViewContext().getRequestOrThrow(), errorMessage, url);

throw new RedirectException(url);
}

@Override
protected String getCommandClassMethodName()
{
Expand Down Expand Up @@ -1769,8 +1805,35 @@ public static URLHelper getWelcomeURL()

public record Reauth(String token, User user){}
public static final String REAUTH_TOKEN_NAME = "reauthToken";
public static final String ERROR_MESSAGE = "errorMessage";

public static @Nullable User getAndClearReauthUser(HttpServletRequest request, @Nullable String token)
/**
* @param reauthUser Re-auth user to stash in session with the re-auth token
* @param sessionUser If not null, validate that this user and reauthUser are the same
* @param request Request from which to retrieve the session
* @param errorMessage Pre-existing error message to add to the URL
* @param redirectUrl URL to which the token (on success) or error message (on failure) gets added
*/
public static void setReauthUser(User reauthUser, @Nullable User sessionUser, HttpServletRequest request, @Nullable String errorMessage, URLHelper redirectUrl)
{
if (errorMessage == null && sessionUser != null && !sessionUser.equals(reauthUser))
{
errorMessage = "Reauthentication failed: wrong user reauthenticated";
}

if (errorMessage != null)
{
redirectUrl.addParameter(ERROR_MESSAGE, errorMessage);
}
else
{
String reauthToken = GUID.makeHash();
redirectUrl.addParameter(REAUTH_TOKEN_NAME, reauthToken);
request.getSession().setAttribute(REAUTH_TOKEN_NAME, new Reauth(reauthToken, reauthUser));
}
}

public static @Nullable User getAndClearReauthUser(HttpServletRequest request, @Nullable String token, @Nullable User sessionUser)
{
if (token != null)
{
Expand All @@ -1787,7 +1850,10 @@ public record Reauth(String token, User user){}
if (matches)
{
session.removeAttribute(REAUTH_TOKEN_NAME);
return reauth.user();
User reauthUser = reauth.user();

if (sessionUser == null || sessionUser.equals(reauthUser))
return reauthUser;
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions api/src/org/labkey/api/security/AuthenticationProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ class AuthenticationResponse
private @NotNull Map<String, String> _userAttributeMap = Collections.emptyMap(); // A case-insensitive map of attribute names and values associated with the user
private @NotNull Map<String, Object> _authenticationProperties = Collections.emptyMap();
private boolean _requireSecondary = true; // Require secondary authentication
private boolean _reauth = false;
private @Nullable String _successDetails = null; // An optional string describing how successful authentication took place, which will
// appear in the audit log. If null, the configuration's description will be used.

Expand Down Expand Up @@ -447,6 +448,17 @@ public AuthenticationResponse setRequireSecondary(boolean requireSecondary)
_requireSecondary = requireSecondary;
return this;
}

public boolean isReauth()
{
return _reauth;
}

public AuthenticationResponse setReauth(boolean reauth)
{
_reauth = reauth;
return this;
}
}

// FailureReasons are only reported to administrators (in the audit log and/or server log), NOT to users (and potential
Expand Down
3 changes: 2 additions & 1 deletion api/src/org/labkey/api/security/LoginUrls.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public interface LoginUrls extends UrlProvider
ActionURL getLoginURL(URLHelper returnUrl);
ActionURL getRegisterURL(Container c, @Nullable URLHelper returnUrl);
ActionURL getLoginURL(Container c, @Nullable URLHelper returnUrl);
ActionURL getForceReauthURL(Container c, @Nullable URLHelper returnUrl);
ActionURL getForceReauthURL(Container c, boolean local, @Nullable URLHelper returnUrl);
ActionURL getLogoutURL(Container c);
ActionURL getLogoutURL(Container c, URLHelper returnUrl);
ActionURL getStopImpersonatingURL(Container c, @Nullable URLHelper returnUrl);
ActionURL getAgreeToTermsURL(Container c, URLHelper returnUrl);
ActionURL getSSORedirectURL(SSOAuthenticationConfiguration<?> configuration, URLHelper returnUrl, boolean skipProfile);
ActionURL getSSOReauthURL(SSOAuthenticationConfiguration<?> configuration, URLHelper returnUrl);
}
4 changes: 2 additions & 2 deletions core/resources/views/login.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<form class="auth-form" name="login" method="post">
<div class="auth-header">Sign In</div>
<div class="auth-header" id="header">Sign In</div>
<div class="labkey-error" id="errors"></div>
<div class="auth-form-body">
<label for="email">Email</label>
Expand All @@ -23,7 +23,7 @@
<div class="auth-item auth-credentials-submit">
<!-- Note: login.js attaches an authenticateUser() click event to elements with classes "loginSubmitButton" and "signin-btn" -->
<input type="submit" tabindex="-1" class="loginSubmitButton"/>
<button class="labkey-button primary signin-btn" type="button"><span>Sign In</span></button>
<button class="labkey-button primary signin-btn" type="button"><span id="button">Sign In</span></button>
<span class="registrationSection" hidden>
<a class="labkey-button" id="registerButton" href="login-register.view">Register</a>
</span>
Expand Down
Loading