package net.techreadiness.persistence.dao; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import javax.inject.Inject; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import net.techreadiness.persistence.domain.OrgDO; import net.techreadiness.persistence.domain.PermissionDO; import net.techreadiness.persistence.domain.UserCasDO; import net.techreadiness.persistence.domain.UserDO; import net.techreadiness.security.PermissionCode; import net.techreadiness.service.exception.ServiceException; import net.techreadiness.util.EmailService; import org.antlr.stringtemplate.StringTemplate; import org.antlr.stringtemplate.language.DefaultTemplateLexer; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Repository; import org.springframework.util.CollectionUtils; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @Repository public class UserDAOImpl extends BaseDAOImpl<UserDO> implements UserDAO { private static final String RESET_PASSWORD = "ea756843f4a446e3063e5606dc407ec4"; private static final String CORE_EMAIL_RESET_TITLE = "core.email.reset.title"; private static final String CORE_EMAIL_RESET_TEXT = "core.email.reset.text"; private static final String CORE_EMAIL_NEWUSER_TITLE = "core.email.newuser.title"; private static final String CORE_EMAIL_NEWUSER_TEXT = "core.email.newuser.text"; private static final String CORE_EMAIL_FORGOT_USERNAME_TITLE = "core.email.forgot.username.title"; private static final String CORE_EMAIL_FORGOT_USERNAME_TEXT = "core.email.forgot.username.text"; @Value("${app.customer.host}") private String host; @Value("${app.customer.protocol}") private String protocol; @Value("${app.customer.port}") private String port; @Value("${app.customer.contextPath}") private String contextPath; @Inject ScopeDAO scopeDAO; @Inject ScopeExtDAO scopeExtDAO; @Inject UserCasDAO userCasDAO; @Inject private EmailService emailService; @Inject private MessageSource bundleSource; @Override public UserDO getByUserId(final Long userId) { StringBuilder sb = new StringBuilder(); sb.append("select distinct u "); sb.append("from UserDO u "); sb.append(" join fetch u.scope "); sb.append("where u.userId=:userId "); UserDO userDO; try { userDO = em.createQuery(sb.toString(), UserDO.class).setParameter("userId", userId).getSingleResult(); } catch (NoResultException nre) { return null; } return userDO; } @Override public void delete(UserDO user) { if (null == user) { return; } // delete only performs a disable, i.e. logical delete user.setDeleteDate(new Date()); update(user); } @Override public void unDelete(UserDO user) { if (null == user) { return; } // undelete only performs an enable, i.e. logical undelete user.setDeleteDate(null); update(user); } @Override public UserDO updateAccounts(UserDO user, String oldUsername) { if (null == user) { return null; } if (oldUsername != null && !oldUsername.equals(user.getUsername())) { UserCasDO userCasDO = userCasDAO.getByUsername(oldUsername); if (userCasDO != null) { userCasDO.setUsername(user.getUsername()); userCasDAO.update(userCasDO); } } return update(user); } @Override public UserDO findByUsername(final String username, final boolean includeDeleted) { StringBuilder sb = new StringBuilder(); sb.append("select distinct u "); sb.append("from UserDO u "); sb.append(" left outer join u.userRoles "); sb.append("where u.username=:username "); if (!includeDeleted) { sb.append("and u.deleteDate is null"); } UserDO userDO; try { TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("username", username); userDO = query.getSingleResult(); } catch (NoResultException nre) { return null; } return userDO; } @Override public boolean hasAccessToOrg(Long userId, Long orgId) { TypedQuery<OrgDO> query = em.createQuery("select o from OrgDO o join o.orgTrees ot, UserOrgDO uo " + "where ot.ancestorOrg.orgId = uo.org.orgId " + "and ot.org.orgId = :orgId " + "and uo.user.userId = :userId ", OrgDO.class); query.setParameter("orgId", orgId); query.setParameter("userId", userId); query.setMaxResults(1); OrgDO org = getSingleResult(query); return org != null; } @Override public boolean hasPermission(PermissionCode[] permissionCodes, Collection<GrantedAuthority> authorities, Long scopeId) { if (authorities == null || authorities.isEmpty()) { return false; } if (permissionCodes == null || permissionCodes.length < 1) { return false; } final List<PermissionDO> availablePermissions = findPermissionsByRoles(authorities, scopeId); return Iterables.all(Arrays.asList(permissionCodes), new Predicate<PermissionCode>() { @Override public boolean apply(PermissionCode permissionCode) { for (PermissionDO availablePermission : availablePermissions) { if (availablePermission.getCode().equalsIgnoreCase(permissionCode.toString())) { return true; } } return false; } }); } @Override public List<PermissionDO> findPermissionsByRoles(Collection<GrantedAuthority> roles, Long scopeId) { if (null == scopeId || null == roles || roles.isEmpty()) { return new ArrayList<>(); } Collection<Long> roleIds = Collections2.transform(roles, new Function<GrantedAuthority, Long>() { @Override public Long apply(GrantedAuthority authority) { return Long.valueOf(authority.getAuthority()); } }); StringBuilder sb = new StringBuilder(); sb.append(" select p "); sb.append(" from PermissionDO p, RolePermissionDO rp, RoleDO r, ScopeTreeDO st "); sb.append(" where r.roleId in (:roleIds)"); sb.append(" and st.ancestorScope.scopeId = r.scope.scopeId"); sb.append(" and st.scope.scopeId = :scopeid"); sb.append(" and r = rp.role "); sb.append(" and rp.permission = p "); TypedQuery<PermissionDO> query = em.createQuery(sb.toString(), PermissionDO.class); query.setParameter("roleIds", roleIds); query.setParameter("scopeid", scopeId); query.setHint("org.hibernate.cacheable", Boolean.TRUE); return query.getResultList(); } @Override public List<UserDO> findAllUsersWithRole(Long roleId) { if (null == roleId) { return new ArrayList<>(); } StringBuilder sb = new StringBuilder(); sb.append(" select ur.user "); sb.append(" from UserRoleDO ur "); sb.append(" where ur.role.roleId = :roleid"); TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("roleid", roleId); query.setHint("org.hibernate.cacheable", Boolean.TRUE); return query.getResultList(); } public List<UserDO> findAllUsersWithEmail(String email) { if (null == email) { return new ArrayList<>(); } StringBuilder sb = new StringBuilder(); sb.append(" select u "); sb.append(" from UserDO u "); sb.append(" where u.email=:email"); TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("email", email); query.setHint("org.hibernate.cacheable", Boolean.TRUE); return query.getResultList(); } @Override public void changePassword(String username, final String formattedPassword) { UserCasDO userCasDO = userCasDAO.getByUsername(username); if (userCasDO != null) { userCasDO.setPassword(formattedPassword); userCasDO.setFailedAttempts(0); userCasDAO.update(userCasDO); } } @Override public void resetPassword(UserDO userDO) { changePassword(userDO.getUsername(), RESET_PASSWORD); String token = setResetToken(userDO); sendPasswordResetEmail(userDO, token); } @Override public void newAccountPassword(UserDO userDO) { changePassword(userDO.getUsername(), RESET_PASSWORD); String token = setResetToken(userDO); sendNewAccountEmail(userDO, token); } @Override public void forgotUsername(String email) { List<UserDO> users = findAllUsersWithEmail(email); sendForgotUsernameEmail(email, users); } private void sendForgotUsernameEmail(String email, List<UserDO> users) { doSendMail(email, users, CORE_EMAIL_FORGOT_USERNAME_TITLE, CORE_EMAIL_FORGOT_USERNAME_TEXT); } private void sendPasswordResetEmail(UserDO userDO, String token) { doSendMail(userDO, token, CORE_EMAIL_RESET_TITLE, CORE_EMAIL_RESET_TEXT); } private void sendNewAccountEmail(UserDO userDO, String token) { doSendMail(userDO, token, CORE_EMAIL_NEWUSER_TITLE, CORE_EMAIL_NEWUSER_TEXT); } private void doSendMail(String email, List<UserDO> users, String title_key, String key) { if (email != null) { String title = bundleSource.getMessage(title_key, null, Locale.ENGLISH); emailService.sendTextEmail(email, title, getForgotSubstitutedEmailText(email, users, buildUrlString(), key)); } } private void doSendMail(UserDO userDO, String token, String title_key, String key) { if (userDO.getEmail() != null) { String title = bundleSource.getMessage(title_key, null, Locale.ENGLISH); emailService.sendTextEmail(userDO.getEmail(), title, getTokenSubstitutedEmailText(userDO, token, buildUrlString(), key)); } } private String buildUrlString() { URL url; try { url = new URL(protocol.trim(), host.trim(), Integer.valueOf(port.trim()), contextPath.trim()); return url.toExternalForm(); } catch (Exception e) { throw new ServiceException(e); } } private String setResetToken(UserDO userDO) { String token = String.valueOf(UUID.randomUUID()); // as tokens are added, they are given a postfix index to help make sure // we only overwrite the oldest token if necessary. // tokenIndex [pos:val] Map<String, String> tokenIndexMap = determineTokenIndexMap(userDO); switch (Integer.valueOf(tokenIndexMap.get("position"))) { case 1: userDO.setResetToken1(token + ":" + tokenIndexMap.get("tokenIndex")); break; case 2: userDO.setResetToken2(token + ":" + tokenIndexMap.get("tokenIndex")); break; case 3: userDO.setResetToken3(token + ":" + tokenIndexMap.get("tokenIndex")); break; case 4: userDO.setResetToken4(token + ":" + tokenIndexMap.get("tokenIndex")); break; case 5: userDO.setResetToken5(token + ":" + tokenIndexMap.get("tokenIndex")); break; default: userDO.setResetToken1(token + ":" + tokenIndexMap.get("tokenIndex")); break; } update(userDO); return token + ":" + tokenIndexMap.get("tokenIndex"); } private static Map<String, String> determineTokenIndexMap(UserDO userDO) { Map<String, String> map = Maps.newHashMap(); Integer[] indexes = new Integer[5]; if (userDO.getResetToken1() != null && !userDO.getResetToken1().isEmpty()) { String[] splits = userDO.getResetToken1().split(":"); indexes[0] = Integer.valueOf(splits[1]); } else { indexes[0] = 0; } if (userDO.getResetToken2() != null && !userDO.getResetToken2().isEmpty()) { String[] splits = userDO.getResetToken2().split(":"); indexes[1] = Integer.valueOf(splits[1]); } else { indexes[1] = 0; } if (userDO.getResetToken3() != null && !userDO.getResetToken3().isEmpty()) { String[] splits = userDO.getResetToken3().split(":"); indexes[2] = Integer.valueOf(splits[1]); } else { indexes[2] = 0; } if (userDO.getResetToken4() != null && !userDO.getResetToken4().isEmpty()) { String[] splits = userDO.getResetToken4().split(":"); indexes[3] = Integer.valueOf(splits[1]); } else { indexes[3] = 0; } if (userDO.getResetToken5() != null && !userDO.getResetToken5().isEmpty()) { String[] splits = userDO.getResetToken5().split(":"); indexes[4] = Integer.valueOf(splits[1]); } else { indexes[4] = 0; } int highest = 0; for (int i = 0; i < 5; i++) { if (indexes[i] > highest) { highest = indexes[i]; } } int lowestPosition = 1; int lowestValue = Integer.MAX_VALUE; for (int i = 0; i < 5; i++) { if (indexes[i] < lowestValue) { lowestValue = indexes[i]; lowestPosition = i + 1; } } map.put("position", String.valueOf(lowestPosition)); map.put("tokenIndex", String.valueOf(highest + 1)); return map; } public String getTokenSubstitutedEmailText(UserDO userDO, String token, String urlString, String key) { // core.email.text=Hello ${username}. Please Reset Password. Use // ${url}/changeresetpassword?username=${username}&token=${token} StringTemplate stDetail = new StringTemplate(bundleSource.getMessage(key, null, Locale.ENGLISH), DefaultTemplateLexer.class); stDetail.setAttribute("username", userDO.getUsername()); stDetail.setAttribute("firstname", userDO.getFirstName()); stDetail.setAttribute("lastname", userDO.getLastName()); stDetail.setAttribute("email", userDO.getEmail()); stDetail.setAttribute("url", urlString); stDetail.setAttribute("token", token); stDetail.setAttribute("customerservicename", bundleSource.getMessage("customerservicename", null, Locale.ENGLISH)); stDetail.setAttribute("customerserviceemail", bundleSource.getMessage("customerserviceemail", null, Locale.ENGLISH)); stDetail.setAttribute("customerservicephone", bundleSource.getMessage("customerservicephone", null, Locale.ENGLISH)); stDetail.setAttribute("customerserviceurl", bundleSource.getMessage("customerserviceurl", null, Locale.ENGLISH)); return stDetail.toString(); } public String getForgotSubstitutedEmailText(String email, List<UserDO> users, String urlString, String key) { StringTemplate stDetail = new StringTemplate(bundleSource.getMessage(key, null, Locale.ENGLISH), DefaultTemplateLexer.class); stDetail.setAttribute("usernames", Joiner.on("\n * ").join(Iterables.transform(users, new Function<UserDO, String>() { @Override public String apply(UserDO user) { return user.getUsername(); } }))); stDetail.setAttribute("customerservicename", bundleSource.getMessage("customerservicename", null, Locale.ENGLISH)); stDetail.setAttribute("customerserviceemail", bundleSource.getMessage("customerserviceemail", null, Locale.ENGLISH)); stDetail.setAttribute("customerservicephone", bundleSource.getMessage("customerservicephone", null, Locale.ENGLISH)); stDetail.setAttribute("customerserviceurl", bundleSource.getMessage("customerserviceurl", null, Locale.ENGLISH)); return stDetail.toString(); } @Override public boolean isTokenValid(String username, String token) { if (username == null || token == null) { return false; } UserDO userDO = findByUsername(username, false); if (userDO == null) { return false; } if (token.trim().equalsIgnoreCase(userDO.getResetToken1())) { return true; } if (token.trim().equalsIgnoreCase(userDO.getResetToken2())) { return true; } if (token.trim().equalsIgnoreCase(userDO.getResetToken3())) { return true; } if (token.trim().equalsIgnoreCase(userDO.getResetToken4())) { return true; } if (token.trim().equalsIgnoreCase(userDO.getResetToken5())) { return true; } return false; } @Override public void clearTokens(String username) { if (username == null) { return; } UserDO userDO = findByUsername(username, false); if (userDO == null) { return; } userDO.setResetToken1(null); userDO.setResetToken2(null); userDO.setResetToken3(null); userDO.setResetToken4(null); userDO.setResetToken5(null); update(userDO); } @Override public boolean isUserInScope(Long scopeId, Long userId) { StringBuilder sb = new StringBuilder(); sb.append("select distinct u "); sb.append("from UserDO u "); sb.append(" join fetch u.scope "); sb.append("where u.userId=:userId "); sb.append("and u.scope.scopeId=:scopeId "); try { TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("userId", userId); query.setParameter("scopeId", scopeId).getSingleResult(); } catch (NoResultException nre) { return false; } return true; } @Override public boolean isUsernameEmailPairValid(String username, String email) { StringBuilder sb = new StringBuilder(); sb.append("select distinct u "); sb.append("from UserDO u "); sb.append("where u.username=:username "); sb.append("and u.email=:email "); try { TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("username", username); query.setParameter("email", email).getSingleResult(); } catch (NoResultException nre) { return false; } return true; } @Override public boolean isEmailInUse(String email) { StringBuilder sb = new StringBuilder(); sb.append(" select distinct u "); sb.append(" from UserDO u "); sb.append("where u.email=:email "); try { TypedQuery<UserDO> query = em.createQuery(sb.toString(), UserDO.class); query.setParameter("email", email); query.setHint("org.hibernate.cacheable", Boolean.TRUE); if (!CollectionUtils.isEmpty(query.getResultList())) { return true; } return false; } catch (NoResultException nre) { return false; } } @Override public boolean hasAccessToOrg(Long userId, String orgCode) { StringBuilder sb = new StringBuilder(); sb.append("select o from OrgDO o join o.orgTrees ot, UserOrgDO uo "); sb.append("where ot.ancestorOrg.orgId = uo.org.orgId "); sb.append("and ot.org.code = :code "); sb.append("and uo.user.userId = :userId "); TypedQuery<OrgDO> query = em.createQuery(sb.toString(), OrgDO.class); query.setParameter("code", orgCode); query.setParameter("userId", userId); query.setMaxResults(1); OrgDO org = getSingleResult(query); return org != null; } @Override public String getCurrentResetEmailText(UserDO userDO) { if (StringUtils.isEmpty(userDO.getResetToken1()) && StringUtils.isEmpty(userDO.getResetToken2()) && StringUtils.isEmpty(userDO.getResetToken3()) && StringUtils.isEmpty(userDO.getResetToken4()) && StringUtils.isEmpty(userDO.getResetToken5())) { return "Password token is not available. Please use the Password Reset task to obtain a new password token."; } String token = ""; Map<String, String> tokenIndexMap = determineTokenIndexMap(userDO); // this tells us the next point for adding a token, so peek back one for the current token switch (Integer.valueOf(tokenIndexMap.get("position"))) { case 1: token = userDO.getResetToken5(); break; case 2: token = userDO.getResetToken1(); break; case 3: token = userDO.getResetToken2(); break; case 4: token = userDO.getResetToken3(); break; case 5: token = userDO.getResetToken4(); break; } return getTokenSubstitutedEmailText(userDO, token, buildUrlString(), CORE_EMAIL_RESET_TEXT); } }