Skip to content

Commit

Permalink
feat: OIDC flow updated (#599)
Browse files Browse the repository at this point in the history
* chore: added debug messages

* chore: obiba commons snapshot and session related log messages

* feat: updated oidc session handling

* fix: set realm domain in u_auth cookie, added log messages

* fix: added Location header with redirect response

* chore: added signup with debug logs

* fix: handle case signup with cookie is not available

* feat: refresh signup with if u_auth is missing
  • Loading branch information
ymarcon authored Feb 27, 2025
1 parent 9fec230 commit c2c2af4
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,30 @@

package org.obiba.agate.security;

import org.obiba.oidc.OIDCSession;
import org.obiba.oidc.utils.DefaultOIDCSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class AuthSessionManager extends DefaultOIDCSessionManager {
private final Logger log = LoggerFactory.getLogger(AuthSessionManager.class);
@Override
public void saveSession(OIDCSession session) {
log.debug("Saving session {}", session.getStateValue());
super.saveSession(session);
}

@Override
public boolean hasSession(String state) {
log.debug("Checking if session {} exists", state);
return super.hasSession(state);
}

@Override
public OIDCSession getSession(String state) {
log.debug("Getting session {}", state);
return super.getSession(state);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.obiba.agate.config;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class AgateSessionListener implements HttpSessionListener {
private static final Logger logger = LoggerFactory.getLogger(AgateSessionListener.class);

@Override
public void sessionCreated(HttpSessionEvent event) {
logger.info("🔹 Session Created: ID={}", event.getSession().getId());
}

@Override
public void sessionDestroyed(HttpSessionEvent event) {
logger.warn("⚠️ Session Destroyed: ID={}, Possible Cause: Timeout or Explicit Invalidation",
event.getSession().getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,12 @@ public ModelAndView signup(@CookieValue(value = "NG_TRANSLATE_LANG_KEY", require
}

@GetMapping("/signup-with")
public ModelAndView signupWith(@CookieValue(value = "u_auth", required = false, defaultValue = "{}") String uAuth) {
public ModelAndView signupWith(@CookieValue(value = "u_auth", required = false, defaultValue = "{}") String uAuth, @RequestParam(value = "redirect", required = false) String redirect) {
if (!configurationService.getConfiguration().isJoinPageEnabled())
return new ModelAndView("redirect:" + configurationService.getContextPath() + "/");

ModelAndView mv = new ModelAndView("signup-with");
mv.getModel().put("authConfig", new AuthConfiguration(configurationService.getConfiguration(), clientConfiguration));
JSONObject uAuthObj;
try {
String fixedUAuth = uAuth.replaceAll("\\\\", "");
Expand All @@ -132,12 +133,12 @@ public ModelAndView signupWith(@CookieValue(value = "u_auth", required = false,
uAuthObj = new JSONObject();
}

if (uAuthObj.has("username")) {
mv.getModel().put("uAuth", uAuthObj);
mv.getModel().put("authConfig", new AuthConfiguration(configurationService.getConfiguration(), clientConfiguration));
log.debug("Signup with username {}", uAuth);
mv.getModel().put("uAuth", uAuthObj.toMap());
if (uAuthObj.has("username") || Strings.isNullOrEmpty(redirect)) {
return mv;
}
return new ModelAndView("redirect:/signup");
return check(redirect);
}

@GetMapping("/signout")
Expand Down Expand Up @@ -224,6 +225,10 @@ public ModelAndView reset(@RequestParam(value = "key", required = false) String
return mv;
}

//
// Private methods
//

private String getLang(String language, String locale) {
return language == null ? locale : language;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

public abstract class AbstractAgateAuthenticationFilter extends OIDCLoginFilter {

private static final Logger log = LoggerFactory.getLogger(AbstractAgateAuthenticationFilter.class);

private final RealmConfigService realmConfigService;

private final ConfigurationService configurationService;
Expand Down Expand Up @@ -77,6 +82,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
}
}

protected J2EContext makeJ2EContext(HttpServletRequest request, HttpServletResponse response) {
String sid = request.getRequestedSessionId();
log.debug("login filter requested session id: {}", sid);
return super.makeJ2EContext(request, response);
}

/**
* Creates an OIDC session and adds the <code>action</code> request parameter to be used later by the callback filter.
*
Expand All @@ -95,14 +106,14 @@ protected OIDCSession makeSession(J2EContext context, AuthenticationRequest auth
}

@Override
protected String makeCallbackURL(String provider) {
protected String makeCallbackURL(String provider, String callbackURL) {
RealmConfig realmConfig = realmConfigService.findConfig(provider);
// get agate's callback url from this realm config, if defined
if (realmConfig.hasPublicUrl()) {
String callbackURL = realmConfig.getPublicUrl() + "/auth/callback/";
return makeCallbackURL(provider, callbackURL);
String cbURL = realmConfig.getPublicUrl() + "/auth/callback/";
return super.makeCallbackURL(provider, cbURL);
}
// otherwise fallback to agate's public url
return super.makeCallbackURL(provider);
return super.makeCallbackURL(provider, callbackURL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,20 +141,24 @@ protected void onAuthenticationError(OIDCSession session, String error, HttpServ
@Override
protected void onRedirect(OIDCSession session, J2EContext context, String provider) throws IOException {
if (session == null) return;
if (context.getResponse().isCommitted()) return;

Map<String, String[]> requestParameters = session.getRequestParameters();
String action = retrieveRequestParameter(FilterParameter.ACTION.value(), requestParameters);

String redirect = retrieveRequestParameter(FilterParameter.REDIRECT.value(), requestParameters);
log.debug("onRedirect: Action: {}", action);
log.debug("onRedirect: Redirect URL (params): {}", redirect);

if (Strings.isNullOrEmpty(redirect)) {
if (FilterAction.SIGNIN.equals(FilterAction.valueOf(action))) {
context.getResponse().sendRedirect(retrieveRedirectUrl(requestParameters));
redirect = retrieveRedirectUrl(requestParameters);
} else {
context.getResponse().sendRedirect(retrieveSignupRedirectUrl(requestParameters));
redirect = retrieveSignupRedirectUrl(requestParameters);
}
} else {
context.getResponse().sendRedirect(redirect);
}
log.debug("onRedirect: Redirect URL (location): {}", redirect);
context.getResponse().addHeader(HttpHeaders.LOCATION, redirect);
context.getResponse().sendRedirect(redirect);
}

/**
Expand Down Expand Up @@ -258,7 +262,7 @@ private void signInWithTicket(OIDCCredentials credentials, HttpServletResponse r
} else {
log.info("Agate Authentication failure for '{}', user does not exist in Agate", credentials.getUsername(getUsernameClaim(realmConfig)));
try {
setUserAuthCookieForSignUp(credentials, oidcAuthenticationToken, response, provider, errorUrl);
setUserAuthCookieForSignUp(credentials, response, provider, errorUrl);
} catch (JSONException e) {
// ignore
}
Expand Down Expand Up @@ -322,7 +326,7 @@ private void signIn(OIDCCredentials credentials, HttpServletResponse response, S
} else {
log.info("Agate Authentication failure for '{}', user does not exist in Agate", credentials.getUsername(getUsernameClaim(config)));
try {
setUserAuthCookieForSignUp(credentials, oidcAuthenticationToken, response, provider, errorUrl);
setUserAuthCookieForSignUp(credentials, response, provider, errorUrl);
} catch (JSONException e) {
// ignore
}
Expand All @@ -337,14 +341,14 @@ private void signUp(OIDCCredentials credentials, HttpServletResponse response, S
User user = userService.findUser(credentials.getUsername(getUsernameClaim(config)));

if (user == null) {
setUserAuthCookieForSignUp(credentials, oidcAuthenticationToken, response, provider, errorUrl);
setUserAuthCookieForSignUp(credentials, response, provider, errorUrl);
} else {
log.info("SignUp failure for '{}' with provider '{}', user already exists in Agate", credentials.getUsername(getUsernameClaim(config)), provider);
sendRedirectOrSendError(errorUrl, "Can't sign up with these credentials.", response);
}
}

private void setUserAuthCookieForSignUp(OIDCCredentials credentials, OIDCAuthenticationToken oidcAuthenticationToken, HttpServletResponse response, String provider, String errorUrl) throws IOException, JSONException {
private void setUserAuthCookieForSignUp(OIDCCredentials credentials, HttpServletResponse response, String provider, String errorUrl) throws IOException, JSONException {
RealmConfig realmConfig = realmConfigService.findConfig(provider);

if (realmConfig != null && realmConfig.isForSignup()) {
Expand All @@ -369,16 +373,15 @@ private void setUserAuthCookieForSignUp(OIDCCredentials credentials, OIDCAuthent

log.debug("User info mapped: {}", userMappedInfo);

// TODO get domain from realm config
// String domain = realmConfig.getDomain();
// if (Strings.isNullOrEmpty(domain)) domain = configuration.getDomain();

Configuration configuration = configurationService.getConfiguration();
// get domain from realm config
String domain = realmConfig.hasDomain() ? realmConfig.getDomain() : configuration.getDomain();
response.addHeader(HttpHeaders.SET_COOKIE, toCookieString(
new NewCookie.Builder("u_auth")
.value(URLUtils.encode(userMappedInfo.toString()).replaceAll("\\+", "%20"))
.path("/")
.domain(configuration.getDomain())
.domain(domain)
.maxAge(600)
.secure(true)
.httpOnly(true)
Expand Down Expand Up @@ -490,4 +493,9 @@ private String toCookieString(NewCookie cookie) {
return RuntimeDelegate.getInstance().createHeaderDelegate(NewCookie.class).toString(cookie);
}

protected J2EContext makeJ2EContext(HttpServletRequest request, HttpServletResponse response) {
String sid = request.getRequestedSessionId();
log.debug("callback filter requested session id: {}", sid);
return super.makeJ2EContext(request, response);
}
}
17 changes: 10 additions & 7 deletions agate-webapp/src/main/resources/_templates/signup-with.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<#include "libs/head.ftl">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>${config.name!""} | <@message "sign-up"/></title>
<title>${config.name?default("")} | <@message "sign-up"/></title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
Expand All @@ -18,7 +18,7 @@
<body id="signup-with-page" class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<a href="${contextPath}/"><b>${config.name!""}</b></a>
<a href="${contextPath}/"><b>${config.name?default("")}</b></a>
</div>
<!-- /.login-logo -->
<div class="card">
Expand All @@ -32,37 +32,37 @@
<form id="form" method="post">

<div class="input-group mb-3">
<input name="username" type="text" class="form-control" placeholder="<@message "username"/>" value="${uAuth.username!""}" readonly>
<input name="username" type="text" class="form-control" placeholder="<@message "username"/>" value="${uAuth.username?default("")}" readonly>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input name="email" type="email" class="form-control" placeholder="<@message "email"/>" value="${uAuth.email!""}" readonly>
<input name="email" type="email" class="form-control" placeholder="<@message "email"/>" value="${uAuth.email?default("")}" readonly>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-envelope"></span>
</div>
</div>
</div>
<input type="hidden" name="realm" value="${uAuth.realm}"/>
<input type="hidden" name="realm" value="${uAuth.realm?default("")}"/>

<div class="text-center">
<p>- <@message "personal-information"/> -</p>
</div>

<div class="input-group mb-3">
<input name="firstname" type="text" class="form-control" placeholder="<@message "firstname"/>" value="${uAuth.firstname!""}">
<input name="firstname" type="text" class="form-control" placeholder="<@message "firstname"/>" value="${uAuth.firstname?default("")}">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input name="lastname" type="text" class="form-control" placeholder="<@message "lastname"/>" value="${uAuth.lastname!""}">
<input name="lastname" type="text" class="form-control" placeholder="<@message "lastname"/>" value="${uAuth.lastname?default("")}">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
Expand Down Expand Up @@ -130,6 +130,9 @@

<#include "libs/scripts.ftl">
<script>
<#if !uAuth.realm??>
agatejs.refreshSignupWith()
</#if>
const requiredFields = [
{ name: 'email', title: "<@message "email"/>" },
{ name: 'firstname', title: "<@message "firstname"/>" },
Expand Down
5 changes: 5 additions & 0 deletions agate-webapp/src/main/webapp/assets/js/agate.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,17 @@ var agatejs = (function() {
window.location.search = kvp.join('&');
};

const agateRefreshSignupWith = function() {
window.location.assign(normalizeUrl('/signup-with?redirect=/signup'));
}

return {
'normalizeUrl': normalizeUrl,
'signin': agateSignin,
'signout': agateSignout,
'redirect': agateRedirect,
'signup': agateSignup,
'refreshSignupWith': agateRefreshSignupWith,
'forgotPassword': agateForgotPassword,
'resetPassword': agateResetPassword,
'updatePassword': agateUpdatePassword,
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<nimbus-jose-jwt.version>9.37.3</nimbus-jose-jwt.version>
<nosqlunit.version>0.7.9</nosqlunit.version>
<oauth-oidc-sdk.version>11.12</oauth-oidc-sdk.version>
<obiba-commons.version>4.2.0</obiba-commons.version>
<obiba-commons.version>4.3.0</obiba-commons.version>
<parsson.version>1.1.5</parsson.version>
<postgres.version>42.7.2</postgres.version>
<protobuf.version>3.25.5</protobuf.version>
Expand Down

0 comments on commit c2c2af4

Please sign in to comment.