UserChangePasswordService.java
package sk.iway.iwcm.users;
import java.lang.reflect.Method;
import java.security.SecureRandom;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.ui.ModelMap;
import sk.iway.Password;
import sk.iway.iwcm.Adminlog;
import sk.iway.iwcm.AdminlogBean;
import sk.iway.iwcm.Constants;
import sk.iway.iwcm.DB;
import sk.iway.iwcm.Logger;
import sk.iway.iwcm.SendMail;
import sk.iway.iwcm.SpamProtection;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.components.users.userdetail.UserDetailsEntity;
import sk.iway.iwcm.components.users.userdetail.UserDetailsRepository;
import sk.iway.iwcm.components.users.userdetail.UserDetailsService;
import sk.iway.iwcm.database.ComplexQuery;
import sk.iway.iwcm.database.Mapper;
import sk.iway.iwcm.database.SimpleQuery;
import sk.iway.iwcm.doc.DocDB;
import sk.iway.iwcm.helpers.MailHelper;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.logon.UserForm;
import sk.iway.iwcm.system.ConfDB;
public class UserChangePasswordService {
public static final String LOGINS_SEPARATOR = ",";
private static final String CANCEL_ACTION = "cancelChangePasswordAction";
private UserChangePasswordService() {
//utility class
}
/**
* Handle request for password change. If all is ok, send email with link to change password page.
*
* @param request
* @param login
* @return
*/
public static boolean sendPassword(HttpServletRequest request, String login) {
if (SpamProtection.canPost("passwordSend", null, request) == false) {
Prop prop = Prop.getInstance(request);
Logger.error(UsersDB.class, prop.getText("logon.error.blocked"));
request.setAttribute("errors", prop.getText("logon.error.blocked"));
request.setAttribute("error.logon.user.blocked", "true");
return false;
}
//IF is set, use custom method
String method = Constants.getString("sendPasswordMethod");
if (Tools.isNotEmpty(method)) {
int i = method.lastIndexOf('.');
String clazz = method.substring(0, i);
method = method.substring(i+1);
//String
try {
Class<?> c = Class.forName(clazz);
Object o = c.getDeclaredConstructor().newInstance();
Method m;
Class<?>[] parameterTypes = new Class<?>[] {HttpServletRequest.class, String.class, String.class};
Object[] arguments = new Object[] {request, login, login};
m = c.getMethod(method, parameterTypes);
return((boolean)m.invoke(o, arguments));
}
catch (Exception ex) {
sk.iway.iwcm.Logger.error(ex);
return false;
}
}
List<UserDetailsEntity> suitableUsers = getAllSuitableLogins(login, isAdminSection(request));
//Is there suitable user?
if(suitableUsers.size() == 0) {
Logger.println(UserChangePasswordService.class, "Suitable user NOT found :" + login);
if (Constants.getBoolean("formLoginProtect")) {
//nastavime na uspech aj ked user neexistuje aby sa to nedalo rozlisit
request.setAttribute("passResultEmail", "@");
} else {
Logger.println(UserChangePasswordService.class,"K zadanemu uzivatelovi neexistuje email");
request.setAttribute("passResultEmailFail","true");
}
return false;
} else {
//Its ok send email
try{
sendPasswordEmail(request, suitableUsers);
return true;
} catch (Exception e) {sk.iway.iwcm.Logger.error(e);}
}
return false;
}
private static boolean isAdminSection(HttpServletRequest request) {
if(request == null) return false;
Object isAdminSectionObj = request.getAttribute(UsersDB.IS_ADMIN_SECTION_KEY);
if (isAdminSectionObj == null) {
return false;
}
if (isAdminSectionObj instanceof Boolean) {
return (Boolean) isAdminSectionObj;
}
return false;
}
/**
* Get all suitable logins for change password action.
* Users MUST be admin, not disabled, by in good domain if its required.
*
* @param login
* @return
*/
private static List<UserDetailsEntity> getAllSuitableLogins(String loginOrEmail, boolean onlyAdmins) {
List<UserDetailsEntity> suitableUsers = new ArrayList<>();
UserDetailsRepository udr = Tools.getSpringBean("userDetailsRepository", UserDetailsRepository.class);
//Find only ADMIN users -> non admin cant login in
if (loginOrEmail != null && loginOrEmail.contains("@")) {
//Entered EMAIL
if(onlyAdmins == true)
suitableUsers.addAll( udr.findAllByEmailAndAdminTrueOrderByIdDesc(DB.fixAiCiValue(loginOrEmail)) );
else
suitableUsers.addAll( udr.findAllByEmailOrderByIdDesc(DB.fixAiCiValue(loginOrEmail)) );
} else {
//Entered LOGIN
if(onlyAdmins == true)
suitableUsers.addAll( udr.findByLoginAndAdminTrueOrderByIdDesc(DB.fixAiCiValue(loginOrEmail)) );
else
suitableUsers.addAll( udr.findByLoginOrderByIdDesc(DB.fixAiCiValue(loginOrEmail)) );
}
//Filter user by DOMAIN ... IF it's needed
if (UserDetailsService.isUsersSplitByDomain()) {
int domainId = CloudToolsForCore.getDomainId();
suitableUsers = suitableUsers.stream()
.filter(user -> user.getDomainId() == domainId)
.collect(Collectors.toList());
}
//Filter disabled users out
suitableUsers = suitableUsers.stream()
.filter(user -> UserDetailsService.isUserDisabled(user) == false)
.collect(Collectors.toList());
//Filter out users with INVALID email
suitableUsers = suitableUsers.stream()
.filter(user -> Tools.isEmail(user.getEmail()) == true)
.collect(Collectors.toList());
return suitableUsers;
}
/**
* Send email with link to change password page.
*
* @param request
* @param users
* @throws Exception
*/
private static void sendPasswordEmail(HttpServletRequest request, List<UserDetailsEntity> users) throws Exception {
UserDetailsEntity newestUser = users.get(0);
Prop prop = Prop.getInstance(Constants.getServletContext(), request);
//if we are able to decrypt his/her original password
String subject = prop.getText("logon.mail.lost_password") + " " + Tools.getBaseHref(request);
if (!Constants.getBoolean("passwordUseHash"))
{
String text;
text = prop.getText("logon.mail.message") + "\n";
text += prop.getText("logon.mail.login_name") + ": " + newestUser.getLogin() + "\n";
text += prop.getText("logon.mail.password") + ": " + newestUser.getPassword() + "\n";
// from fromEmail toEmail subject text
SendMail.send(newestUser.getFullName(), newestUser.getEmail(), newestUser.getEmail(), subject, text);
} else {
String allLogins = users.stream()
.map(user -> user.getLogin())
.collect(Collectors.joining(","));
int randomNumber = new SecureRandom().nextInt();
String loginHash = new Password().encrypt( allLogins );
String auth = new Password().encrypt(Integer.toString(randomNumber));
Adminlog.add(Adminlog.TYPE_USER_CHANGE_PASSWORD, newestUser.getId().intValue(), "VyĹžiadanie zmeny hesla", randomNumber, UsersDB.APPROVE_APPROVE);
//String text = prop.getText("logon.password.change_at")+"\n";
// pageUrl is set depending if its request from admin section or not
String pageUrl = isAdminSection(request) == true ? Constants.getString("changePasswordPageUrlAdmin") : Constants.getString("changePasswordPageUrl");
if (request !=null && request.getAttribute("sendPasswordUrl") != null) pageUrl = (String)request.getAttribute("sendPasswordUrl");
pageUrl = Tools.getBaseHref(request) + pageUrl + "?login="+loginHash+"&auth="+auth;
String cancelActionLink = pageUrl + "&act=" + CANCEL_ACTION;
String propKey = Tools.getRequestAttribute(request, "sendPasswordTextKey", "logon.password.changeEmailText");
String subjectKey = Tools.getRequestAttribute(request, "sendPasswordSubjectKey", null);
if (subjectKey != null)
{
subject = prop.getText(subjectKey, Tools.getBaseHref(request), DocDB.getDomain(request));
}
String fromName = Tools.getRequestAttribute(request, "sendPasswordFromName", newestUser.getFullName());
String fromEmail = Tools.getRequestAttribute(request, "sendPasswordFromEmail", newestUser.getEmail());
String text = prop.getText(propKey, pageUrl, String.valueOf(Constants.getInt("passwordResetValidityInMinutes")), cancelActionLink);
new MailHelper().
setFromEmail(fromEmail).
setFromName(fromName).
addRecipient(newestUser.getEmail()).
setSubject(subject).
setMessage(text).
send();
}
if (request!=null) request.setAttribute("passResultEmail", newestUser.getEmail());
}
public static AdminlogBean getChangePasswordAdminlogBean(String login, String auth) {
UserDetails user = UsersDB.getUser(login);
return new ComplexQuery().
setSql("SELECT * FROM "+ConfDB.ADMINLOG_TABLE_NAME+" WHERE log_type=? AND user_id = ? AND sub_id1 = ?").
setParams(Adminlog.TYPE_USER_CHANGE_PASSWORD, user.getUserId(), Integer.valueOf(auth)).
singleResult(new Mapper<AdminlogBean>(){;
public AdminlogBean map(ResultSet rs) throws SQLException{
return new AdminlogBean(rs);
}
});
}
/**
* Verify if received logins are valid. If YES, return true, otherwise false.
*
* @param receivedLogins
* @param selectedLogin
* @param auth
* @param request
* @return
*/
public static boolean verifyLoginValue(String receivedLogins, String selectedLogin, String auth, HttpServletRequest request) {
//Check if custom login is implemented , if YES dont do a check
String method = Constants.getString("sendPasswordMethod");
if(Tools.isEmpty(method) == false) return true;
if(Tools.isEmpty(receivedLogins) || Tools.isEmpty(selectedLogin) || Tools.isEmpty(auth)) return false;
//There can be multiple logins separated by LOGINS_SEPARATOR
String[] logins = receivedLogins.split(LOGINS_SEPARATOR);
if(logins.length == 0) return false;
else if(logins.length == 1) {
//We have only one option -> that one option MUST be in adminlog
AdminlogBean log = getChangePasswordAdminlogBean(logins[0], auth);
//If AdminlogBean was returned -> it means login is valid, and we can change password
if(log != null) return true;
} else {
//We have multiple options
//Use FIRST login to get AdminlogBean
AdminlogBean log = getChangePasswordAdminlogBean(logins[0], auth);
if(log != null) {
//AdminlogBean was found -> FIRST login is valid, FOUND all logins by email
UserDetails user = UsersDB.getUser(logins[0]);
List<UserDetailsEntity> users = getAllSuitableLogins(user.getEmail(), isAdminSection(request));
//Check if selected login is in list of valid logins
return users.stream().anyMatch(u -> u.getLogin().equals(selectedLogin));
} else {
//AdminlogBean was not found -> probably wrong received logins
return false;
}
}
return true;
}
/**
* Delete adminlog record for change password action.
* @param loginsStr
* @param auth
*/
public static void deleteChangePasswordAdminlogBean(String loginsStr, String auth) {
if(Tools.isEmpty(loginsStr) || Tools.isEmpty(auth)) return;
//There can be multiple logins separated by LOGINS_SEPARATOR
String[] logins = loginsStr.split(LOGINS_SEPARATOR);
if(logins.length == 0) return;
UserDetails user = UsersDB.getUser(logins[0]);
deleteChangePasswordAdminlogBean(user, auth);
}
/**
* Delete adminlog record for change password action.
*
* @param user
* @param auth
*/
public static void deleteChangePasswordAdminlogBean(UserDetails user, String auth) {
//zmaz zaznam z audit tabulky (aby druhy krat linka nefungovala)
new SimpleQuery().execute("DELETE FROM " + ConfDB.ADMINLOG_TABLE_NAME + " WHERE log_type=? AND user_id=? AND sub_id1=?", Adminlog.TYPE_USER_CHANGE_PASSWORD, user.getUserId(), Tools.getIntValue(auth, -1));
}
/**
* Call {@link #getPreparedUserForm(HttpServletRequest, ModelMap)} with model as NULL -> CALLED from JSP
* @param request
* @return
*/
public static UserForm getPreparedUserForm(HttpServletRequest request) {
return getPreparedUserForm(request, null);
}
/**
* Prepare use form for change password action. IF all is ok, return UserForm object, otherwise NULL.
* If problem occurs, set attribute to model to show error message.
* @param request
* @param model
* @return
*/
public static UserForm getPreparedUserForm(HttpServletRequest request, ModelMap model) {
String auth = request.getParameter("auth");
String login = request.getParameter("login");
String action = request.getParameter("act");
UserForm userForm = null;
try {
login = new Password().decrypt(login);
auth = new Password().decrypt(auth);
//Login handle
login = org.apache.struts.util.ResponseUtils.filter(login);
//Login CAN BE combination of more logins separated by LOGINS_SEPARATOR
String[] logins = login.split(LOGINS_SEPARATOR);
//Its important use the newest login (position 0), it match created AdminlogBean
AdminlogBean log = getChangePasswordAdminlogBean(logins[0], auth);
//Not found = not valid
if(log == null) {
// Show err msg
if(model != null) model.addAttribute("changePasswordActionFailed", true);
return userForm;
}
// FIRST check if this is cancel action
if(CANCEL_ACTION.equals(action)) {
//Delete adminlog record
deleteChangePasswordAdminlogBean(logins[0], auth);
//Set param to show message
if(model != null) model.addAttribute(CANCEL_ACTION, true);
//Return empty userForm
return userForm;
}
long timeAskedFor = log.getCreateDate().getTime();
long timeNow = System.currentTimeMillis();
long validity = Constants.getInt("passwordResetValidityInMinutes")*60L*1000L;
if (timeNow - timeAskedFor > validity) {
// Show err msg
if(model != null) model.addAttribute("changePasswordActionFailed", true);
//No more valid
return userForm;
}
//Valid
userForm = new UserForm();
userForm.setLogin(login);
userForm.setAuth(auth);
userForm.setSelectedLogin( logins[0] );
return userForm;
} catch (Exception e) {
sk.iway.iwcm.Logger.error(e);
// Show err msg
if(model != null) model.addAttribute("changePasswordActionFailed", true);
return userForm;
}
}
}