/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.vertical.adminweb; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.enonic.esl.containers.ExtendedMap; import com.enonic.esl.containers.MultiValueMap; import com.enonic.esl.servlet.http.CookieUtil; import com.enonic.cms.core.AdminConsoleTranslationService; import com.enonic.cms.core.DeploymentPathResolver; import com.enonic.cms.core.admin.AdminConsoleAccessDeniedException; import com.enonic.cms.core.log.LogType; import com.enonic.cms.core.log.StoreNewLogEntryCommand; import com.enonic.cms.core.product.ProductVersion; import com.enonic.cms.core.security.InvalidCredentialsException; import com.enonic.cms.core.security.LoginAdminUserCommand; import com.enonic.cms.core.security.PasswordGenerator; import com.enonic.cms.core.security.SecurityLoggingXml; import com.enonic.cms.core.security.user.QualifiedUsername; import com.enonic.cms.core.security.user.User; import com.enonic.cms.core.security.user.UserEntity; import com.enonic.cms.core.security.user.UserSpecification; import com.enonic.cms.core.security.userstore.UserStoreEntity; import com.enonic.cms.core.security.userstore.UserStoreKey; import com.enonic.cms.core.security.userstore.UserStoreXmlCreator; /** * Administration login servlet. */ public final class AdminLogInServlet extends AdminHandlerBaseServlet { private static final Logger LOG = LoggerFactory.getLogger( AdminLogInServlet.class ); private static final int COOKIE_TIMEOUT = 60 * 60 * 24 * 365 * 50; // 50 years // error codes // 500_unexpected_error : an unexpected error occurred during login // 401_missing_user_passwd: missing user id and/or password // 401_user_passwd_wrong : user id and/or password is wrong for this domain // (or enterprise administrator user id and/or password is incorrect) // 401_access_denied : user doesn't have access to the administration console private static final String EC_401_MISSING_USER_PASSWD = "401_missing_user_passwd"; private static final String EC_500_FAILED_SEND_MAIL = "500_failed_send_mail"; private static final String EC_401_USER_PASSWD_WRONG = "401_user_passwd_wrong"; private static final String EC_401_ACCESS_DENIED = "401_access_denied"; private static final String EC_400_USER_NOT_FOUND = "400_user_not_found"; private static final String EC_400_MISSING_UID = "400_missing_uid"; public static final String SMTP_SERVER_IS_NOT_AVAILABLE = "SMTP server is not available (%s). Check your host configuration."; private static final Pattern PATTERN = Pattern.compile( "^.*editContent=([\\d]+).*$" ); /** * @see com.enonic.vertical.adminweb.AdminHandlerBaseServlet#doGet(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { try { HashMap<String, Object> parameters = new HashMap<String, Object>(); ExtendedMap formItems = parseForm( request ); response.setContentType( "text/html;charset=UTF-8" ); if ( isRequestForAdminPath( "/login", request ) ) { if ( formItems.getBoolean( "login", false ) ) { handlerLogin( request, response, formItems ); } else { HttpSession session = request.getSession( true ); org.jdom.Document doc = new org.jdom.Document( new org.jdom.Element( "data" ) ); handlerLoginForm( request, response, session, parameters, doc ); } } else if ( isRequestForAdminPath( "/logout", request ) ) { HttpSession session = request.getSession( false ); handlerLogout( request, response, session ); } else if ( isRequestForAdminPath( "/forgotpassword", request ) ) { HttpSession session = request.getSession( true ); org.jdom.Document doc = new org.jdom.Document( new org.jdom.Element( "data" ) ); handlerForgotPasswordForm( request, response, session, parameters, doc ); } else { super.doGet( request, response ); } } catch ( Exception e ) { localHandleException( request, response, e ); } } /** * @see com.enonic.vertical.adminweb.AdminHandlerBaseServlet#doPost(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { try { ExtendedMap formItems = parseForm( request ); //ren: VS-1970 int editContent = resolveEditContentParam( request.getHeader( "Referer" ) ); if ( editContent > -1 ) { formItems.put( "editContent", editContent ); } //end: VS-1970 if ( isRequestForAdminPath( "/login", request ) ) { handlerLogin( request, response, formItems ); } else if ( isRequestForAdminPath( "/forgotpassword", request ) ) { handlerForgotPassword( request, response, formItems ); } else { super.doPost( request, response ); } } catch ( Exception e ) { localHandleException( request, response, e ); } } /** * Exactely the same thing that should be done, whether it is doGet or doPost that fails. * * @param request The Http request object * @param response The Http response object * @param e The exception to handle */ private void localHandleException( final HttpServletRequest request, final HttpServletResponse response, final Exception e ) { try { HttpSession session = request.getSession( true ); ErrorPageServlet.Error error = new ErrorPageServlet.ThrowableError( e ); session.setAttribute( "com.enonic.vertical.error", error ); redirectClientToAdminPath( "errorpage", (MultiValueMap) null, request, response ); } catch ( VerticalAdminException vae2 ) { String message = "Failed to redirect to error page: %t"; VerticalAdminLogger.errorAdmin( message, vae2 ); } } private int resolveEditContentParam( String referer ) { int editContent = -1; Matcher matcher = PATTERN.matcher( referer ); Boolean matches = matcher.matches(); if ( matches ) { return Integer.parseInt( matcher.group( 1 ) ); } return editContent; } private void logLoginFailed( final QualifiedUsername user, final String remoteIp ) { final StoreNewLogEntryCommand command = new StoreNewLogEntryCommand(); command.setType( LogType.LOGIN_FAILED ); command.setInetAddress( remoteIp ); command.setTitle( user.getUsername() ); command.setXmlData( SecurityLoggingXml.createUserStoreDataDoc( user ) ); command.setUser( this.securityService.getAnonymousUserKey() ); this.logService.storeNew( command ); } private void logLogin( final User user, final String remoteIp ) { final StoreNewLogEntryCommand command = new StoreNewLogEntryCommand(); command.setType( LogType.LOGIN ); command.setInetAddress( remoteIp ); command.setTitle( user.getDisplayName() + " (" + user.getName() + ")" ); command.setXmlData( SecurityLoggingXml.createUserStoreDataDoc( user.getQualifiedName() ) ); command.setUser( user.getKey() ); this.logService.storeNew( command ); } private void logLogout( final User user, final String remoteIp ) { final StoreNewLogEntryCommand command = new StoreNewLogEntryCommand(); command.setType( LogType.LOGOUT ); command.setInetAddress( remoteIp ); command.setTitle( user.getDisplayName() + " (" + user.getName() + ")" ); command.setXmlData( SecurityLoggingXml.createUserStoreDataDoc( user.getQualifiedName() ) ); command.setUser( user.getKey() ); this.logService.storeNew( command ); } private void handlerLoginForm( HttpServletRequest request, HttpServletResponse response, HttpSession session, HashMap<String, Object> parameters, org.jdom.Document doc ) throws VerticalAdminException { final UserStoreXmlCreator xmlCreator = new UserStoreXmlCreator( userStoreService.getUserStoreConnectorConfigs() ); List<UserStoreEntity> userStores = securityService.getUserStores(); org.jdom.Document tempDoc = xmlCreator.createPagedDocument( userStores, 0, 100 ); org.jdom.Element dataElem = doc.getRootElement(); dataElem.addContent( tempDoc.getRootElement().detach() ); // set correct language and get languages xml AdminConsoleTranslationService languageMap = AdminConsoleTranslationService.getInstance(); String languageCode = request.getParameter( "lang" ); Boolean cookieSet = false; if ( languageCode == null ) { Cookie cookie = CookieUtil.getCookie( request, "languageCode" ); if ( cookie == null ) { languageCode = languageMap.getDefaultLanguageCode(); } else { languageCode = cookie.getValue(); cookieSet = true; } } languageMap.toDoc( doc, languageCode ); session.setAttribute( "languageCode", languageCode ); parameters.put( "languagecode", languageCode ); if ( !cookieSet ) { String deploymentPath = DeploymentPathResolver.getAdminDeploymentPath( request ); CookieUtil.setCookie( response, "languageCode", languageCode, COOKIE_TIMEOUT, deploymentPath ); } String userStoreKeyStr = request.getParameter( "userStorekey" ); if ( userStoreKeyStr != null ) { parameters.put( "userStorekey", userStoreKeyStr ); } String username = request.getParameter( "username" ); if ( username != null ) { parameters.put( "username", username ); } String password = request.getParameter( "password" ); if ( password != null ) { parameters.put( "password", password ); } String errorCode = (String) session.getAttribute( "passworderrorcode" ); if ( errorCode != null ) { session.removeAttribute( "passworderrorcode" ); session.removeAttribute( "passworderror" ); } errorCode = (String) session.getAttribute( "loginerrorcode" ); if ( errorCode != null ) { parameters.put( "errorcode", errorCode ); parameters.put( "errormessage", session.getAttribute( "loginerror" ) ); } // version and copyright info parameters.put( "titleAndVersion", ProductVersion.getFullTitleAndVersion() ); parameters.put( "copyright", ProductVersion.getCopyright() ); String selectedUserStore = (String) session.getAttribute( "selectedloginuserstore" ); if ( StringUtils.isNotEmpty( selectedUserStore ) ) { parameters.put( "selectedloginuserstore", selectedUserStore ); } transformXML( request, response, doc, "login_form.xsl", parameters ); } private void handlerLogin( HttpServletRequest request, HttpServletResponse response, ExtendedMap formItems ) throws VerticalAdminException { String uid = formItems.getString( "username", null ); String passwd = formItems.getString( "password", null ); UserStoreKey userStoreKey; String userStoreKeyStr = formItems.getString( "userstorekey", null ); if ( userStoreKeyStr != null ) { userStoreKey = new UserStoreKey( userStoreKeyStr ); } else { userStoreKey = userStoreService.getDefaultUserStore().getKey(); } securityService.logoutAdminUser(); HttpSession session = request.getSession( true ); session.setAttribute( "selectedloginuserstore", userStoreKey.toString() ); // language AdminConsoleTranslationService languageMap = AdminConsoleTranslationService.getInstance(); String languageCode; Cookie cookie = CookieUtil.getCookie( request, "languageCode" ); if ( cookie == null ) { languageCode = languageMap.getDefaultLanguageCode(); } else { languageCode = cookie.getValue(); } session.setAttribute( "languageCode", languageCode ); User user = null; String errorCode = null; QualifiedUsername qualifiedUsername = null; try { if ( uid == null || passwd == null ) { String message = "User and/or password not set."; VerticalAdminLogger.error( message ); session.setAttribute( "loginerrorcode", EC_401_MISSING_USER_PASSWD ); session.setAttribute( "loginerror", message ); errorCode = EC_401_MISSING_USER_PASSWD; } else { // authenticate user if ( UserEntity.isBuiltInUser( uid ) ) { qualifiedUsername = new QualifiedUsername( uid ); } else { qualifiedUsername = new QualifiedUsername( userStoreKey, uid ); } user = securityService.loginAdminUser( new LoginAdminUserCommand( qualifiedUsername, passwd ) ); } } catch ( InvalidCredentialsException vse ) { session = request.getSession(); String message = "Failed to authenticate user (domain key: {0}): {1}"; Object[] msgData = {userStoreKey, uid}; VerticalAdminLogger.warn( message, msgData ); message = MessageFormat.format( message, userStoreKey, uid, vse ); session.setAttribute( "loginerrorcode", EC_401_USER_PASSWD_WRONG ); session.setAttribute( "loginerror", message ); errorCode = EC_401_USER_PASSWD_WRONG; String remoteAdr = request.getRemoteAddr(); logLoginFailed( qualifiedUsername, remoteAdr ); } catch ( AdminConsoleAccessDeniedException e ) { session = request.getSession(); String message = "User is not authorized to use administration console."; VerticalAdminLogger.error( message ); session.setAttribute( "loginerrorcode", EC_401_ACCESS_DENIED ); session.setAttribute( "loginerror", message ); errorCode = EC_401_ACCESS_DENIED; } session = request.getSession(); if ( errorCode != null ) { if ( formItems.containsKey( "editContent" ) ) { ExtendedMap parameters = new ExtendedMap(); parameters.put( "editContent", formItems.getInt( "editContent" ) ); redirectClientToAdminPath( "login", parameters, request, response ); return; } redirectClientToAdminPath( "login", request, response ); return; } // no errors occured during authentication and authorization of user String remoteAdr = request.getRemoteAddr(); user.setSelectedLanguageCode( languageCode ); try { logLogin( user, remoteAdr ); // Reset some cookie data: String deploymentPath = DeploymentPathResolver.getAdminDeploymentPath( request ); CookieUtil.setCookie( response, user.getKey() + "userstorekey", userStoreKey.toString(), -1, deploymentPath ); CookieUtil.setCookie( response, user.getKey() + "selectedunitkey", "-1", -1, deploymentPath ); // If the enterpriseadmin user did'nt select a domain, // show system tab page, else show domain tab page. Cookie tabPageCookie = CookieUtil.getCookie( request, user.getKey() + "mainmenu_selectedTabPage" ); int tabPage = -1; if ( tabPageCookie != null ) { tabPage = Integer.parseInt( tabPageCookie.getValue() ); } CookieUtil.setCookie( response, user.getKey() + "mainmenu_selectedTabPage", String.valueOf( tabPage ), -1, deploymentPath ); session.setAttribute( "selectedunitkey", "-1" ); ExtendedMap parameters = new ExtendedMap(); parameters.put( "page", "0" ); if ( formItems.containsKey( "rightframe" ) ) { parameters.put( "rightframe", formItems.getString( "rightframe" ) ); } if ( formItems.containsKey( "referer" ) ) { parameters.put( "referer", formItems.getString( "referer", "" ) ); } //ren: VS-1970 if ( formItems.containsKey( "editContent" ) ) { parameters.put( "editContent", formItems.getInt( "editContent" ) ); } //end: VS-1970 session.removeAttribute( "loginerrorcode" ); session.removeAttribute( "loginerror" ); redirectClientToAdminPath( "adminpage", parameters, request, response ); } catch ( VerticalAdminException vae ) { String message = "Failed to redirect to admin page: %t"; VerticalAdminLogger.errorAdmin( message, vae ); } } private void handlerLogout( HttpServletRequest request, HttpServletResponse response, HttpSession session ) { User user = securityService.getLoggedInAdminConsoleUser(); if ( session != null && user != null ) { String remoteAddr = request.getRemoteAddr(); logLogout( user, remoteAddr ); try { securityService.logoutAdminUser(); redirectClientToAdminPath( "login", (MultiValueMap) null, request, response ); } catch ( VerticalAdminException vae ) { String page = "login page"; String message = "Failed to redirect to {0}: %t"; VerticalAdminLogger.errorAdmin( message, page, vae ); } } else { try { redirectClientToAdminPath( "login", (MultiValueMap) null, request, response ); } catch ( VerticalAdminException vae ) { String message = "Failed to redirect to {0}: %t"; VerticalAdminLogger.errorAdmin( message, "login page", vae ); } } } private void handlerForgotPasswordForm( HttpServletRequest request, HttpServletResponse response, HttpSession session, HashMap<String, Object> parameters, org.jdom.Document doc ) throws VerticalAdminException { final UserStoreXmlCreator xmlCreator = new UserStoreXmlCreator( userStoreService.getUserStoreConnectorConfigs() ); List<UserStoreEntity> userStores = securityService.getUserStores(); org.jdom.Document tempDoc = xmlCreator.createPagedDocument( userStores, 0, 100 ); org.jdom.Element dataElem = doc.getRootElement(); dataElem.addContent( tempDoc.getRootElement().detach() ); String errorCode = (String) session.getAttribute( "loginerrorcode" ); if ( errorCode != null ) { session.removeAttribute( "loginerrorcode" ); session.removeAttribute( "loginerror" ); } errorCode = (String) session.getAttribute( "passworderrorcode" ); if ( errorCode != null ) { parameters.put( "errorcode", errorCode ); parameters.put( "errormessage", session.getAttribute( "passworderror" ) ); } // version and copyright info parameters.put( "titleAndVersion", ProductVersion.getFullTitleAndVersion() ); parameters.put( "copyright", ProductVersion.getCopyright() ); String selectedUserStore = (String) session.getAttribute( "selectedloginuserstore" ); if ( StringUtils.isNotEmpty( selectedUserStore ) ) { parameters.put( "selectedloginuserstore", selectedUserStore ); } transformXML( request, response, doc, "forgotpwd_form.xsl", parameters ); } private void handlerForgotPassword( HttpServletRequest request, HttpServletResponse response, ExtendedMap formItems ) throws VerticalAdminException { final HttpSession session = request.getSession( true ); final String uid = formItems.getString( "uid", null ); if ( uid == null || uid.length() == 0 ) { session.setAttribute( "passworderrorcode", EC_400_MISSING_UID ); session.setAttribute( "passworderror", "No user id specified!" ); redirectClientToAdminPath( "forgotpassword", request, response ); return; } final String userStoreKeyStr = formItems.getString( "userstorekey", null ); final UserStoreKey userStoreKey = userStoreKeyStr != null ? new UserStoreKey( userStoreKeyStr ) : null; final QualifiedUsername qualifiedUsername; if ( uid.contains( "@" ) ) { UserSpecification userSpecification = new UserSpecification(); userSpecification.setUserStoreKey( userStoreKey ); userSpecification.setEmail( uid ); userSpecification.setDeletedStateNotDeleted(); final UserEntity userEntity = userDao.findSingleBySpecification( userSpecification ); if ( userEntity == null ) { String message = "Failed to authenticate user by email " + uid; session.setAttribute( "passworderrorcode", EC_400_USER_NOT_FOUND ); session.setAttribute( "passworderror", message ); redirectClientToAdminPath( "forgotpassword", request, response ); return; } qualifiedUsername = new QualifiedUsername( userStoreKey, userEntity.getName() ); } else { qualifiedUsername = new QualifiedUsername( userStoreKey, uid ); } final String password = PasswordGenerator.generateNewPassword(); try { securityService.changePassword( qualifiedUsername, password ); } catch ( InvalidCredentialsException ex ) { session.setAttribute( "passworderrorcode", EC_400_USER_NOT_FOUND ); session.setAttribute( "passworderror", "Failed to authenticate user " + uid ); redirectClientToAdminPath( "forgotpassword", request, response ); return; } catch ( Exception ex ) { LOG.error( "cannot change password for " + uid, ex ); session.setAttribute( "passworderrorcode", EC_401_ACCESS_DENIED ); session.setAttribute( "passworderror", ex.getMessage() ); redirectClientToAdminPath( "forgotpassword", request, response ); return; } try { sendMailService.sendChangePasswordMail( qualifiedUsername, password ); LOG.info( "Sent mail with new password to " + qualifiedUsername ); // AS DESIGNED: message "email was sent, check your INBOX" is not displayed. redirectClientToAdminPath( "login", request, response ); } catch ( Exception e ) { final String message = String.format( SMTP_SERVER_IS_NOT_AVAILABLE, e.getCause().getMessage() ); session.setAttribute( "passworderrorcode", EC_500_FAILED_SEND_MAIL ); session.setAttribute( "passworderror", message ); LOG.error( message ); redirectClientToAdminPath( "forgotpassword", request, response ); } } }