nl.strohalm.cyclos.services.access.AccessServiceSecurity.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.access.AccessServiceSecurity.java

Source

/*
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.services.access;

import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;

import nl.strohalm.cyclos.access.AdminAdminPermission;
import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.access.AdminSystemPermission;
import nl.strohalm.cyclos.access.BasicPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.MemberUser;
import nl.strohalm.cyclos.entities.access.Session;
import nl.strohalm.cyclos.entities.access.SessionQuery;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.accounts.cards.Card;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.Group.Nature;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.Operator;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.BaseServiceSecurity;
import nl.strohalm.cyclos.services.access.exceptions.BlockedCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.CredentialsAlreadyUsedException;
import nl.strohalm.cyclos.services.access.exceptions.InactiveMemberException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidCardException;
import nl.strohalm.cyclos.services.access.exceptions.InvalidCredentialsException;
import nl.strohalm.cyclos.services.access.exceptions.NotConnectedException;
import nl.strohalm.cyclos.services.access.exceptions.SessionAlreadyInUseException;
import nl.strohalm.cyclos.services.access.exceptions.UserNotFoundException;
import nl.strohalm.cyclos.services.elements.ResetTransactionPasswordDTO;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.access.PermissionHelper;
import nl.strohalm.cyclos.utils.validation.ValidationException;

import org.apache.commons.collections.CollectionUtils;

/**
 * Security layer for {@link AccessService}
 * 
 * @author luis
 */
public class AccessServiceSecurity extends BaseServiceSecurity implements AccessService {

    private AccessServiceLocal accessService;
    private GroupServiceLocal groupService;

    @Override
    public boolean canChangeChannelsAccess(final Member member) {
        // Nothing to check
        return accessService.canChangeChannelsAccess(member);
    }

    @Override
    public Member changeChannelsAccess(Member member, final Collection<Channel> channels,
            final boolean verifySmsChannel) {
        member = fetchService.fetch(member, Element.Relationships.GROUP);
        PermissionHelper.checkSelection(member.getMemberGroup().getChannels(), channels);
        if (!canChangeChannelsAccess(member)) {
            throw new PermissionDeniedException();
        }
        return accessService.changeChannelsAccess(member, channels, verifySmsChannel);
    }

    @Override
    public User changePassword(final ChangeLoginPasswordDTO params)
            throws InvalidCredentialsException, BlockedCredentialsException, CredentialsAlreadyUsedException {
        checkChangePassword(params);
        return accessService.changePassword(params);
    }

    @Override
    public MemberUser changePin(final ChangePinDTO params)
            throws InvalidCredentialsException, BlockedCredentialsException, CredentialsAlreadyUsedException {
        checkChangePin(params);
        return accessService.changePin(params);
    }

    @Override
    public MemberUser checkCredentials(final Channel channel, final MemberUser user, final String credentials,
            final String remoteAddress, final Member relatedMember)
            throws InvalidCredentialsException, BlockedCredentialsException, InvalidCardException {
        // We cannot enforce permissions here, as this method is used under different contexts, like web services, webshop receive payments, etc...
        return accessService.checkCredentials(channel, user, credentials, remoteAddress, relatedMember);
    }

    @Override
    public Session checkSession(final String sessionId) throws NotConnectedException {
        // This is invoked even before LoggedUser is initialized. So, there's nothing to enforce...
        return accessService.checkSession(sessionId);
    }

    @Override
    public User checkTransactionPassword(final String transactionPassword)
            throws InvalidCredentialsException, BlockedCredentialsException {
        // Nothing to check, as this method only affects the logged user
        return accessService.checkTransactionPassword(transactionPassword);
    }

    @Override
    public User disconnect(Session session) throws NotConnectedException {
        try {
            session = fetchService.fetch(session,
                    RelationshipHelper.nested(Session.Relationships.USER, User.Relationships.ELEMENT));
        } catch (EntityNotFoundException e) {
            throw new NotConnectedException();
        }
        Element element = session.getUser().getElement();
        checkDisconnect(element);
        accessService.disconnect(session);
        return session.getUser();
    }

    @Override
    public User disconnect(User user) throws NotConnectedException {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        checkDisconnect(user.getElement());
        return accessService.disconnect(user);
    }

    @Override
    public String generateTransactionPassword() throws UnexpectedEntityException {
        // Nothing to check, as this method only affects the logged user
        return accessService.generateTransactionPassword();
    }

    @Override
    public Collection<Channel> getChannelsEnabledForMember(final Member member) {
        permissionService.checkManages(member);

        return accessService.getChannelsEnabledForMember(member);
    }

    @Override
    public User getLoggedUser(final String sessionId) throws NotConnectedException {
        User user = accessService.getLoggedUser(sessionId);
        permissionService.checkManages(user.getElement());
        return user;
    }

    @Override
    public boolean hasPasswordExpired() {
        // Nothing to check, as this method only affects the logged user
        return accessService.hasPasswordExpired();
    }

