package fr.lirmm.aren.ws.rest; import javax.annotation.security.RolesAllowed; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import fr.lirmm.aren.model.ws.ResetPassword; import fr.lirmm.aren.service.InstitutionService; import fr.lirmm.aren.service.UserService; import fr.lirmm.aren.exception.AccessDeniedException; import fr.lirmm.aren.model.User; import fr.lirmm.aren.model.ws.ChangePassword; import fr.lirmm.aren.model.ws.UserCredentials; import fr.lirmm.aren.producer.Configurable; import fr.lirmm.aren.security.token.AuthenticationTokenDetails; import fr.lirmm.aren.security.token.AuthenticationTokenService; import fr.lirmm.aren.service.AuthentificationService; import fr.lirmm.aren.service.MailingService; import fr.lirmm.aren.service.NotificationService; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; import javax.annotation.security.PermitAll; import javax.mail.MessagingException; import javax.ws.rs.BadRequestException; import javax.ws.rs.DELETE; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.PathParam; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.NewCookie; import javax.ws.rs.core.Response; /** * JAX-RS resource class for Users managment * * @author Florent Descroix {@literal } */ @RequestScoped @Path("users") public class UserRESTFacade extends AbstractRESTFacade { @Inject private UserService userService; @Inject private InstitutionService institutionService; @Inject private MailingService mailingService; @Inject private AuthentificationService authentificationService; @Inject private AuthenticationTokenService authenticationTokenService; @Inject private NotificationService notificationService; @Inject @Configurable("reverse-proxy") private String reverseProxy; /** * */ @QueryParam("standalone") protected String standalone; /** * */ @QueryParam("returnUrl") protected String returnUrl; /** * * @return */ @Override protected UserService getService() { return userService; } /** * Create a token for the User matching the credentials * * @param credentials to fetch an User * @return the token */ @POST @PermitAll @Path("auth") public String authenticate(UserCredentials credentials) { User user = authentificationService.validateCredentials(credentials.getUsername(), credentials.getPassword()); return authenticationTokenService.issueToken(user); } /** * Create a token for the User matching the credentials and encapsulate it * in a Cookie * * @param credentials to fetch an User * @return a Cookie */ @POST @PermitAll @Path("login") public Response login(UserCredentials credentials) { if (credentials == null) { throw new BadRequestException(); } String token = authenticate(credentials); int maxAge = -1; if (credentials.isRememberMe()) { maxAge = 360 * 24 * 60 * 60; } String domain; Response response; try { domain = new URL(request.getRequestURL().toString()).getHost(); NewCookie cookie = new NewCookie(HttpHeaders.AUTHORIZATION, token, "/", domain, "Authentification token for Aren platform", maxAge, false, true); response = Response.ok(token).cookie(cookie).build(); } catch (MalformedURLException ex) { response = Response.ok(token).build(); } return response; } /** * Remove the Cookie that stored the previously created token * * @return */ @POST @Path("logout") @PermitAll public Response logout() { String domain; Response response; try { domain = new URL(request.getRequestURL().toString()).getHost(); NewCookie cookie = new NewCookie(HttpHeaders.AUTHORIZATION, "", "/", domain, "", 0, false, true); response = Response.ok().cookie(cookie).build(); } catch (MalformedURLException ex) { response = Response.ok().build(); } return response; } /** * * @param id * @param entity * @return */ @Override @RolesAllowed({"USER"}) public User edit(Long id, User entity) { if (!(getUser().is("MODO") && getUser().hasSameOrMoreRightThan(entity) || getUser().getId().equals(entity.getId()))) { throw AccessDeniedException.PERMISSION_MISSING(); } if (!getUser().is("SUPERADMIN")) { User newUser = new User(); // The only alterable fields in User if (getUser().is("MODO") || getUser().getId().equals(entity.getId())) { newUser.setFirstName(entity.getFirstName()); newUser.setLastName(entity.getLastName()); newUser.setEmail(entity.getEmail()); } if (getUser().is("MODO") && getUser().hasSameOrMoreRightThan(entity)) { newUser.setAuthority(entity.getAuthority()); } entity = newUser; } return super.edit(id, entity); } /** * * @param user * @return */ @Override @RolesAllowed({"GUEST"}) public User create(User user) { if (user.getInstitution() == null || !getUser().is(User.Authority.SUPERADMIN)) { user.setInstitution(institutionService.getReference(0L)); } if (getUser().getAuthority() == User.Authority.GUEST) { user.setActive(false); super.create(user); try { sendLink(user, "mail_new_user_subject", "mail_new_user_body"); } catch (MessagingException ex) { super.remove(user.getId()); throw new RuntimeException(); } } else if (getUser().hasSameOrMoreRightThan(user)) { super.create(user); } else { throw AccessDeniedException.PERMISSION_MISSING(); } return user; } /** * * @param user * @param subject * @param body */ private void sendLink(User user, String subject, String body) throws MessagingException { Locale currentLocale = request.getLocale(); ResourceBundle messages = ResourceBundle.getBundle("messages", currentLocale); ResourceBundle application_config = ResourceBundle.getBundle("application", currentLocale); String token = authenticationTokenService.issueToken(user, 24L * 60 * 60); System.out.println(authenticationTokenService.parseToken(token).getIssuedDate()); String activationLink; String localSubject; String localBody; String serverRoot = this.reverseProxy; if (serverRoot.length() == 0) { serverRoot = request.getRequestURL().substring(0, request.getRequestURL().length() - "/ws/users".length()); } if (returnUrl != null && !returnUrl.isEmpty()) { activationLink = serverRoot + returnUrl.replace("{token}", token); localSubject = messages.getString(subject); localBody = MessageFormat.format(messages.getString(body), activationLink, activationLink); } else { localSubject = "AREN API token"; localBody = token; } mailingService.sendMail(application_config.getString("smtp.username"), user.getEmail(), localSubject, localBody); } private void sendLinkResetPassword(User user, String subject, String body) throws MessagingException { Locale currentLocale = request.getLocale(); ResourceBundle messages = ResourceBundle.getBundle("messages", currentLocale); ResourceBundle application_config = ResourceBundle.getBundle("application", currentLocale); String token = authenticationTokenService.issueToken(user, 24L * 60 * 60); System.out.println(authenticationTokenService.parseToken(token).getIssuedDate()); String activationLink; String localSubject; String localBody; String serverRoot = this.reverseProxy; if (serverRoot.length() == 0) { serverRoot=request.getRequestURL().substring(0, request.getRequestURL().length() - "/ws/users/resetPasswd".length()) ; } if (returnUrl != null && !returnUrl.isEmpty()) { activationLink = serverRoot + returnUrl.replace("{token}", token); localSubject = messages.getString(subject); localBody = MessageFormat.format(messages.getString(body), activationLink, activationLink); } else { localSubject = "AREN API token"; localBody = token; } mailingService.sendMail(application_config.getString("smtp.username"), user.getEmail(), localSubject, localBody); } /** * Mark a User as being activated after having clik in the mail link * */ @GET @Path("activate") @RolesAllowed({"USER"}) public void activateUser() { getUser().setActive(true); getService().edit(getUser()); getService().invalidateToken(getUser()); } /** * Ask for a password reset for a User * * @param identifier of the User */ @POST @Path("resetPasswd") @RolesAllowed({"GUEST"}) public void resetPasswd(String identifier) { User user = getService().findByUsernameOrEmail(identifier); if (user != null && user.isActive()) { try { sendLinkResetPassword(user, "mail_reset_password_subject", "mail_reset_password_body"); } catch (MessagingException ex) { throw new RuntimeException(); } } // else nothing happend, it avoids someone to bruteforce user } @PUT @Path("resetPswd") @RolesAllowed({"USER"}) public void resetPasswd(ResetPassword resetPasswd, @QueryParam("token") String token) { // Mail token are 24h long, login token are 1 year long // If this is a token from a mail, then it comes from a password reset requests // so we do not check the old password if (token != null && !token.isEmpty()) { AuthenticationTokenDetails authToken = authenticationTokenService.parseToken(token); if (authToken.getIssuedDate().plusSeconds(24 * 3600).isEqual(authToken.getExpirationDate())) { userService.changePassword(getUser(), resetPasswd.getPassword()); getService().invalidateToken(getUser()); return; } } authentificationService.validateCredentials(getUser().getUsername(), resetPasswd.getPassword()); userService.changePassword(getUser(), resetPasswd.getPassword()); getService().invalidateToken(getUser()); } /** * Mark a User as being removed without deleting its associated datas * * @param userId to remove */ @Override public void remove(Long userId) { User toDelete = getService().find(userId); notificationService.removeAllByUser(userId); getService().hide(userId); super.edit(userId, toDelete); } /** * Remove an User with its associated datas * * @param userId to remove */ @DELETE @Path("{id}/permanent") @RolesAllowed({"SUPERADMIN"}) public void permanentRemove(@PathParam("id") Long userId) { User entity = find(userId); safetyPreDeletion(entity); notificationService.removeAllByUser(userId); getService().remove(entity); } /** * Change User's password * * @param changePasswd * @param token */ @PUT @Path("passwd") @RolesAllowed({"USER"}) public void changePassword(ChangePassword changePasswd, @QueryParam("token") String token) { // Mail token are 24h long, login token are 1 year long // If this is a token from a mail, then it comes from a password reset requests // so we do not check the old password if (token != null && !token.isEmpty()) { AuthenticationTokenDetails authToken = authenticationTokenService.parseToken(token); if (authToken.getIssuedDate().plusSeconds(24 * 3600).isEqual(authToken.getExpirationDate())) { userService.changePassword(getUser(), changePasswd.getNewPassword()); getService().invalidateToken(getUser()); return; } } authentificationService.validateCredentials(getUser().getUsername(), changePasswd.getPassword()); userService.changePassword(getUser(), changePasswd.getNewPassword()); getService().invalidateToken(getUser()); } /** * * @return */ @Override @RolesAllowed({"ADMIN", "MODO"}) public Set findAll() { boolean alone = this.standalone != null; return getService().findAll(alone); } /** * Find the User the is currently authentificated * * @return */ @GET @Path("me") @RolesAllowed({"GUEST"}) public User getAuthenticatedUser() { return getUser(); } /** * Check the existance of an User by its username or mail * * @param usernameOrMail * @return */ @GET @Path("test") @RolesAllowed({"GUEST"}) public boolean exists(@QueryParam("identifier") String usernameOrMail) { return getService().findByUsernameOrEmail(usernameOrMail) != null; } }