Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OIDC flow updated #599

Merged
merged 8 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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