/*
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.mobile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.access.LoginForm;
import nl.strohalm.cyclos.controls.mobile.exceptions.InvalidUserForMobileException;
import nl.strohalm.cyclos.controls.mobile.exceptions.MobileException;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.Channel.Credentials;
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.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupFilter;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.members.Member;
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.InvalidCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidUserForChannelException;
import nl.strohalm.cyclos.services.access.exceptions.LoginException;
import nl.strohalm.cyclos.services.accounts.AccountDTO;
import nl.strohalm.cyclos.services.accounts.AccountService;
import nl.strohalm.cyclos.services.accounts.AccountTypeService;
import nl.strohalm.cyclos.services.accounts.MemberAccountTypeQuery;
import nl.strohalm.cyclos.services.groups.GroupFilterService;
import nl.strohalm.cyclos.services.groups.GroupService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.LoginHelper;
import nl.strohalm.cyclos.utils.MessageHelper;
import nl.strohalm.cyclos.utils.RequestHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
/**
* Action used for login in mobile access
* @author luis
*/
public class MobileLoginAction extends Action {
private static final Log LOG = LogFactory.getLog(MobileLoginAction.class);
private AccessService accessService;
private AccountService accountService;
private AccountTypeService accountTypeService;
private ChannelService channelService;
private GroupService groupService;
private GroupFilterService groupFilterService;
protected ActionHelper actionHelper;
protected LoginHelper loginHelper;
protected MessageHelper messageHelper;
/**
* @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
* javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward execute(final ActionMapping actionMapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final HttpSession session = request.getSession();
// Test a previously logged user
final User user = LoginHelper.getLoggedUser(request);
if (user != null) {
if (user instanceof MemberUser) {
final String channel = MobileHelper.mobileChannel(request);
if (!accessService.isChannelEnabledForMember(channel, ((MemberUser) user).getMember())) {
session.invalidate();
return MobileHelper.sendException(actionMapping, request, new InvalidUserForMobileException());
} else {
return MobileHelper.getHomeForward(actionMapping, request);
}
} else {
session.invalidate();
return MobileHelper.sendException(actionMapping, request, new InvalidUserForMobileException());
}
}
// Check for a query string
final String queryString = StringUtils.trimToNull(request.getQueryString());
if (queryString != null) {
// Try to find a group filter
try {
final GroupFilter groupFilter = groupFilterService.findByLoginPageName(queryString);
request.setAttribute("styleGroupFilter", groupFilter);
} catch (final EntityNotFoundException e) {
// Try to find a group
try {
final Group group = groupService.findByLoginPageName(queryString);
request.setAttribute("styleGroup", group);
} catch (final EntityNotFoundException e1) {
// Ignore
}
}
}
if (RequestHelper.isGet(request)) { // only in this case we should store the queryString
session.setAttribute("loginQueryString", queryString);
}
ActionForward forward = null;
try {
// Check if is a page preparation or execution
if (RequestHelper.isGet(request)) {
forward = prepareForm(actionMapping, actionForm, request, response);
} else if (RequestHelper.isPost(request)) {
forward = doLogin(actionMapping, actionForm, request, response);
}
} catch (final MobileException e) {
return MobileHelper.sendException(actionMapping, request, e);
}
return MobileBaseAction.processForward(forward, request);
}
@Inject
public void setAccessService(final AccessService accessService) {
this.accessService = accessService;
}
@Inject
public void setAccountService(final AccountService accountService) {
this.accountService = accountService;
}
@Inject
public void setAccountTypeService(final AccountTypeService accountTypeService) {
this.accountTypeService = accountTypeService;
}
@Inject
public void setActionHelper(final ActionHelper actionHelper) {
this.actionHelper = actionHelper;
}
@Inject
public void setChannelService(final ChannelService channelService) {
this.channelService = channelService;
}
@Inject
public void setGroupFilterService(final GroupFilterService groupFilterService) {
this.groupFilterService = groupFilterService;
}
@Inject
public void setGroupService(final GroupService groupService) {
this.groupService = groupService;
}
@Inject
public final void setLoginHelper(final LoginHelper loginHelper) {
this.loginHelper = loginHelper;
}
@Inject
public void setMessageHelper(final MessageHelper messageHelper) {
this.messageHelper = messageHelper;
}
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;
}
@SuppressWarnings("unchecked")
private ActionForward doLogin(final ActionMapping actionMapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) {
HttpSession session = request.getSession();
try {
final String channelName = MobileHelper.mobileChannel(request);
final Channel channel = channelService.loadByInternalName(channelName);
final LoginForm form = (LoginForm) actionForm;
final String principal = form.getPrincipal();
// Resolve the credentials
String credentials = form.getPassword();
if (channel.getCredentials() == Credentials.TRANSACTION_PASSWORD) {
// Ensure transaction password is uppercased
credentials = credentials.toUpperCase();
}
// Do the login
User user;
try {
user = loginHelper.login(MemberUser.class, form.getPrincipalType(), null, principal, credentials, channelName, request, response);
// Get the session again, as a new one might be generated after logging in
session = request.getSession();
} catch (final AccessDeniedException e) {
session.invalidate();
throw new InvalidUserForMobileException();
} catch (final AlreadyConnectedException e) {
return MobileHelper.sendException(actionMapping, request, new MobileException("login.error.alreadyConnected"));
} catch (final InvalidUserForChannelException e) {
session.invalidate();
return MobileHelper.sendException(actionMapping, request, new InvalidUserForMobileException());
}
// Prepare account related data
final Member member = ((MemberUser) user).getMember();
final MemberGroup memberGroup = member.getMemberGroup();
final MemberAccountTypeQuery atQuery = new MemberAccountTypeQuery();
atQuery.fetch(AccountType.Relationships.CURRENCY);
atQuery.setRelatedToGroup(memberGroup);
final List<MemberAccountType> accountTypes = (List<MemberAccountType>) accountTypeService.search(atQuery);
// Validate that the user has an account
if (accountTypes.isEmpty()) {
session.invalidate();
throw new MobileException("mobile.error.inactiveUser");
}
boolean multipleAccounts = false;
if (accountTypes.size() > 1) {
multipleAccounts = true;
final Map<Long, MemberAccountType> accountTypesById = new HashMap<Long, MemberAccountType>();
for (final MemberAccountType accountType : accountTypes) {
accountTypesById.put(accountType.getId(), accountType);
}
session.setAttribute("accountTypes", accountTypes);
session.setAttribute("accountTypesById", accountTypesById);
}
MemberAccountType accountType = accountTypeService.getDefault(memberGroup, AccountType.Relationships.CURRENCY);
if (accountType == null) {
// When no account is the default, use the first one
accountType = accountTypes.get(0);
}
final AccountDTO accountDto = new AccountDTO();
accountDto.setOwner(member);
accountDto.setType(accountType);
final MemberAccount account = (MemberAccount) accountService.getAccount(accountDto);
session.setAttribute("mobileAccount", account);
session.setAttribute("mobileAccountType", accountType);
session.setAttribute("multipleAccounts", multipleAccounts);
return actionMapping.findForward("success");
} catch (final InactiveMemberException e) {
throw new MobileException("login.error.inactive");
} catch (final BlockedCredentialsException e) {
final String key = e.getCredentialsType() == Credentials.TRANSACTION_PASSWORD ? "transactionPassword.error.blockedByTrials" : "login.error.blocked";
throw new MobileException(key);
} catch (final InvalidCredentialsException e) {
final String key = e.getCredentialsType() == Credentials.TRANSACTION_PASSWORD ? "transactionPassword.error.invalid" : "login.error";
throw new MobileException(key);
} catch (final LoginException e) {
throw new MobileException("login.error");
} catch (final PermissionDeniedException e) {
throw new MobileException("error.accessDenied");
} catch (final MobileException e) {
throw e;
} catch (final Exception e) {
actionHelper.generateLog(request, getServlet().getServletContext(), e);
LOG.error("Application error on mobile login action", e);
throw new MobileException();
}
}
private ActionForward prepareForm(final ActionMapping actionMapping, final ActionForm actionForm, final HttpServletRequest request, final HttpServletResponse response) {
final Channel channel = channelService.loadByInternalName(MobileHelper.mobileChannel(request));
// Store the credentials
Credentials credentials = channel.getCredentials();
if (credentials == Credentials.DEFAULT) {
// For login, default credentials == login password
credentials = Credentials.LOGIN_PASSWORD;
}
request.setAttribute("credentials", credentials);
final LoginForm form = (LoginForm) actionForm;
// Build the access links
final List<Map<String, String>> accessLinks = new ArrayList<Map<String, String>>();
final PrincipalType selectedPrincipalType;
if (StringUtils.isEmpty(form.getPrincipalType())) {
selectedPrincipalType = channel.getDefaultPrincipalType();
} else {
selectedPrincipalType = channelService.resolvePrincipalType(channel.getInternalName(), form.getPrincipalType());
}
// Get the principal types
final Set<PrincipalType> allPrincipalTypes = channel.getPrincipalTypes();
for (final PrincipalType principalType : allPrincipalTypes) {
if (principalType.equals(selectedPrincipalType)) {
// Don't show the selected principal type as link
continue;
}
final String label = messageHelper.message("mobile.login.accessUsing", resolvePrincipalLabel(principalType));
accessLinks.add(createLink(request, label, "principalType", principalType.toString()));
}
request.setAttribute("selectedPrincipalType", selectedPrincipalType);
request.setAttribute("selectedPrincipalLabel", resolvePrincipalLabel(selectedPrincipalType));
request.setAttribute("accessLinks", accessLinks);
return actionMapping.getInputForward();
}
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());
}
}
}