    @Override
    public boolean isCardSecurityCodeBlocked(Card card) {
        card = fetchService.fetch(card, Card.Relationships.OWNER);
        permissionService.permission(card.getOwner()).admin(AdminMemberPermission.CARDS_VIEW)
                .broker(BrokerPermission.CARDS_VIEW).member(MemberPermission.CARDS_VIEW).check();
        return accessService.isCardSecurityCodeBlocked(card);
    }

    @Override
    public boolean isChannelEnabledForMember(final Channel channel, final Member member) {
        // We cannot check management here, as this is invoked, for example, on web services restricted to a member -
        // we just need to know whether a channel is enabled
        permissionService.checkRelatesTo(member);
        return accessService.isChannelEnabledForMember(channel, member);
    }

    @Override
    public boolean isChannelEnabledForMember(final String channelInternalName, final Member member) {
        // We cannot check management here, as this is invoked, for example, on web services restricted to a member -
        // we just need to know whether a channel is enabled
        permissionService.checkRelatesTo(member);
        return accessService.isChannelEnabledForMember(channelInternalName, member);
    }

    @Override
    public boolean isLoggedIn(User user) throws NotConnectedException {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        // We cannot just throw PermissionDeniedException here, as this method is used over related (not managed) users.
        // In that case, just assume the user is not logged in
        if (!permissionService.manages(user.getElement())) {
            return false;
        }
        return accessService.isLoggedIn(user);
    }

    @Override
    public boolean isLoginBlocked(User user) {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        // We cannot just throw PermissionDeniedException here, as this method is used over related (not managed) users.
        // In that case, just assume the user login is not blocked
        if (!permissionService.manages(user.getElement())) {
            return false;
        }
        return accessService.isLoginBlocked(user);
    }

    @Override
    public boolean isPinBlocked(MemberUser user) {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        // We cannot just throw PermissionDeniedException here, as this method is used over related (not managed) users.
        // In that case, just assume the user pin is not blocked
        if (!permissionService.manages(user.getElement())) {
            return false;
        }
        return accessService.isPinBlocked(user);
    }

    @Override
    public User login(User user, final String password, final String channel, final boolean isPosWeb,
            final String remoteAddress, final String sessionId) throws UserNotFoundException,
            InvalidCredentialsException, BlockedCredentialsException, SessionAlreadyInUseException {
        user = fetchService.fetch(user,
                RelationshipHelper.nested(User.Relationships.ELEMENT, Element.Relationships.GROUP));
        permissionService.permission(user.getElement().getGroup()).basic(BasicPermission.BASIC_LOGIN).check();
        return accessService.login(user, password, channel, isPosWeb, remoteAddress, sessionId);
    }

    @Override
    public User logout(final String sessionId) {
        // This method is invoked by a session listener, so, probably there's nothing on LoggedUser => nothing to check
        return accessService.logout(sessionId);
    }

    @Override
    public boolean notifyPermissionDeniedException() {
        // Nothing to check, as this method only affects the logged user
        return accessService.notifyPermissionDeniedException();
    }

    @Override
    public User reenableLogin(User user) {
        user = fetchService.fetch(user,
                RelationshipHelper.nested(User.Relationships.ELEMENT, Element.Relationships.GROUP));
        // Check that the user doesn't belong to a removed group.
        if (user.getElement().getGroup().isRemoved()) {
            throw new PermissionDeniedException();
        }
        permissionService.permission(user.getElement())
                .admin(AdminAdminPermission.ACCESS_ENABLE_LOGIN, AdminMemberPermission.ACCESS_ENABLE_LOGIN)
                .member(MemberPermission.OPERATORS_MANAGE).check();
        return accessService.reenableLogin(user);
    }

    @Override
    public MemberUser resetPassword(MemberUser user) {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        permissionService.permission(user.getElement()).admin(AdminMemberPermission.ACCESS_RESET_PASSWORD)
                .broker(BrokerPermission.MEMBER_ACCESS_RESET_PASSWORD).check();
        return accessService.resetPassword(user);
    }

    @Override
    public User resetTransactionPassword(final ResetTransactionPasswordDTO dto) {
        User user = fetchService.fetch(dto.getUser(), User.Relationships.ELEMENT);
        dto.setUser(user);
        permissionService.permission(user.getElement())
                .admin(AdminAdminPermission.ACCESS_TRANSACTION_PASSWORD,
                        AdminMemberPermission.ACCESS_TRANSACTION_PASSWORD)
                .broker(BrokerPermission.MEMBER_ACCESS_TRANSACTION_PASSWORD)
                .member(MemberPermission.OPERATORS_MANAGE).check();
        return accessService.resetTransactionPassword(dto);
    }

