UserServlet.java

  1. /*
  2.  * Copyright (C) 2020-2024 by Savoir-faire Linux
  3.  *
  4.  * This program is free software; you can redistribute it and/or modify
  5.  * it under the terms of the GNU General Public License as published by
  6.  * the Free Software Foundation; either version 3 of the License, or
  7.  * (at your option) any later version.
  8.  *
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU General Public License
  15.  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  16.  */
  17. package net.jami.jams.server.servlets.api.admin.users;

  18. import static net.jami.jams.server.Server.certificateAuthority;
  19. import static net.jami.jams.server.Server.dataStore;
  20. import static net.jami.jams.server.Server.nameServer;
  21. import static net.jami.jams.server.Server.userAuthenticationModule;

  22. import com.google.gson.Gson;
  23. import com.google.gson.JsonObject;

  24. import jakarta.servlet.ServletException;
  25. import jakarta.servlet.annotation.WebServlet;
  26. import jakarta.servlet.http.HttpServlet;
  27. import jakarta.servlet.http.HttpServletRequest;
  28. import jakarta.servlet.http.HttpServletResponse;

  29. import net.jami.jams.authmodule.PasswordUtil;
  30. import net.jami.jams.common.annotations.JsonContent;
  31. import net.jami.jams.common.annotations.ScopedServletMethod;
  32. import net.jami.jams.common.authentication.AuthenticationSourceType;
  33. import net.jami.jams.common.objects.devices.Device;
  34. import net.jami.jams.common.objects.responses.DeviceRevocationResponse;
  35. import net.jami.jams.common.objects.user.AccessLevel;
  36. import net.jami.jams.common.objects.user.User;
  37. import net.jami.jams.common.serialization.adapters.GsonFactory;
  38. import net.jami.jams.server.core.workflows.RevokeDeviceFlow;
  39. import net.jami.jams.server.core.workflows.RevokeUserFlow;

  40. import org.apache.commons.codec.binary.Base64;
  41. import org.bouncycastle.cert.X509CRLEntryHolder;
  42. import org.bouncycastle.cert.X509CRLHolder;

  43. import java.io.IOException;
  44. import java.util.List;
  45. import java.util.Optional;
  46. import java.util.concurrent.atomic.AtomicReference;

  47. @WebServlet("/api/admin/user")
  48. public class UserServlet extends HttpServlet {
  49.     private final Gson gson = GsonFactory.createGson();

  50.     // Get the user
  51.     @Override
  52.     @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN})
  53.     @JsonContent
  54.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  55.         String username = req.getParameter("username");
  56.         Optional<User> result = dataStore.getUserDao().getByUsername(username);
  57.         if (result.isEmpty()) {
  58.             resp.sendError(404, "An error occurred while attempting to obtain user.");
  59.         }

  60.         User user = result.get();
  61.         X509CRLHolder crl = certificateAuthority.getLatestCRL().get();
  62.         if (crl != null) {
  63.             X509CRLEntryHolder revoked =
  64.                     crl.getRevokedCertificate(user.getCertificate().getSerialNumber());
  65.             user.setRevoked(revoked != null);
  66.         } else {
  67.             user.setRevoked(false);
  68.         }

  69.         if (!user.getNeedsPasswordReset() && req.getParameter("needPW") != null) {
  70.             String pw = req.getParameter("password");

  71.             if (pw == null || pw.isEmpty()) {
  72.                 resp.sendError(400, "Password is empty!");
  73.                 return;
  74.             }

  75.             String password = PasswordUtil.hashPassword(pw, Base64.decodeBase64(user.getSalt()));
  76.             dataStore.getUserDao().updateObject(password, username);

  77.             user = dataStore.getUserDao().getByUsername(username).orElseThrow();
  78.         }
  79.         resp.getOutputStream().write(gson.toJson(user).getBytes());
  80.         resp.flushBuffer();
  81.         resp.setStatus(200);
  82.     }

  83.     // Create an internal user - this is always technically available, because
  84.     // internal users have the right to exist.
  85.     @Override
  86.     @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN})
  87.     @JsonContent
  88.     protected void doPost(HttpServletRequest req, HttpServletResponse resp)
  89.             throws ServletException, IOException {
  90.         JsonObject obj = gson.fromJson(req.getReader(), JsonObject.class);
  91.         String username = obj.get("username").getAsString();
  92.         String password = obj.get("password").getAsString();

  93.         if (password.isEmpty()) {
  94.             resp.sendError(400, "Password is empty!");
  95.             return;
  96.         }

  97.         byte[] salt = PasswordUtil.generateSalt();
  98.         String hashedPassword = PasswordUtil.hashPassword(password, salt);

  99.         User user = new User();
  100.         user.setUsername(username);
  101.         user.setNeedsPasswordReset(true);
  102.         user.setPassword(hashedPassword);
  103.         user.setSalt(Base64.encodeBase64String(salt));
  104.         user.setRealm("LOCAL");
  105.         user.setUserType(AuthenticationSourceType.LOCAL);

  106.         if (userAuthenticationModule.createUser(
  107.                 user.getUserType(), user.getRealm(), nameServer, user)) {
  108.             resp.setStatus(201);
  109.             return;
  110.         }

  111.         resp.sendError(500, "An error occurred while attempting to create the user.");
  112.     }

  113.     // Update user data.
  114.     @Override
  115.     @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN})
  116.     protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  117.         JsonObject obj = gson.fromJson(req.getReader(), JsonObject.class);
  118.         String username = obj.get("username").getAsString();
  119.         String pw = obj.get("password").getAsString();

  120.         Optional<User> result = dataStore.getUserDao().getByUsername(username);

  121.         if (result.isEmpty()) {
  122.             resp.sendError(404, "User was not found!");
  123.             return;
  124.         }

  125.         User user = result.get();

  126.         // Check if he is AD/LDAP - then return a 403, because we are unable to set such
  127.         // password.
  128.         if (user.getUserType() != AuthenticationSourceType.LOCAL) {
  129.             resp.sendError(500, "Unable to change user data as user is not a local user.");
  130.             return;
  131.         }

  132.         byte[] salt = PasswordUtil.generateSalt();
  133.         String password = PasswordUtil.hashPassword(pw, salt);
  134.         String encodedSalt = Base64.encodeBase64String(salt);
  135.         if (dataStore.getUserDao().updateObject(password, encodedSalt, username))
  136.             resp.setStatus(200);
  137.         else
  138.             resp.sendError(
  139.                     500, "An error occurred while attempting to update the user's data field.");
  140.     }

  141.     // Revoke a user.
  142.     @Override
  143.     @ScopedServletMethod(securityGroups = {AccessLevel.ADMIN})
  144.     protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  145.         String username = req.getParameter("username");
  146.         AtomicReference<DeviceRevocationResponse> devResponse =
  147.                 new AtomicReference<>(RevokeUserFlow.revokeUser(username));
  148.         List<Device> devices = dataStore.getDeviceDao().getByOwner(username);

  149.         if (certificateAuthority.getLatestCRL() != null) {
  150.             devices.forEach(
  151.                     device -> {
  152.                         if (certificateAuthority
  153.                                         .getLatestCRL()
  154.                                         .get()
  155.                                         .getRevokedCertificate(
  156.                                                 device.getCertificate().getSerialNumber())
  157.                                 == null)
  158.                             devResponse.set(
  159.                                     RevokeDeviceFlow.revokeDevice(username, device.getDeviceId()));
  160.                     });
  161.         }
  162.         if (devResponse.get() != null && devResponse.get().isSuccess()) {
  163.             resp.getOutputStream().write(gson.toJson(devResponse.get()).getBytes());
  164.             resp.flushBuffer();
  165.         } else resp.sendError(500, "An error occurred while revoking the user.");
  166.     }
  167. }