/********************************************************************************* * The contents of this file are subject to the Common Public Attribution * License Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.openemm.org/cpal1.html. The License is based on the Mozilla * Public License Version 1.1 but Sections 14 and 15 have been added to cover * use of software over a computer network and provide for limited attribution * for the Original Developer. In addition, Exhibit A has been modified to be * consistent with Exhibit B. * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License. * * The Original Code is OpenEMM. * The Original Developer is the Initial Developer. * The Initial Developer of the Original Code is AGNITAS AG. All portions of * the code written by AGNITAS AG are Copyright (c) 2007 AGNITAS AG. All Rights * Reserved. * * Contributor(s): AGNITAS AG. ********************************************************************************/ package org.agnitas.web; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.agnitas.beans.Admin; import org.agnitas.beans.AdminGroup; import org.agnitas.beans.Company; import org.agnitas.beans.FailedLoginData; import org.agnitas.beans.VersionObject; import org.agnitas.dao.AdminDao; import org.agnitas.dao.AdminGroupDao; import org.agnitas.dao.CompanyDao; import org.agnitas.dao.DocMappingDao; import org.agnitas.dao.EmmLayoutBaseDao; import org.agnitas.dao.LoginTrackDao; import org.agnitas.service.VersionControlService; import org.agnitas.util.AgnUtils; import org.agnitas.web.forms.LogonForm; import org.apache.log4j.Logger; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.action.ActionMessages; /** * Implementation of <strong>Action</strong> that validates a user logon. * * @author Martin Helff */ public class LogonAction extends StrutsActionBase { private static final transient Logger logger = Logger.getLogger( LogonAction.class); public static final int ACTION_LOGON = 1; public static final int ACTION_LOGOFF = 2; public static final int ACTION_PASSWORD_CHANGE_REQ = 3; public static final int ACTION_PASSWORD_CHANGE = 4; public static final int ACTION_FORWARD = 5; public static final int ACTION_LAYOUT = 6; // --------------------------------------------------------- Public Methods /** * Process the specified HTTP request, and create the corresponding HTTP * response (or forward to another web component that will create it). * Return an <code>ActionForward</code> instance describing where and how * control should be forwarded, or <code>null</code> if the response has * already been completed. * * <br> * ACTION_LOGON: tries to logon a user. If user with passed name and password doesn't exists or user is blocked forwards to logon page again with error messages.<br> * Also checks if user password is expired ("password.expire.days"). If true forwards to "suggest_password_change". * Otherwise loads user settings into the session (language, layout settings) and forwards to "success". * <br><br> * ACTION_LOGOFF: logs off a user, loads default layout settings and forwards to "logged_out". * <br><br> * ACTION_PASSWORD_CHANGE_REQ: only forwards to jsp for changing user password. * <br><br> * ACTION_PASSWORD_CHANGE: tries to save a new password. If new password is invalid forwards to jsp for changing user password again and shows corresponding error messages. <br> * Otherwise to "change_password_success". * <br><br> * ACTION_FORWARD: only forwards to jsp with message of the day for current user. * <br><br> * ACTION_LAYOUT: loads layout settings and forwards to logon page. * <br><br> * Any other ACTION_* would loads default layout settings and forwards to logon page. * <br> * @param form data for the action filled by the jsp * @param req request from jsp * @param res response * @param mapping The ActionMapping used to select this instance * @exception IOException if an input/output error occurs * @exception ServletException if a servlet exception occurs * @return destination specified in struts-config.xml to forward to next jsp */ @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // Validate the request parameters specified by the user ActionMessages errors = new ActionMessages(); LogonForm aForm=(LogonForm)form; ActionForward destination=null; checkForUpdate( req ); try { if( logger.isInfoEnabled()) { logger.info("execute: action " + aForm.getAction()); } switch(aForm.getAction()) { case ACTION_LOGON: if(logon(aForm, req, errors)) { setLayout(aForm, req); destination=mapping.findForward(checkPassword(req)); } break; case ACTION_LOGOFF: if( logger.isInfoEnabled()) { logger.info("execute: logoff"); } logoff(aForm, req); setLayout(aForm, req); destination=mapping.findForward("logged_out"); aForm.setAction(LogonAction.ACTION_LOGON); break; case ACTION_PASSWORD_CHANGE_REQ: aForm.setAction(ACTION_PASSWORD_CHANGE); destination=mapping.findForward("change_password"); break; case ACTION_PASSWORD_CHANGE: if(changePassword(aForm, req, errors)) { destination=mapping.findForward("change_password_success"); } else { aForm.setAction(ACTION_PASSWORD_CHANGE); saveErrors(req, errors); return mapping.findForward("change_password"); } break; case ACTION_FORWARD: destination = mapping.findForward("motd"); break; case ACTION_LAYOUT: setLayout(aForm, req); aForm.setAction(LogonAction.ACTION_LOGON); destination=mapping.findForward("view_logon"); break; default: if( logger.isInfoEnabled()) { logger.info("execute: default"); } setLayout(aForm, req); aForm.setAction(LogonAction.ACTION_LOGON); destination=mapping.findForward("view_logon"); } } catch (Exception e) { logger.error( "Error", e); errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.exception")); } // Report any errors we have discovered back to the original form if (!errors.isEmpty()) { saveErrors(req, errors); return new ActionForward(mapping.getInput()); } return destination; } /** * Checks is password is secured. * * @param pwd password to check * @param errors ActionMessages for errors * @return true if password is secured, otherwise false */ boolean checkSecurity(String pwd, ActionMessages errors) { // TODO: check for length, digits, chars & special chars: if(!pwd.matches(".*\\p{Alpha}.*")) { errors.add("password", new ActionMessage("error.password_no_letters")); return false; } if(!pwd.matches(".*\\p{Digit}.*")) { errors.add("password", new ActionMessage("error.password_no_digits")); return false; } // if(!pwd.matches(".*[,.-;:_#+*!=].*")) { if(!pwd.matches(".*\\p{Punct}.*")) { errors.add("password", new ActionMessage("error.password_no_special_chars")); return false; } return true; } /** * Validates new password and if no errors found updates password for current user. * * @param aForm LogonForm * @param req request * @param errors ActionMessages for errors * @return true on success, otherwise false */ protected boolean changePassword(LogonForm aForm, HttpServletRequest req, ActionMessages errors) { Admin admin = AgnUtils.getAdmin(req); if (admin == null) { return false; } if(!aForm.getPassword_new1().equals(aForm.getPassword())) { errors.add("password", new ActionMessage("error.password.mismatch")); return false; } if(checkSecurity(aForm.getPassword_new1(),errors)) { AdminDao dao=(AdminDao) getBean("AdminDao"); admin.setPassword(aForm.getPassword_new1()); admin.setLastPasswordChange(new Date()); AdminGroupDao groupDao=(AdminGroupDao) getBean("AdminGroupDao"); AdminGroup group=(AdminGroup) groupDao.getAdminGroup(admin.getGroup().getGroupID()); admin.setGroup(group); dao.save(admin); } else { logger.warn("password problem"); return false; } return true; } /** * Checks password expiration for current logged in user. * * @param req request * @return forward name */ protected String checkPassword(HttpServletRequest req) { Admin admin = AgnUtils.getAdmin(req); Date lastChange = admin.getLastPasswordChange(); Calendar expire = new GregorianCalendar(); int days = -1; try { days=Integer.parseInt(AgnUtils.getDefaultValue("password.expire.days")); } catch(Exception e) { days=-1; } /* No expire set, so don't request password change. */ if(days <= 0) { return "success"; } expire.add(Calendar.DAY_OF_MONTH, -days); if(lastChange.before(expire.getTime())) { return "suggest_password_change"; } return "success"; } private void checkForUpdate(HttpServletRequest request) { if(AgnUtils.isMySQLDB()) { try{ StringBuffer referrer = request.getRequestURL(); VersionControlService vcService = ( VersionControlService ) getBean( "versionControlService" ); VersionObject latestVersion = vcService.getLatestVersion( AgnUtils.getCurrentVersion(), referrer != null ? referrer.toString() : "" ); request.setAttribute( "latestVersion", latestVersion ); } catch ( Exception ex ) { logger.error( "Error while retrieving latest version", ex ); } } } /** * Loads special layout for given design details */ private void setLayout(LogonForm aForm, HttpServletRequest req) { HttpSession session=req.getSession(); EmmLayoutBaseDao layoutDao=(EmmLayoutBaseDao) getBean("EmmLayoutBaseDao"); int companyID = 0; int layoutID = 0; if(aForm.getDesign() != null && !aForm.getDesign().isEmpty()) { layoutID = AgnUtils.decryptLayoutID(aForm.getDesign()); companyID = AgnUtils.decryptCompanyID(aForm.getDesign()); session.setAttribute("emmLayoutBase", layoutDao.getEmmLayoutBase(companyID, layoutID)); } else { Object layout = session.getAttribute("emmLayoutBase"); if (layout == null) { session.setAttribute("emmLayoutBase", layoutDao.getEmmLayoutBase(companyID, layoutID)); } } } /** * Tries to logon a user. */ protected boolean logon(LogonForm aForm, HttpServletRequest req, ActionMessages errors) { HttpSession session=req.getSession(); AdminDao adminDao=(AdminDao) getBean("AdminDao"); EmmLayoutBaseDao layoutDao=(EmmLayoutBaseDao) getBean("EmmLayoutBaseDao"); Admin aAdmin=adminDao.getAdminByLogin(aForm.getUsername(), aForm.getPassword()); LoginTrackDao loginTrackDao = (LoginTrackDao) getBean("LoginTrackDao"); DocMappingDao docMappingDao = (DocMappingDao) getBean("DocMappingDao"); if(aAdmin!=null) { if (isIPLogonBlocked(req, aAdmin)) { logger.warn("logon: login FAILED (IP " + req.getRemoteAddr() + " blocked) User: " + aForm.getUsername() + " Password-Length: " + aForm.getPassword().length()); errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage("error.login")); loginTrackDao.trackLoginDuringBlock(req.getRemoteAddr(), aForm.getUsername()); return false; } else { req.getSession().invalidate(); session = req.getSession(); session.setAttribute("emm.admin", aAdmin); session.setAttribute("emmLayoutBase", layoutDao.getEmmLayoutBase(aForm.getCompanyID(req), aAdmin.getLayoutBaseID())); session.setAttribute("emm.locale", aAdmin.getLocale()); session.setAttribute(org.apache.struts.Globals.LOCALE_KEY, aAdmin.getLocale()); String helplanguage = getHelpLanguage(req); session.setAttribute("helplanguage", helplanguage) ; AgnUtils.userlogger().info(aAdmin.getUsername()+": do login"); loginTrackDao.trackSuccessfulLogin(req.getRemoteAddr(), aForm.getUsername()); session.setAttribute("docMapping",docMappingDao.getDocMapping()); return true; } } else { logger.warn("logon: login FAILED User: " + aForm.getUsername() + " Password-Length: " + aForm.getPassword().length()); errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage("error.login")); loginTrackDao.trackFailedLogin(req.getRemoteAddr(), aForm.getUsername()); return false; } } /** * Logs off a user */ protected void logoff(LogonForm aForm, HttpServletRequest req) { if( logger.isInfoEnabled()) { logger.info("logoff: logout "+aForm.getUsername()+"!"); } AgnUtils.userlogger().info((AgnUtils.getAdmin(req) == null ? aForm.getUsername() : AgnUtils.getAdmin(req).getUsername()) + ": do logout"); req.getSession().removeAttribute("emm.admin"); req.getSession().invalidate(); } /** * Determines how long the current IP address is blocked. * @param request Servlet request with IP address * @return true, if IP is temporarily blocked, otherwise false */ protected boolean isIPLogonBlocked(HttpServletRequest request, Admin admin) { LoginTrackDao loginTrackDao = (LoginTrackDao) getBean("LoginTrackDao"); CompanyDao companyDao = (CompanyDao) getBean("CompanyDao"); if (loginTrackDao != null) { FailedLoginData data = loginTrackDao.getFailedLoginData(request.getRemoteAddr()); Company company = companyDao.getCompany(admin.getCompanyID()); if( data != null && company != null) { if (data.getNumFailedLogins() > company.getMaxLoginFails()) { return data.getLastFailedLoginTimeDifference() < company.getLoginBlockTime(); } else { return false; } } else { return false; // No data found, IP not blocked } } else { // No bean instance, no check! return false; } } }