LoginServlet.java
/*
* Copyright (C) 2020-2024 by Savoir-faire Linux
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.jami.jams.server.servlets;
import static net.jami.jams.server.Server.certificateAuthority;
import static net.jami.jams.server.Server.dataStore;
import static net.jami.jams.server.servlets.api.auth.login.AuthRequestProcessor.processUsernamePasswordAuth;
import static net.jami.jams.server.servlets.api.auth.login.AuthRequestProcessor.processX509Auth;
import com.google.gson.Gson;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.common.annotations.JsonContent;
import net.jami.jams.common.authmodule.AuthTokenResponse;
import net.jami.jams.common.objects.user.User;
import net.jami.jams.common.serialization.adapters.GsonFactory;
import net.jami.jams.common.serialization.tomcat.TomcatCustomErrorHandler;
import net.jami.jams.server.servlets.api.auth.login.LoginRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Optional;
@WebServlet("/api/login")
// This method returns the token which is used for all the next calls to the
// API.
public class LoginServlet extends HttpServlet {
private final Gson gson = GsonFactory.createGson();
/**
* @apiVersion 1.0.0
* @api {post} /api/login Obtain an 0Auth token
* @apiName postLogin
* @apiGroup Login
* @apiParam {header} [authorization] classical HTTP auth header
* @apiParam {attribute} [X509Certificate] X509 User certificate
* @apiParam {body} [LoginRequest] username/password sent to server as JSON object
* @apiSuccess (200) {body} AuthTokenResponse the 0Auth authentication token
* @apiError (403) {null} null The user is unauthorized
*/
@Override
@JsonContent
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
AuthTokenResponse res = null;
// Case 1: Authorization header.
if (req.getHeader("authorization") != null) {
res = processUsernamePasswordAuth(req.getHeader("authorization"));
}
// Case 2 SSL Certificate
String clientCert = req.getHeader("X-Client-Cert");
if (JamsCA.reverseProxy && clientCert != null) {
try {
// URL-decode the header value
String decodedHeader = URLDecoder.decode(clientCert, StandardCharsets.UTF_8.name());
// Remove the PEM header and footer
String pem =
decodedHeader
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replaceAll("\\s+", ""); // Remove all whitespace characters
// Decode the Base64 encoded certificate
byte[] decodedBytes = Base64.getDecoder().decode(pem);
// Generate the X509Certificate object
CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(decodedBytes);
X509Certificate certificate = (X509Certificate) factory.generateCertificate(in);
// Process the certificate as needed
res = processX509Auth(certificate);
} catch (Exception e) {
e.printStackTrace();
}
} else if (req.getAttribute("jakarta.servlet.request.X509Certificate") != null) {
res =
processX509Auth(
(X509Certificate[])
req.getAttribute("jakarta.servlet.request.X509Certificate"));
} else {
// Case 4: form submitted username/password
LoginRequest object = gson.fromJson(req.getReader(), LoginRequest.class);
if (object.getUsername() != null && object.getPassword() != null) {
res = processUsernamePasswordAuth(object.getUsername(), object.getPassword());
Optional<User> user = dataStore.getUserDao().getByUsername(object.getUsername());
if (user.isPresent()
&& certificateAuthority.getLatestCRL().get() != null
&& !user.get().getAccessLevelName().equals("ADMIN")
&& certificateAuthority
.getLatestCRL()
.get()
.getRevokedCertificate(
user.get().getCertificate().getSerialNumber())
!= null) {
TomcatCustomErrorHandler.sendCustomError(
resp, 401, "Invalid credentials provided!");
}
}
}
resp.setContentType("application/json;charset=UTF-8");
if (res == null)
TomcatCustomErrorHandler.sendCustomError(resp, 401, "Invalid credentials provided!");
else {
resp.getOutputStream().write(gson.toJson(res).getBytes());
resp.flushBuffer();
}
}
}