package net.techreadiness.service;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import net.techreadiness.annotation.CoreDataModificationStatus;
import net.techreadiness.annotation.CoreDataModificationStatus.ModificationType;
import net.techreadiness.annotation.CoreSecured;
import net.techreadiness.persistence.AbstractAuditedBaseEntityWithExt;
import net.techreadiness.persistence.dao.EntityDAO.EntityTypeCode;
import net.techreadiness.persistence.dao.EntityFieldDAO;
import net.techreadiness.persistence.dao.ExtDAO;
import net.techreadiness.persistence.dao.ScopeDAO;
import net.techreadiness.persistence.dao.ScopeExtDAO;
import net.techreadiness.persistence.dao.UserCasDAO;
import net.techreadiness.persistence.dao.UserDAO;
import net.techreadiness.persistence.dao.UserOrgDAO;
import net.techreadiness.persistence.dao.UserRoleDAO;
import net.techreadiness.persistence.domain.EntityFieldDO;
import net.techreadiness.persistence.domain.PermissionDO;
import net.techreadiness.persistence.domain.ScopeDO;
import net.techreadiness.persistence.domain.ScopeExtDO;
import net.techreadiness.persistence.domain.UserDO;
import net.techreadiness.persistence.domain.UserRoleDO;
import net.techreadiness.security.CorePermissionCodes;
import net.techreadiness.security.PermissionCode;
import net.techreadiness.service.common.ValidationError;
import net.techreadiness.service.exception.AuthorizationException;
import net.techreadiness.service.exception.FaultInfo;
import net.techreadiness.service.exception.ServiceException;
import net.techreadiness.service.exception.ValidationServiceException;
import net.techreadiness.service.object.Org;
import net.techreadiness.service.object.Permission;
import net.techreadiness.service.object.Role;
import net.techreadiness.service.object.Scope;
import net.techreadiness.service.object.User;
import net.techreadiness.util.PasswordComplexityEvaluator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.log4j.Logger;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
@WebService
@Service
@Transactional
public class UserServiceImpl extends BaseServiceWithValidationAndExt<UserDO, AbstractAuditedBaseEntityWithExt<UserDO>>
implements UserService {
@Inject
@Qualifier("userExtDAOImpl")
private ExtDAO<UserDO, AbstractAuditedBaseEntityWithExt<UserDO>> userExtDAO;
@Inject
private UserDAO userDAO;
@Inject
private UserOrgDAO userOrgDAO;
@Inject
private UserCasDAO userCasDAO;
@Inject
private ScopeDAO scopeDAO;
@Inject
private ScopeExtDAO scopeExtDAO;
@Inject
private UserRoleService userRoleService;
@Inject
private UserOrgService userOrgService;
@Inject
private EntityFieldDAO entityFieldDAO;
@Inject
private UserRoleDAO userRoleDAO;
@PersistenceContext
private EntityManager em;
private static final DateFormat formatter = new SimpleDateFormat("MM/dd/yy");
private static final String PASSWORD_COMPLEXITY = "passwordComplexity";
public static final String RESET_PASSWORD = "ea756843f4a446e3063e5606dc407ec4";
private static final Logger log = Logger.getLogger(UserServiceImpl.class);
@Override
@Transactional(readOnly = true)
public User getByUsername(ServiceContext context, String username) {
return getMappingService().map(userDAO.findByUsername(username, true));
}
@Override
@WebMethod(exclude = true)
public User getById(ServiceContext context, Long userId) {
return getMappingService().map(userDAO.getById(userId));
}
@Override
@WebMethod(exclude = true)
public boolean userActive(ServiceContext context) {
UserDO user = userDAO.findByUsername(context.getUserName(), true);
if (null == user) {
return false;
}
if (null != user.getDeleteDate() || null != user.getDisableDate()) {
return false;
}
Date now = new Date();
if (null != user.getActiveBeginDate()) {
if (!now.after(user.getActiveBeginDate())) {
return false;
}
}
if (null != user.getActiveEndDate()) {
if (!now.before(user.getActiveEndDate())) {
return false;
}
}
return true;
}
@Override
@WebMethod(exclude = true)
@Transactional(readOnly = true)
@Cacheable(value = "hasPermission")
public boolean hasPermission(ServiceContext context, final PermissionCode... permissionCodes) {
if (context == null || context.getUserId() == null) {
return false;
}
Collection<GrantedAuthority> authorities = getGrantedAuthorities(context);
return userDAO.hasPermission(permissionCodes, authorities, context.getScopeId());
}
@Override
@Transactional(readOnly = true)
@WebMethod(exclude = true)
public boolean hasAccessToOrg(ServiceContext context, Long userId, Long orgId) {
return userDAO.hasAccessToOrg(userId, orgId);
}
private Collection<GrantedAuthority> getGrantedAuthorities(ServiceContext context) {
if (context == null) {
return Collections.emptySet();
}
Collection<GrantedAuthority> authorities = Lists.newArrayList();
List<UserRoleDO> userRoles = userRoleDAO.findUserRolesByUser(context.getUserId());
for (UserRoleDO userRole : userRoles) {
authorities.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRole().getRoleId())));
}
return authorities;
}
@Override
@WebMethod(exclude = true)
public List<Permission> findAvailablePermissions(ServiceContext context) {
List<PermissionDO> permissionDOs = userDAO.findPermissionsByRoles(getGrantedAuthorities(context),
context.getScopeId());
return getMappingService().mapFromDOList(permissionDOs);
}
@Override
/*
* at CoreFeature(CORE_USER) at CoreSecured({ CORE_CONFIG_USER_DELETE })
*/
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.DELETE, entityClass = UserDO.class)
public void delete(ServiceContext context, User user) {
UserDO userDO = userDAO.getById(user.getUserId());
// do not reset delete date if already listed with a date
if (userDO.getDeleteDate() == null) {
userDAO.delete(userDO);
}
user.setDeleteDate(userDO.getDeleteDate());
}
@Override
/*
* at CoreFeature(CORE_USER) at CoreSecured({ CORE_CONFIG_USER_DELETE })
*/
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.DELETE, entityClass = UserDO.class)
public void unDelete(ServiceContext context, User user) {
UserDO userDO = userDAO.getById(user.getUserId());
// do not undelete if already cleared
if (userDO.getDeleteDate() != null) {
userDAO.unDelete(userDO);
}
user.setDeleteDate(null);
}
@Override
@WebMethod(exclude = true)
public List<User> findAllUsersWithRole(ServiceContext context, Long roleId) {
return getMappingService().mapFromDOList(userDAO.findAllUsersWithRole(roleId));
}
@Override
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User disableUser(ServiceContext context, Long userId, Date disableDate, String reason) {
UserDO userDO = userDAO.getById(userId);
if (disableDate == null) {
disableDate = new Date();
}
userDO.setDisableDate(DateUtils.truncate(disableDate, Calendar.DAY_OF_MONTH));
userDO.setDisableReason(reason);
List<ValidationError> errors = performValidation(userDO.getAsMap(), userDO.getScope().getScopeId(),
EntityTypeCode.USER);
errors.addAll(performCrossFieldValidation(context, userDO.getAsMap(), userDO.getScope().getScopeId(),
EntityTypeCode.USER));
if (!errors.isEmpty()) {
FaultInfo faultInfo = new FaultInfo();
faultInfo.setMessage("User failed validation.");
faultInfo.setAttributeErrors(errors);
throw new ValidationServiceException(faultInfo);
}
return getMappingService().map(userDAO.update(userDO));
}
@Override
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User enableUser(ServiceContext context, Long userId) {
UserDO userDO = userDAO.getById(userId);
userDO.setDisableDate(null);
userDO.setDisableReason(null);
return getMappingService().map(userDAO.update(userDO));
}
@Override
@Transactional(readOnly = true)
@WebMethod(exclude = true)
public User getNew(ServiceContext context) {
User user = new User();
Scope scope = getMappingService().map(scopeDAO.getScopeForUsers(context.getScopeId()));
user.setScope(scope);
return user;
}
@Override
@Transactional(readOnly = true)
@WebMethod(exclude = true)
public boolean isUsernameUnique(ServiceContext context, String username, Long userId) throws ServiceException {
UserDO userDO = userDAO.findByUsername(username, true);
if (userDO == null) {
return true;
}
if (userDO.getUserId().equals(userId)) {
return true;
}
return false;
}
@Override
@WebMethod(exclude = true)
@CoreSecured(CorePermissionCodes.CORE_CUSTOMER_USER_CREATE)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User create(ServiceContext context, User user) throws ValidationServiceException, ServiceException {
UserDO userDO = getMappingService().map(user);
trimEmailAndUsername(userDO);
ScopeDO scopeDO = scopeDAO.getScopeForUsers(context.getScopeId());
userDO.setScope(scopeDO);
userDO.setSelectedScope(scopeDAO.getById(context.getScopeId()));
validate(context, userDO, userDO.getExtAttributes(), new ArrayList<ValidationError>());
userDAO.persist(userDO);
if (userDO.isExtAttributesNull()) {
userDO.setExtAttributes(new HashMap<String, String>());
}
storeExtFields(context, userDO, userExtDAO, EntityTypeCode.USER, userDO.getScope().getScopeId());
userCasDAO.create(userDO, RESET_PASSWORD);
return getMappingService().map(userDO);
}
@Override
@WebMethod(exclude = true)
@CoreSecured(CorePermissionCodes.CORE_CUSTOMER_USER_CREATE)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User createFromMap(ServiceContext context, Map<String, String> map) throws ValidationServiceException,
ServiceException {
UserDO userDO = userFromMap(context, map);
trimEmailAndUsername(userDO);
try {
if (StringUtils.isEmpty(map.get("activeBeginDate"))) {
userDO.setActiveBeginDate(null);
} else {
userDO.setActiveBeginDate(formatter.parse(map.get("activeBeginDate")));
}
if (StringUtils.isEmpty(map.get("activeEndDate"))) {
userDO.setActiveEndDate(null);
} else {
userDO.setActiveEndDate(formatter.parse(String.valueOf(map.get("activeEndDate"))));
}
} catch (ParseException pe) {
throw new ServiceException(messageSource.getMessage("validation.user.activeDates",
new Object[] { "MM/DD/YYYY" }, null), pe);
}
validate(context, userDO, map, new ArrayList<ValidationError>());
userDAO.persist(userDO);
if (userDO.isExtAttributesNull()) {
userDO.setExtAttributes(new HashMap<String, String>());
}
storeExtFields(context, userDO, userExtDAO, EntityTypeCode.USER, userDO.getScope().getScopeId());
userCasDAO.create(userDO, RESET_PASSWORD);
return getMappingService().map(userDAO.getById(userDO.getUserId()));
}
private UserDO userFromMap(ServiceContext context, Map<String, String> map) {
UserDO userDO = new UserDO();
ScopeDO scopeDO = scopeDAO.getScopeForUsers(context.getScopeId());
userDO.setScope(scopeDO);
userDO.setSelectedScope(scopeDAO.getById(context.getScopeId()));
userDO.setUsername(map.get("username") == null ? null : String.valueOf(map.get("username")));
userDO.setFirstName(map.get("firstName") == null ? null : String.valueOf(map.get("firstName")));
userDO.setLastName(map.get("lastName") == null ? null : String.valueOf(map.get("lastName")));
userDO.setEmail(map.get("email") == null ? null : String.valueOf(map.get("email")));
return userDO;
}
@Override
@WebMethod(exclude = true)
@CoreSecured(CorePermissionCodes.CORE_CUSTOMER_USER_CREATE)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User createFromMapWithRoles(ServiceContext context, Map<String, String> map, List<Role> roles, List<Org> orgs)
throws ValidationServiceException, ServiceException {
UserDO userDO = userFromMap(context, map);
trimEmailAndUsername(userDO);
List<ValidationError> errors = performValidation(userDO.getAsMap(), userDO.getScope().getScopeId(),
EntityTypeCode.USER);
errors.addAll(performCrossFieldValidation(context, userDO.getAsMap(), userDO.getScope().getScopeId(),
EntityTypeCode.USER));
if (CollectionUtils.isEmpty(roles)) {
String message = messageSource.getMessage("user.roles.needOne", null, Locale.getDefault());
errors.add(new ValidationError("roleValidation", "role", message, "validation.user.roles.needOne", message));
}
if (CollectionUtils.isEmpty(orgs)) {
String message = messageSource.getMessage("user.orgs.needOne", null, Locale.getDefault());
errors.add(new ValidationError("orgValidation", "org", message, "validation.user.orgs.needOne", message));
}
if (!errors.isEmpty()) {
FaultInfo faultInfo = new FaultInfo();
faultInfo.setMessage("User failed validation.");
faultInfo.setAttributeErrors(errors);
throw new ValidationServiceException(faultInfo);
}
User user = createFromMap(context, map);
for (Role role : roles) {
userRoleService.persist(context, user.getUserId(), role.getRoleId());
}
for (Org org : orgs) {
userOrgService.persist(context, user.getUserId(), org.getOrgId());
}
UserDO find = em.find(UserDO.class, user.getUserId());
em.refresh(find);
return getMappingService().getMapper().map(find, User.class);
}
private static void trimEmailAndUsername(UserDO userDO) {
if (null != userDO.getEmail()) {
userDO.setEmail(userDO.getEmail().trim());
}
if (null != userDO.getUsername()) {
userDO.setUsername(userDO.getUsername().trim());
}
}
@Override
@WebMethod(exclude = true)
@CoreSecured(CorePermissionCodes.CORE_CUSTOMER_USER_UPDATE)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public User update(ServiceContext context, User user) {
User oldUser;
if (user.getUserId() == null) {
oldUser = getByUsername(context, user.getUsername());
} else {
oldUser = getById(context, user.getUserId());
}
if (null == oldUser) {
FaultInfo faultInfo = new FaultInfo();
faultInfo.setMessage("Unable to find user.");
throw new ValidationServiceException(faultInfo);
}
ScopeDO scope = scopeDAO.getScopeForUsers(context.getScopeId());
if (!userDAO.isUserInScope(scope.getScopeId(), oldUser.getUserId())) {
throw new AuthorizationException(messageSource.getMessage("validation.user.updateWrongScope", new Object[] {
oldUser.getScope().getName(), context.getScopeName() }, Locale.getDefault()));
}
UserDO userDO = getMappingService().map(user);
trimEmailAndUsername(userDO);
userDO.setScope(scope);
userDO.setUserId(oldUser.getUserId());
validate(context, userDO, userDO.getExtAttributes(), new ArrayList<ValidationError>());
if (userDO.isExtAttributesNull()) {
userDO.setExtAttributes(new HashMap<String, String>());
}
copyExtFieldsToCore(context, userDO);
storeExtFields(context, userDO, userExtDAO, EntityTypeCode.USER, userDO.getScope().getScopeId());
userDO = userDAO.updateAccounts(userDO, oldUser.getUsername());
return getMappingService().map(userDO);
}
private void validate(ServiceContext context, UserDO userDO, Map<String, String> extAttributes,
List<ValidationError> errors) throws ValidationServiceException {
Map<String, String> userMap = new HashMap<>();
userMap.putAll(extAttributes);
userMap.putAll(userDO.getAsMap());
List<ValidationError> errorsVal = performValidation(userMap, userDO.getScope().getScopeId(), EntityTypeCode.USER);
errors.addAll(errorsVal);
if (!isUsernameUnique(context, userDO.getUsername(), userDO.getUserId())) {
String errorMessage = messageSource.getMessage("validation.user.username.alreadyExists",
new Object[] { userDO.getUsername() }, null);
EntityFieldDO usernameField = entityFieldDAO.findByScopeAndTypeAndCode(context.getScopeId(),
EntityTypeCode.USER, "username");
errors.add(new ValidationError(usernameField.getCode(), usernameField.getName(), errorMessage,
"validation.user.username.alreadyExists", errorMessage));
}
if (userDO.getActiveBeginDate() != null && userDO.getActiveEndDate() != null) {
if (userDO.getActiveEndDate().before(userDO.getActiveBeginDate())) {
EntityFieldDO activeEndDate = entityFieldDAO.findByScopeAndTypeAndCode(context.getScopeId(),
EntityTypeCode.USER, "activeEndDate");
EntityFieldDO activeStartDate = entityFieldDAO.findByScopeAndTypeAndCode(context.getScopeId(),
EntityTypeCode.USER, "activeBeginDate");
String errorMessage = messageSource.getMessage("validation.user.activeDateRange", new Object[] {
activeEndDate.getName(), activeStartDate.getName() }, null);
errors.add(new ValidationError(activeEndDate.getCode(), activeEndDate.getName(), errorMessage,
"validation.user.activeDateRange", errorMessage));
}
}
if (!errors.isEmpty()) {
FaultInfo faultInfo = new FaultInfo();
faultInfo.setMessage("User failed validation.");
faultInfo.setAttributeErrors(errors);
throw new ValidationServiceException(faultInfo);
}
}
@Override
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.UPDATE, entityClass = UserDO.class)
public void changePassword(ServiceContext context, String username, String password, String confirmPassword) {
List<ValidationError> errors = new ArrayList<>();
if (password == null || password.isEmpty() || password.length() < 8) {
String errorMessage = messageSource.getMessage("validation.user.password.minLength", null, null);
errors.add(new ValidationError("passwordField", "passwordField", errorMessage,
"validation.user.password.minLength", errorMessage));
}
if (!confirmPassword.equals(password)) {
String errorMessage = messageSource.getMessage("validation.user.password.notMatch", null, null);
errors.add(new ValidationError("confirmPasswordField", "confirmPasswordField", errorMessage,
"validation.user.password.notMatch", errorMessage));
}
User user = getByUsername(context, username);
ScopeExtDO complexitySEDO = scopeExtDAO.getLowestExistingConfigurationItem(user.getScope().getScopeId(),
PASSWORD_COMPLEXITY);
if (complexitySEDO == null) {
// default to complexity of 3 if none found
complexitySEDO = new ScopeExtDO();
complexitySEDO.setValue("3");
}
int complexity = PasswordComplexityEvaluator.getPasswordComplexity(password);
int configuredComplexity = 0;
if (StringUtils.isNotBlank(complexitySEDO.getValue())) {
configuredComplexity = Integer.valueOf(complexitySEDO.getValue());
}
if (complexity < configuredComplexity) {
String errorMessage = messageSource.getMessage("validation.user.password.complexity", null, null);
errors.add(new ValidationError("passwordField", "passwordField", errorMessage,
"validation.user.password.complexity", errorMessage));
}
if (!errors.isEmpty()) {
FaultInfo faultInfo = new FaultInfo();
faultInfo.setMessage("User failed validation.");
faultInfo.setAttributeErrors(errors);
throw new ValidationServiceException(faultInfo);
}
userDAO.changePassword(username, BCrypt.hashpw(password, BCrypt.gensalt()));
}
@Override
@WebMethod(exclude = true)
@CoreDataModificationStatus(modificationType = ModificationType.OTHER, entityClass = UserDO.class)
public void resetPassword(ServiceContext context, Long userId) {
UserDO userDO = userDAO.getById(userId);
userDAO.resetPassword(userDO);
log.info("ZZZ.ResetPasswordSent. User: " + userDO.getUsername() + " Email: " + userDO.getEmail());
}
@Override
@WebMethod(exclude = true)
public void newAccountPassword(ServiceContext context, User user) {
UserDO userDO = userDAO.getById(user.getUserId());
userDAO.newAccountPassword(userDO);
log.info("ZZZ.NewAccountSent. User: " + userDO.getUsername() + " Email: " + userDO.getEmail());
}
@Override
@WebMethod(exclude = true)
public void forgotUsername(ServiceContext context, String email) {
userDAO.forgotUsername(email);
}
@Override
@WebMethod(exclude = true)
public boolean isTokenValid(ServiceContext context, String username, String token) {
return userDAO.isTokenValid(username, token);
}
@Override
@WebMethod(exclude = true)
public void clearTokens(ServiceContext context, String username) {
userDAO.clearTokens(username);
}
@Override
@Transactional(readOnly = true)
@WebMethod(exclude = true)
public boolean isUsernameEmailPairValid(ServiceContext context, String username, String email) throws ServiceException {
return userDAO.isUsernameEmailPairValid(username, email);
}
@Override
@WebMethod(exclude = true)
public boolean isEmailInUse(ServiceContext context, String email) {
return userDAO.isEmailInUse(email);
}
@Override
@WebMethod(exclude = true)
public void updateUserSelectedScopeId(ServiceContext context) {
UserDO userDO = userDAO.getById(context.getUserId());
ScopeDO scopeDO = scopeDAO.getById(context.getScopeId());
userDO.setSelectedScope(scopeDO);
userDAO.update(userDO);
}
@Override
@WebMethod(exclude = true)
public boolean hasAccessToOrgByCode(ServiceContext context, Long userId, String orgCode) {
return userDAO.hasAccessToOrg(userId, orgCode);
}
@Override
@WebMethod(exclude = true)
public String getCurrentResetEmailText(ServiceContext context, Long userId) {
return userDAO.getCurrentResetEmailText(userDAO.getByUserId(userId));
}
@Override
@WebMethod(exclude = true)
public List<Org> findOrgsForUsers(ServiceContext context, Long orgId, Long scopeId, Collection<Long> userIds) {
return getMappingService().mapFromDOList(userOrgDAO.findOrgsForUsers(scopeId, orgId, userIds));
}
@Override
@WebMethod(exclude = true)
public List<Role> findRolesForUsers(ServiceContext context, Long scopeId, Collection<Long> userIds) {
return getMappingService().mapFromDOList(userRoleDAO.findRolesForUsers(scopeId, userIds));
}
@Override
public Collection<User> findById(ServiceContext context, Collection<Long> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Collections.emptySet();
}
ScopeDO scope = scopeDAO.getScopeForUsers(context.getScopeId());
StringBuilder sb = new StringBuilder();
sb.append("select user from UserDO user ");
sb.append("where user.scope.scopeId = :scopeId ");
sb.append("and user.userId in (:userIds)");
TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class);
query.setParameter("scopeId", scope.getScopeId());
query.setParameter("userIds", userIds);
return getMappingService().getMapper().mapAsSet(query.getResultList(), User.class);
}
}