    @Override
    public List<Session> searchSessions(final SessionQuery query) {
        if (LoggedUser.isAdministrator()) {
            Collection<Nature> natures = query.getNatures();
            if (CollectionUtils.isEmpty(natures)) {
                // As usual, empty means all. We want to ensure one-by-one, so we add them here
                natures = EnumSet.allOf(Nature.class);
            }
            if (!permissionService.hasPermission(AdminSystemPermission.STATUS_VIEW_CONNECTED_ADMINS)) {
                natures.remove(Nature.ADMIN);
            }
            if (!permissionService.hasPermission(AdminSystemPermission.STATUS_VIEW_CONNECTED_MEMBERS)) {
                natures.remove(Nature.MEMBER);
            }
            if (!permissionService.hasPermission(AdminSystemPermission.STATUS_VIEW_CONNECTED_BROKERS)) {
                natures.remove(Nature.BROKER);
            }
            if (!permissionService.hasPermission(AdminSystemPermission.STATUS_VIEW_CONNECTED_OPERATORS)) {
                natures.remove(Nature.OPERATOR);
            }
            if (natures.isEmpty()) {
                // Nothing left to see
                throw new PermissionDeniedException();
            }
            // Apply the allowed groups
            Collection<Group> allowedGroups = new HashSet<Group>();
            allowedGroups.addAll(permissionService.getVisibleMemberGroups());
            if (natures.contains(Nature.ADMIN)) {
                // Add all admin groups, as they are not present on the permissionService.getVisibleMemberGroups()
                GroupQuery admins = new GroupQuery();
                admins.setNatures(Group.Nature.ADMIN);
                allowedGroups.addAll(groupService.search(admins));
            }
            if (natures.contains(Nature.OPERATOR)) {
                // Add all operator groups, as they are not present on the permissionService.getVisibleMemberGroups()
                GroupQuery operators = new GroupQuery();
                operators.setIgnoreManagedBy(true);
                operators.setNatures(Group.Nature.OPERATOR);
                allowedGroups.addAll(groupService.search(operators));
            }
            query.setGroups(PermissionHelper.checkSelection(allowedGroups, query.getGroups()));
        } else {
            // Members can only view connected operators
            permissionService.permission(query.getMember()).member(MemberPermission.OPERATORS_MANAGE).check();
        }
        return accessService.searchSessions(query);
    }

    public void setAccessServiceLocal(final AccessServiceLocal accessService) {
        this.accessService = accessService;
    }

    public void setGroupServiceLocal(final GroupServiceLocal groupService) {
        this.groupService = groupService;
    }

    @Override
    public MemberUser unblockPin(MemberUser user) {
        user = fetchService.fetch(user, User.Relationships.ELEMENT);
        permissionService.permission(user.getElement()).admin(AdminMemberPermission.ACCESS_UNBLOCK_PIN)
                .broker(BrokerPermission.MEMBER_ACCESS_CHANGE_PIN).member(MemberPermission.ACCESS_UNBLOCK_PIN)
                .check();
        return accessService.unblockPin(user);
    }

    @Override
    public void validateChangePassword(final ChangeLoginPasswordDTO params) throws ValidationException {
        checkChangePassword(params);
        accessService.validateChangePassword(params);
    }

    @Override
    public void validateChangePin(final ChangePinDTO params) throws ValidationException {
        checkChangePin(params);
        accessService.validateChangePin(params);
    }

    @Override
    public User verifyLogin(final String member, final String username, final String remoteAddress)
            throws UserNotFoundException, InactiveMemberException, PermissionDeniedException {
        // This method is invoked before logging in, so we cannot check anything here
        return accessService.verifyLogin(member, username, remoteAddress);
    }

    private void checkChangePassword(final ChangeLoginPasswordDTO params) {
        User user = fetchService.fetch(params.getUser(),
                RelationshipHelper.nested(User.Relationships.ELEMENT, Element.Relationships.GROUP));

        params.setUser(user);
        if (LoggedUser.user().equals(user)) {
            // No permission to check - an user can always change his own password
            return;
        }
        // Can't change password of a removed element.
        if (user.getElement().getGroup().isRemoved()) {
            throw new PermissionDeniedException();
        }

        permissionService.permission(user.getElement())
                .admin(AdminAdminPermission.ACCESS_CHANGE_PASSWORD, AdminMemberPermission.ACCESS_CHANGE_PASSWORD)
                .broker(BrokerPermission.MEMBER_ACCESS_CHANGE_PASSWORD).member(MemberPermission.OPERATORS_MANAGE)
                .check();
    }

    private void checkChangePin(final ChangePinDTO params) {
        MemberUser user = fetchService.fetch(params.getUser(), User.Relationships.ELEMENT);
        params.setUser(user);
        permissionService.permission(user.getElement()).admin(AdminMemberPermission.ACCESS_CHANGE_PIN)
                .broker(BrokerPermission.MEMBER_ACCESS_CHANGE_PIN).member().check();
    }

    private void checkDisconnect(final Element element) {
        if (LoggedUser.isAdministrator() && element instanceof Operator) {
            // Special case: admins can disconnect logged operators (even without managing them), as long as the member can be disconnected
            Operator operator = (Operator) element;
            permissionService.permission(operator.getMember())
                    .admin(AdminMemberPermission.ACCESS_DISCONNECT_OPERATOR).check();
        } else {
            permissionService.permission(element)
                    .admin(AdminAdminPermission.ACCESS_DISCONNECT, AdminMemberPermission.ACCESS_DISCONNECT)
                    .member(MemberPermission.OPERATORS_MANAGE).check();
        }
    }
}