ServiceConnection.java
/*
* Copyright (C) 2000 - 2025 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.org/legal/floss_exception.html"
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.silverpeas.mobile.server.services;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import org.silverpeas.core.admin.domain.model.Domain;
import org.silverpeas.core.admin.service.AdminException;
import org.silverpeas.core.admin.service.Administration;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.user.model.UserDetail;
import org.silverpeas.core.admin.user.model.UserFull;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.mail.MailAddress;
import org.silverpeas.core.mail.MailSending;
import org.silverpeas.core.security.authentication.AuthenticationCredential;
import org.silverpeas.core.security.authentication.AuthenticationResponse;
import org.silverpeas.core.security.authentication.AuthenticationServiceProvider;
import org.silverpeas.core.security.authentication.exception.AuthenticationException;
import org.silverpeas.core.chat.ChatUsersRegistration;
import org.silverpeas.core.web.rs.UserPrivilegeValidation;
import org.silverpeas.kernel.SilverpeasException;
import org.silverpeas.mobile.server.dao.SecurityCode;
import org.silverpeas.mobile.server.dao.statistics.StatisticsDAO;
import org.silverpeas.mobile.server.helpers.DataURLHelper;
import org.silverpeas.mobile.server.services.helpers.UserHelper;
import org.silverpeas.mobile.shared.dto.DetailUserDTO;
import org.silverpeas.mobile.shared.dto.DomainDTO;
import org.silverpeas.mobile.shared.exceptions.AuthenticationException.AuthenticationError;
import ua_parser.Client;
import ua_parser.Parser;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
* Service de gestion des connexions.
* @author svuillet
*/
@WebService
@Path(ServiceConnection.PATH)
public class ServiceConnection extends AbstractRestWebService {
private static ConcurrentHashMap<String, SecurityCode> securityCodeRequests = new ConcurrentHashMap<>();
private static long CODE_MAX_DURATION = 30;
@Inject
private ChatUsersRegistration chatUsersRegistration;
@Context
HttpServletRequest request;
@Inject
private OrganizationController organizationController;
@Inject
private StatisticsDAO statisticsDAO;
static final String PATH = "mobile/connection";
private boolean isUserGUIMobileForTablets() {
return getSettings().getBoolean("guiMobileForTablets", true);
}
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("login")
public DetailUserDTO login(List<String> ids) {
String login = ids.get(0);
String password = ids.get(1);
String domainId = ids.get(2);
// vérification
AuthenticationCredential credential = getCredentials(login, password, domainId);
AuthenticationResponse result =
AuthenticationServiceProvider.getService().authenticate(credential);
if (result == null || result.getStatus().isInError()) {
AuthenticationResponse.Status status =
result == null ? AuthenticationResponse.Status.BAD_LOGIN_PASSWORD : result.getStatus();
WebApplicationException e;
switch (status) {
case NO_PASSWORD:
e = new WebApplicationException(AuthenticationError.PwdNotAvailable.name());
break;
case PASSWORD_EXPIRED:
e = new WebApplicationException(AuthenticationError.PwdExpired.name());
break;
case PASSWORD_TO_CHANGE:
e = new WebApplicationException(AuthenticationError.PwdMustBeChanged.name());
break;
case PASSWORD_EMAIL_TO_CHANGE_ON_FIRST_LOGIN:
e = new WebApplicationException(AuthenticationError.PwdMustBeChangedOnFirstLogin.name());
break;
case USER_ACCOUNT_BLOCKED:
e = new WebApplicationException(AuthenticationError.UserAccountBlocked.name());
break;
case USER_ACCOUNT_DEACTIVATED:
e = new WebApplicationException(AuthenticationError.UserAccountDeactivated.name());
break;
default:
e = new WebApplicationException(AuthenticationError.BadCredential.name());
break;
}
throw e;
}
// récupération des informations de l'utilisateur
String userId;
try {
userId = getUserId(login, domainId);
} catch (Exception e) {
throw new WebApplicationException(AuthenticationError.Host.name());
}
UserDetail user = getUserDetail(userId);
setUserInSession(user);
try {
authenticate(login, password, domainId);
} catch (SilverpeasException e) {
throw new WebApplicationException(AuthenticationError.CanCreateMainSessionController.name());
}
initSilverpeasSession(request);
DetailUserDTO userDTO = UserHelper.getInstance().populate(user);
String avatar = DataURLHelper.convertAvatarToUrlData(user.getAvatarFileName(),
getSettings().getString("big.avatar.size", "40x"));
userDTO.setAvatar(avatar);
try {
userDTO.setStatus(new ServiceRSE().getStatus().getDescription());
} catch (Exception e) {
userDTO.setStatus("");
}
// chat init
request.getSession().setAttribute("Silverpeas.Chat",
chatUsersRegistration.isChatServiceEnabled() && user != null && !user.isAnonymous() &&
!user.isAccessGuest());
// Add connexion in stats
statisticsDAO.saveLoginEvent(Long.parseLong(userId), getPlatform(),getSettings().getString("version"),
getOsVersion(), getClientIp(getHttpRequest()), getPreferredLanguage(getHttpRequest()), getDevice());
return userDTO;
}
public String getPreferredLanguage(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.isEmpty()) {
return null;
}
// Prend la première langue (ex: fr-FR)
String firstLang = acceptLanguage.split(",")[0];
// Garde uniquement les 2 premiers caractères
return firstLang.substring(0, Math.min(2, firstLang.length())).toLowerCase();
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// Si X-Forwarded-For contient plusieurs IP séparées par des virgules, prendre la première
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
private short getPlatform() {
HttpServletRequest request = this.getHttpRequest();
String userAgent = request.getHeader("User-Agent");
short platform = 0; // 1=ios, 2=android, 0=autre
Parser parser = new Parser();
Client client = parser.parse(userAgent);
if (client.os.family.equalsIgnoreCase("android")) {
platform = 2; // Android
} else if(client.os.family.equalsIgnoreCase("ios")) {
platform = 1; // iOS
}
return platform;
}
private String getOsVersion() {
HttpServletRequest request = this.getHttpRequest();
String userAgent = request.getHeader("User-Agent");
Parser parser = new Parser();
Client client = parser.parse(userAgent);
return client.os.major + "." + client.os.minor;
}
private String getDevice() {
HttpServletRequest request = this.getHttpRequest();
String userAgent = request.getHeader("User-Agent");
Parser parser = new Parser();
Client client = parser.parse(userAgent);
return client.device.family;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("securityCode/check/{login}/{domainId}/{code}")
public Boolean checkSecurityCode(@PathParam("login") String login, @PathParam("domainId") String domainId,
@PathParam("code") String code) {
Boolean valid = Boolean.FALSE;
try {
String userId = Administration.get().getUserIdByLoginAndDomain(login, domainId);
UserDetail user = Administration.get().getUserDetail(userId);
SecurityCode sc = securityCodeRequests.get(user.getEmailAddress());
if (sc == null) return Boolean.FALSE;
Date now = new Date();
long diff = (now.getTime() - sc.getCreationDate().getTime()) / (1000 * 60);
if (diff < CODE_MAX_DURATION) {
return sc.getCode().equals(code);
}
} catch (Throwable e) {
throw new WebApplicationException(e);
}
return valid;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("securityCode/{login}/{domainId}")
public void generateSecurityCode(@PathParam("login") String login, @PathParam("domainId") String domainId) {
try {
String userId = Administration.get().getUserIdByLoginAndDomain(login, domainId);
UserDetail user = Administration.get().getUserDetail(userId);
if (securityCodeRequests.containsKey(user.getEmailAddress())) {
SecurityCode sc = securityCodeRequests.get(user.getEmailAddress());
Date now = new Date();
long diff = (now.getTime() - sc.getCreationDate().getTime()) / 1000;
if (diff >= CODE_MAX_DURATION) {
securityCodeRequests.remove(user.getEmailAddress());
String code = generateSecurityCode(user);
sendSecurityCode(user, code);
}
} else {
String code = generateSecurityCode(user);
sendSecurityCode(user, code);
}
} catch (Throwable e) {
throw new WebApplicationException(e);
}
}
private void sendSecurityCode(UserDetail user, String code) {
MailSending mail = MailSending.from(MailAddress.eMail(user.getEmailAddress()));
mail = mail.to(MailAddress.eMail(user.getEmailAddress()));
mail = mail.withContent("Code de sécurité");
mail = mail.withTextContent("Votre code est : " + code);
mail.send();
}
private String generateSecurityCode(UserDetail user) {
Random random = new Random();
String code = String.format("%04d", random.nextInt(10000));
securityCodeRequests.put(user.getEmailAddress(), new SecurityCode(code, new Date()));
return code;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("userExist/{login}/{domainId}")
public Boolean userExist(@PathParam("login") String login,
@PathParam("domainId") String domainId) {
try {
String id = getUserId(login, domainId);
return id != null;
} catch (Exception e) {
return false;
}
}
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("setTabletMode")
public Boolean setTabletMode() {
if (!isUserGUIMobileForTablets()) {
request.getSession().setAttribute("tablet", Boolean.TRUE);
return true;
}
return false;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("domains")
public List<DomainDTO> getDomains() {
Domain[] allDomains = organizationController.getAllDomains();
ArrayList<DomainDTO> domains = new ArrayList<>();
for (Domain allDomain : allDomains) {
domains.add(populate(allDomain));
}
return domains;
}
private String getUserId(String login, String domainId) throws AdminException {
return Administration.get().getUserIdByLoginAndDomain(login, domainId);
}
private UserDetail getUserDetail(String userId) {
return organizationController.getUserDetail(userId);
}
private DomainDTO populate(Domain domain) {
DomainDTO dto = new DomainDTO();
dto.setName(domain.getName());
dto.setId(domain.getId());
return dto;
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("changePwd/")
public void changePwd(String newPwd) {
if (getUserInSession() == null) {
throw new NotAuthorizedException(getHttpServletResponse());
}
UserFull user;
try {
user = Administration.get().getUserFull(getUserInSession().getId());
user.setPassword(newPwd);
Administration.get().updateUserFull(user);
} catch (AdminException e) {
throw new WebApplicationException(e);
}
}
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("userAcceptsTermsOfService")
public void userAcceptsTermsOfService() {
try {
Administration.get().userAcceptsTermsOfService(getUserInSession().getId());
} catch (AdminException e) {
throw new WebApplicationException(e);
}
}
protected void setUserInSession(UserDetail user) {
request.getSession().setAttribute(AbstractRestWebService.USER_ATTRIBUT_NAME, user);
}
protected UserDetail getUserInSession() {
return (UserDetail) request.getSession()
.getAttribute(AbstractRestWebService.USER_ATTRIBUT_NAME);
}
@Override
protected String getResourceBasePath() {
return PATH;
}
@Override
public String getComponentId() {
return null;
}
@Override
public void validateUserAuthorization(final UserPrivilegeValidation validation) {
// no need to validate the authorization
}
private AuthenticationCredential getCredentials(String login, String password, String domainId) {
try {
return AuthenticationCredential.newWithAsLogin(login)
.withAsPassword(password)
.withAsDomainId(domainId);
} catch (AuthenticationException e) {
throw new WebApplicationException(AuthenticationError.BadCredential.name());
}
}
}