package org.sigmah.server.service;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* 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
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.sigmah.server.auth.SecureTokenGenerator;
import org.sigmah.server.dao.ContactDAO;
import org.sigmah.server.dao.ContactModelDAO;
import org.sigmah.server.dao.OrgUnitDAO;
import org.sigmah.server.dao.ProfileDAO;
import org.sigmah.server.dao.UserDAO;
import org.sigmah.server.dao.UserUnitDAO;
import org.sigmah.server.dispatch.impl.UserDispatch.UserExecutionContext;
import org.sigmah.server.domain.Contact;
import org.sigmah.server.domain.ContactModel;
import org.sigmah.server.domain.OrgUnit;
import org.sigmah.server.domain.User;
import org.sigmah.server.domain.profile.OrgUnitProfile;
import org.sigmah.server.domain.profile.Profile;
import org.sigmah.server.mail.MailService;
import org.sigmah.server.mapper.Mapper;
import org.sigmah.server.security.Authenticator;
import org.sigmah.server.service.base.AbstractEntityService;
import org.sigmah.server.service.util.PropertyMap;
import org.sigmah.shared.Language;
import org.sigmah.shared.command.result.UserUnitsResult;
import org.sigmah.shared.dispatch.CommandException;
import org.sigmah.shared.dispatch.FunctionalException;
import org.sigmah.shared.dispatch.FunctionalException.ErrorCode;
import org.sigmah.shared.dto.UserDTO;
import org.sigmah.shared.dto.UserUnitDTO;
import org.sigmah.shared.dto.base.EntityDTO;
import org.sigmah.shared.dto.profile.ProfileDTO;
import org.sigmah.shared.dto.referential.ContactModelType;
import org.sigmah.shared.dto.referential.EmailKey;
import org.sigmah.shared.dto.referential.EmailKeyEnum;
import org.sigmah.shared.dto.referential.EmailType;
import org.sigmah.shared.util.Users;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
/**
* {@link User} service implementation.
*
* @author nrebiai
* @author Denis Colliot (dcolliot@ideia.fr)
*/
@Singleton
public class UserService extends AbstractEntityService<User, Integer, UserDTO> {
/**
* Logger
*/
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
private final Injector injector;
private final Mapper mapper;
private final ContactDAO contactDAO;
private final ContactModelDAO contactModelDAO;
private final OrgUnitDAO orgUnitDAO;
private final ProfileDAO profileDAO;
private final UserDAO userDAO;
private final UserUnitDAO userUnitDAO;
private final MailService mailService;
private final Authenticator authenticator;
@Inject
public UserService(Injector injector, ContactDAO contactDAO, ContactModelDAO contactModelDAO, OrgUnitDAO orgUnitDAO, ProfileDAO profileDAO, UserDAO userDAO, UserUnitDAO userUnitDAO, MailService mailService, Mapper mapper, Authenticator authenticator) {
this.injector = injector;
this.contactDAO = contactDAO;
this.contactModelDAO = contactModelDAO;
this.orgUnitDAO = orgUnitDAO;
this.profileDAO = profileDAO;
this.userDAO = userDAO;
this.userUnitDAO = userUnitDAO;
this.mailService = mailService;
this.mapper = mapper;
this.authenticator = authenticator;
}
/**
* {@inheritDoc}
*/
@Override
public User create(final PropertyMap properties, final UserExecutionContext context) throws CommandException {
final User executingUser = context.getUser();
User userToPersist;
User userFound = null;
// get User that need to be saved from properties
final Integer id = properties.get(UserDTO.ID);
final String email = properties.get(UserDTO.EMAIL);
final String name = properties.get(UserDTO.NAME);
final String firstName = properties.get(UserDTO.FIRST_NAME);
final Language language = properties.get(UserDTO.LOCALE);
String password = properties.get(UserDTO.PASSWORD);
UserUnitsResult userUnits = properties.get(UserDTO.USER_UNITS);
if (email == null && name == null) {
throw new IllegalArgumentException("Invalid argument.");
}
// Saves user.
if(id != null) {
userFound = userDAO.findById(id);
}
userToPersist = createNewUser(email, name, language.getLocale());
userToPersist.setFirstName(firstName);
userToPersist.setOrganization(executingUser.getOrganization());
if (StringUtils.isNotBlank(password)) {
userToPersist.setHashedPassword(authenticator.hashPassword(password));
userToPersist.setChangePasswordKey(null);
userToPersist.setDateChangePasswordKeyIssued(new Date());
} else if (userFound != null) {
userToPersist.setHashedPassword(userFound.getHashedPassword());
}
if (userFound != null && userFound.getId() != null) {
userToPersist.setOrgUnitsWithProfiles(userUnitDAO.findAllOrgUnitProfilesByUserId(userFound.getId()));
// Updates user.
userToPersist.setId(userFound.getId());
// BUGFIX #736 : Keeping the active state of modified users.
userToPersist.setActive(userFound.getActive());
userToPersist = em().merge(userToPersist);
} else {
// Creates new user.
if (userDAO.doesUserExist(email)) {
throw new FunctionalException(ErrorCode.ADMIN_USER_DUPLICATE_EMAIL, email);
}
password = authenticator.generatePassword();
userToPersist.setHashedPassword(authenticator.hashPassword(password));
userToPersist = userDAO.persist(userToPersist, executingUser);
try {
// Invitation email parameters.
final Map<EmailKey, String> parameters = new HashMap<>();
parameters.put(EmailKeyEnum.USER_USERNAME, Users.getUserCompleteName(userToPersist.getFirstName(), userToPersist.getName()));
parameters.put(EmailKeyEnum.INVITING_USERNAME, Users.getUserCompleteName(executingUser.getFirstName(), executingUser.getName()));
parameters.put(EmailKeyEnum.INVITING_EMAIL, executingUser.getEmail());
parameters.put(EmailKeyEnum.APPLICATION_LINK, context.getApplicationUrl());
parameters.put(EmailKeyEnum.USER_LOGIN, email);
parameters.put(EmailKeyEnum.USER_PASSWORD, password);
mailService.send(EmailType.INVITATION, parameters, language, email);
} catch (final Exception e) {
// Ignore, don't abort because mail didn't work
if (LOG.isDebugEnabled()) {
LOG.debug("Error occured during invitation email sending. Continuing user creation process anyway.", e);
}
}
Integer contactId = properties.get(UserDTO.CONTACT);
if (contactId != null) {
// It means that the user was created from a contact
// Let's remove all duplicated properties which should stay in user entity
Contact contact = contactDAO.findById(contactId);
contact.setName(null);
contact.setFirstname(null);
contact.setMainOrgUnit(null);
contact.setSecondaryOrgUnits(new ArrayList<OrgUnit>());
// Now let's link the contact with the user
contact.setUser(userToPersist);
contactDAO.persist(contact, context.getUser());
} else {
// Let's create a contact for this user
ContactModel contactModel = contactModelDAO.findById((Integer) properties.get(UserDTO.CONTACT_MODEL));
Contact parent = contactDAO.findById((Integer) properties.get(UserDTO.CONTACT_ORGANIZATION));
Contact contact = new Contact();
contact.setUser(userToPersist);
contact.setContactModel(contactModel);
contact.setParent(parent);
contact.setDateCreated(new Date());
contactDAO.persist(contact, context.getUser());
}
}
// update link to profile
if (userToPersist.getId() != null) {
// Needs to remove all subs profiles to avoid orphan entities
while (!userToPersist.getOrgUnitsWithProfiles().isEmpty()) {
userToPersist.getOrgUnitsWithProfiles().get(0).getProfiles().removeAll(userToPersist.getOrgUnitsWithProfiles().get(0).getProfiles());
userToPersist.getOrgUnitsWithProfiles().remove(0);
}
userToPersist.getOrgUnitsWithProfiles().removeAll(userToPersist.getOrgUnitsWithProfiles());
OrgUnitProfile mainOrgUnitProfile = createOrUpdateUserUnit(userUnits.getMainUserUnit(), userToPersist);
if (mainOrgUnitProfile == null) {
throw new IllegalStateException("A main orgUnit profile should be provided.");
}
userToPersist.getOrgUnitsWithProfiles().add(mainOrgUnitProfile);
// Avoid duplicates by checking the OrgUnit id of each UserUnit
Set<Integer> orgUnitIds = new HashSet<>();
orgUnitIds.add(userUnits.getMainUserUnit().getOrgUnit().getId());
for (UserUnitDTO userUnitDTO : userUnits.getSecondaryUserUnits()) {
if (!orgUnitIds.add(userUnitDTO.getOrgUnit().getId())) {
continue;
}
OrgUnitProfile secondaryOrgUnitProfile = createOrUpdateUserUnit(userUnitDTO, userToPersist);
userToPersist.getOrgUnitsWithProfiles().add(secondaryOrgUnitProfile);
}
userToPersist = userDAO.persist(userToPersist, context.getUser());
}
return userToPersist;
}
/**
* {@inheritDoc}
*/
@Override
protected EntityDTO<?> handleMapping(final User createdUser) throws CommandException {
UserDTO userPersisted = null;
if (createdUser != null) {
userPersisted = mapper.map(createdUser, new UserDTO(), UserDTO.Mode.WITH_BASE_ORG_UNIT_AND_BASE_PROFILES);
userPersisted.setIdd(createdUser.getId());
}
return userPersisted;
}
/**
* {@inheritDoc}
*/
@Override
public User update(Integer entityId, PropertyMap changes, final UserExecutionContext context) {
throw new UnsupportedOperationException("No policy update operation implemented for '" + entityClass.getSimpleName() + "' entity.");
}
private OrgUnitProfile createOrUpdateUserUnit(UserUnitDTO userUnitDTO, User user) {
OrgUnitProfile orgUnitProfile = new OrgUnitProfile();
if (userUnitDTO.getMainUserUnit()) {
orgUnitProfile.setType(OrgUnitProfile.OrgUnitProfileType.MAIN);
} else {
orgUnitProfile.setType(OrgUnitProfile.OrgUnitProfileType.SECONDARY);
}
orgUnitProfile.setUser(user);
// Apply the new orgUnit
Integer orgUnitId = userUnitDTO.getOrgUnit().getId();
orgUnitProfile.setOrgUnit(orgUnitDAO.findById(orgUnitId));
// Apply the new profiles
Set<Integer> profileIds = new HashSet<>();
for (ProfileDTO profileDTO : userUnitDTO.getProfiles()) {
if (profileDTO == null) {
continue;
}
profileIds.add(profileDTO.getId());
}
orgUnitProfile.setProfiles(profileDAO.findByIds(profileIds));
return orgUnitProfile;
}
/**
* <p>
* Initializes a new {@link User} with a secure changePasswordKey.
* </p>
* <p>
* Sets the following user properties:
* <ul>
* <li>{@code name}</li>
* <li>{@code email} (populated with given one)</li>
* <li>{@code newUser} (set to {@code true})</li>
* <li>{@code locale} (populated with given one)</li>
* <li>{@code changePasswordKey} (see {@link SecureTokenGenerator#generate()})</li>
* </ul>
* </p>
*
* @param email
* The user email value.
* @param name
* The user name value.
* @param locale
* The user locale value.
* @return the initialized new user.
*/
private static User createNewUser(final String email, final String name, final String locale) {
final User user = new User();
user.setEmail(email);
user.setName(name);
user.setNewUser(true);
user.setLocale(locale);
user.setChangePasswordKey(SecureTokenGenerator.generate());
// BUGFIX: #771 new users should be active when created.
user.setActive(Boolean.TRUE);
return user;
}
}