/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.controls.access; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import nl.strohalm.cyclos.annotations.Inject; import nl.strohalm.cyclos.controls.BasePublicFormAction; import nl.strohalm.cyclos.entities.access.Channel; import nl.strohalm.cyclos.entities.access.Channel.Principal; import nl.strohalm.cyclos.entities.access.MemberUser; import nl.strohalm.cyclos.entities.access.PrincipalType; import nl.strohalm.cyclos.entities.access.User; import nl.strohalm.cyclos.entities.settings.AccessSettings; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.exceptions.AccessDeniedException; import nl.strohalm.cyclos.exceptions.PermissionDeniedException; import nl.strohalm.cyclos.services.access.AccessService; import nl.strohalm.cyclos.services.access.ChannelService; import nl.strohalm.cyclos.services.access.exceptions.AlreadyConnectedException; import nl.strohalm.cyclos.services.access.exceptions.BlockedCredentialsException; import nl.strohalm.cyclos.services.access.exceptions.InactiveMemberException; import nl.strohalm.cyclos.services.access.exceptions.LoginException; import nl.strohalm.cyclos.services.access.exceptions.SystemOfflineException; import nl.strohalm.cyclos.utils.ActionHelper; import nl.strohalm.cyclos.utils.LoginHelper; import nl.strohalm.cyclos.utils.StringHelper; import nl.strohalm.cyclos.utils.validation.RequiredError; import nl.strohalm.cyclos.utils.validation.ValidationException; import org.apache.commons.lang.StringUtils; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; /** * Action used to login the user * @author luis */ public class LoginAction extends BasePublicFormAction { protected AccessService accessService; protected ChannelService channelService; protected LoginHelper loginHelper; public ChannelService getChannelService() { return channelService; } @Inject public void setAccessService(final AccessService accessService) { this.accessService = accessService; } @Inject public void setChannelService(final ChannelService channelService) { this.channelService = channelService; } @Inject public final void setLoginHelper(final LoginHelper loginHelper) { this.loginHelper = loginHelper; } /** * Returns a forward for a page preparation when there's already a logged user */ protected ActionForward alreadyLoggedForward(final ActionMapping mapping, final HttpServletRequest request, final HttpServletResponse response, final LoginForm form, final User user) { final HttpSession session = request.getSession(); return new ActionForward(session.getAttribute("pathPrefix") + "/home", true); } /** * Logins the user and returns a forward to the next action */ protected ActionForward doLogin(final ActionMapping mapping, final HttpServletRequest request, final HttpServletResponse response, final LoginForm form) { final String member = StringUtils.trimToNull(form.getMember()); final String principal = StringUtils.trimToNull(form.getPrincipal()); final String password = form.getPassword(); final String errorReturnTo = resolveErrorReturnTo(mapping, request, response, form); final HttpSession session = request.getSession(); if (errorReturnTo == null) { session.removeAttribute("errorReturnTo"); session.setAttribute("forceBack", true); } else { session.setAttribute("errorReturnTo", errorReturnTo); } try { final Class<? extends User> requiredUserType = requiredUserType(mapping, request, response, form); final User user = loginHelper.login(requiredUserType, form.getPrincipalType(), member, principal, password, Channel.WEB, request, response); return loginForward(mapping, request, response, form, user); } catch (final BlockedCredentialsException e) { return ActionHelper.sendError(mapping, request, response, "login.error.blocked"); } catch (final InactiveMemberException e) { return ActionHelper.sendError(mapping, request, response, "login.error.inactive"); } catch (final AlreadyConnectedException e) { return ActionHelper.sendError(mapping, request, response, "login.error.alreadyConnected"); } catch (final AccessDeniedException e) { return ActionHelper.sendError(mapping, request, response, "error.accessDenied"); } catch (final PermissionDeniedException e) { return ActionHelper.sendError(mapping, request, response, "error.accessDenied"); } catch (final SystemOfflineException e) { return ActionHelper.sendError(mapping, request, response, "error.systemOffline"); } catch (final LoginException e) { return ActionHelper.sendError(mapping, request, response, "login.error"); } } @Override protected ActionForward handleDisplay(final ActionMapping mapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) { final LoginForm form = (LoginForm) actionForm; final HttpSession session = request.getSession(); final LocalSettings localSettings = settingsService.getLocalSettings(); final AccessSettings accessSettings = settingsService.getAccessSettings(); session.removeAttribute("errorReturnTo"); if (session.getAttribute("loggedOut") != null) { request.setAttribute("loggedOut", true); session.removeAttribute("loggedOut"); } final boolean allowOperatorLogin = accessSettings.isAllowOperatorLogin(); final boolean isOperator = Boolean.parseBoolean(request.getParameter("operator")); // Build the access links final List<Map<String, String>> accessLinks = new ArrayList<Map<String, String>>(); PrincipalType selectedPrincipalType = channelService.resolvePrincipalType(Channel.WEB, form.getPrincipalType()); if (isOperator) { // Accessing as operator: there is only the option to login as member again accessLinks.add(createLink(request, messageHelper.message("login.action.loginAsMember"), null, null)); selectedPrincipalType = Channel.DEFAULT_PRINCIPAL_TYPE; } else { // Get the principal types final Channel web = channelService.loadByInternalName(Channel.WEB); final Set<PrincipalType> allPrincipalTypes = web.getPrincipalTypes(); for (final PrincipalType principalType : allPrincipalTypes) { if (principalType.getPrincipal() == Principal.EMAIL && !localSettings.isEmailUnique()) { // A very specific case: e-mail was default, then the settings for unique e-mail was unchecked if (selectedPrincipalType.equals(principalType)) { selectedPrincipalType = Channel.DEFAULT_PRINCIPAL_TYPE; } // If the e-mail was not set as unique, skip the e-mail principal continue; } else if (principalType.equals(selectedPrincipalType)) { // Don't show the selected principal type as link continue; } final String label = messageHelper.message("login.accessUsing", resolvePrincipalLabel(principalType)); accessLinks.add(createLink(request, label, "principalType", principalType.toString())); } if (allowOperatorLogin) { accessLinks.add(createLink(request, messageHelper.message("login.action.loginAsOperator"), "operator", "true")); } } request.setAttribute("selectedPrincipalType", selectedPrincipalType); request.setAttribute("selectedPrincipalLabel", resolvePrincipalLabel(selectedPrincipalType)); request.setAttribute("accessLinks", accessLinks); request.setAttribute("singleAccessLink", accessLinks.size() == 1 ? accessLinks.get(0) : null); final User user = LoginHelper.getLoggedUser(request); if (user != null) { return alreadyLoggedForward(mapping, request, response, form, user); } storeCookie(request, response, true); return mapping.getInputForward(); } @Override protected ActionForward handleSubmit(final ActionMapping mapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) { return doLogin(mapping, request, response, (LoginForm) actionForm); } /** * Returns whether the member is required - only in case of operator-only logins */ protected boolean isMemberRequired(final HttpServletRequest request) { return "true".equals(request.getParameter("operatorLogin")); } /** * Returns a forward to the next action in case of a successful login */ protected ActionForward loginForward(final ActionMapping mapping, final HttpServletRequest request, final HttpServletResponse response, final LoginForm form, final User user) { final String returnTo = (String) request.getSession().getAttribute("returnTo"); ActionForward forward = null; if (user instanceof MemberUser && elementService.shallAcceptAgreement(((MemberUser) user).getMember())) { // Should accept a registration agreement first request.getSession().setAttribute("shallAcceptRegistrationAgreement", true); forward = mapping.findForward("acceptRegistrationAgreement"); } else if (accessService.hasPasswordExpired()) { // Should change an expired password request.getSession().setAttribute("expiredPassword", true); forward = actionHelper.getForwardFor(user.getElement().getNature(), "changeExpiredPassword", true); } else if (StringUtils.isNotEmpty(returnTo)) { // When redirecting to the previous page, remove from session to avoid a next unwanted redirection final HttpSession session = request.getSession(); session.removeAttribute("returnTo"); forward = new ActionForward(!returnTo.startsWith("/do") ? "/do" + returnTo : returnTo, true); } else { forward = actionHelper.getForwardFor(user.getElement().getNature(), "home", true); } return forward; } /** * May be overridden in order to return the required user type */ protected Class<? extends User> requiredUserType(final ActionMapping mapping, final HttpServletRequest request, final HttpServletResponse response, final LoginForm form) { return User.class; } protected String resolveErrorReturnTo(final ActionMapping mapping, final HttpServletRequest request, final HttpServletResponse response, final LoginForm form) { final String member = StringUtils.trimToNull(form.getMember()); if (StringUtils.isEmpty(member)) { return null; } else { return "/do/login?operator=true"; } } @Override protected void validateForm(final ActionMapping mapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) throws ValidationException { final LoginForm form = (LoginForm) actionForm; String member = null; final boolean memberRequired = isMemberRequired(request); if (memberRequired) { member = StringUtils.trimToNull(form.getMember()); } final String principal = StringUtils.trimToNull(form.getPrincipal()); final String password = form.getPassword(); final ValidationException vex = new ValidationException(); if (memberRequired && member == null) { vex.setPropertyKey("member", "login.memberUsername"); vex.addPropertyError("member", new RequiredError()); } if (principal == null) { vex.setPropertyKey("username", "login.username"); vex.addPropertyError("username", new RequiredError()); } if (StringUtils.isEmpty(password)) { vex.setPropertyKey("_password", "login.password"); vex.addPropertyError("_password", new RequiredError()); } vex.throwIfHasErrors(); } private Map<String, String> createLink(final HttpServletRequest request, final String label, final String paramName, final String paramValue) { final Map<String, String> link = new HashMap<String, String>(); link.put("label", label); link.put("paramName", paramName); link.put("paramValue", paramValue); return link; } private String resolvePrincipalLabel(final PrincipalType principalType) { final Principal principal = principalType.getPrincipal(); if (principal == Principal.CUSTOM_FIELD) { return principalType.getCustomField().getName(); } else { return messageHelper.message(principal.getKey()); } } private void storeCookie(final HttpServletRequest request, final HttpServletResponse response, final boolean force) { final String queryString = StringHelper.removeMarkupTags(request.getQueryString()); if (force || StringUtils.isNotEmpty(queryString)) { // Store the query string as a cookie in order to be restored on logout final Cookie queryStringCookie = new Cookie("loginQueryString", StringHelper.encodeUrl(queryString)); queryStringCookie.setPath(request.getContextPath()); response.addCookie(queryStringCookie); } if (force) { // Remove the after logout cookie (received on external login) final Cookie afterLogoutCookie = new Cookie("afterLogout", null); afterLogoutCookie.setPath(request.getContextPath()); response.addCookie(afterLogoutCookie); } } }