package servlets;
import dbProcs.Getter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.Properties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.pac4j.saml.profile.SAML2Profile;
import utils.Hash;
import utils.LoginMethod;
import utils.SamlSso;
import utils.ShepherdLogManager;
import utils.SsoUserMapper;
import utils.UserKicker;
/**
* Control class for the authentication procedure.
*
* This file is part of the Security Shepherd Project.
*
*
The Security Shepherd project is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
*
The Security Shepherd project is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
*
You should have received a copy of the GNU General Public License along with the Security
* Shepherd project. If not, see .
*
* @author Mark Denihan
*/
public class ACS extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger log = LogManager.getLogger(ACS.class);
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!LoginMethod.isSaml()) {
// SSO is off (sso.enabled in sso.properties): this endpoint must not authenticate anyone,
// even if a SAML configuration is present
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
ShepherdLogManager.setRequestIp(request.getRemoteAddr(), request.getHeader("X-Forwarded-For"));
log.debug("**** servlets.ACS ***");
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
HttpSession ses = request.getSession(true);
Object language = ses.getAttribute("lang");
response.setContentType("text/plain");
log.debug("Getting ApplicationRoot");
String ApplicationRoot = getServletContext().getRealPath("");
log.debug("Servlet root = " + ApplicationRoot);
log.debug("Processing SAML data...");
Optional profile = SamlSso.authenticate(request, response);
log.debug("SAML data processed");
if (!profile.isPresent()) {
log.debug("User not authenticated");
ses.setAttribute("loginFailed", "Not authenticated");
response.sendRedirect("../login.jsp");
log.debug("**** End servlets.ACS ***");
return;
}
log.debug("User authenticated, unpacking SAML attributes...");
SsoUserMapper.Result ssoUser =
SsoUserMapper.map(profile.get().getAttributes(), loadSsoProperties());
String errorMessage = "";
boolean loggedIn = false;
if (ssoUser.isValid()) {
log.debug("ssoName = " + ssoUser.ssoName);
log.debug("userName = " + ssoUser.userName);
log.debug("userRole = " + ssoUser.userRole);
log.debug("Saml userdata loaded, calling authUserSSO");
String user[] =
Getter.authUserSSO(
ApplicationRoot, null, ssoUser.userName, ssoUser.ssoName, ssoUser.userRole);
if (user != null && !user[0].isEmpty()) {
// Kill Session and Create a new one with user logged in
log.debug("Creating new session for " + user[2] + " " + user[1]);
ses.invalidate();
ses = request.getSession(true);
ses.setAttribute("userStamp", user[0]);
ses.setAttribute("userName", user[1]);
ses.setAttribute("userRole", user[2]);
ses.setAttribute("lang", language);
// Kept for single logout, which needs the NameID and session index
ses.setAttribute(SamlSso.PROFILE_SESSION_ATTRIBUTE, profile.get());
// Lets an IdP-initiated logout end this session
SamlSso.registerLoginSession(ses, profile.get());
log.debug("userClassId = " + user[4]);
ses.setAttribute("userClass", user[4]);
if (user[5].equalsIgnoreCase("true")) {
log.debug("Temporary Username Detected, user will be prompted to change");
ses.setAttribute("ChangeUsername", "true");
}
log.debug("Setting CSRF cookie");
Cookie token = new Cookie("token", Hash.randomString());
if (request.getRequestURL().toString().startsWith("https")) // If Requested over HTTPs
{
token.setSecure(true);
}
// We must set the path because the ACS servlet is in a subdir...
token.setPath("/");
response.addCookie(token);
// Removing user from kick list. If they were on it before, their suspension
// must have ended if their authentication Succeeded
UserKicker.removeFromKicklist(user[1]);
loggedIn = true;
}
} else {
log.debug("SSO attributes rejected: " + ssoUser.error);
errorMessage = ssoUser.error;
}
if (loggedIn) {
response.sendRedirect("../index.jsp");
} else {
log.debug("Could not authenticate");
ses.setAttribute("loginFailed", errorMessage + "SSO login failed.");
// Lagging Response
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
response.sendRedirect("../login.jsp");
}
log.debug("**** End servlets.ACS ***");
}
/** Redirects user to index.jsp */
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.sendRedirect("../index.jsp");
}
private Properties loadSsoProperties() {
String unpackFileName = "sso.properties";
log.debug("Loading saml unpack properties file");
try (InputStream inputStream =
getClass().getClassLoader().getResourceAsStream(unpackFileName)) {
if (inputStream == null) {
String errorMsg =
"SAML unpack properties file '" + unpackFileName + "' not found in the classpath";
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}
Properties prop = new Properties();
prop.load(inputStream);
return prop;
} catch (IOException e) {
String errorMsg = "SAML unpack properties file '" + unpackFileName + "' cannot be loaded";
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
}