com.ushahidi.swiftriver.core.api.service.AccountService.java Source code

Java tutorial

Introduction

Here is the source code for com.ushahidi.swiftriver.core.api.service.AccountService.java

Source

/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/agpl.html>
 * 
 * Copyright (C) Ushahidi Inc. All Rights Reserved.
 */
package com.ushahidi.swiftriver.core.api.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.dozer.Mapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ushahidi.swiftriver.core.api.auth.AuthenticationScheme;
import com.ushahidi.swiftriver.core.api.auth.crowdmapid.CrowdmapIDClient;
import com.ushahidi.swiftriver.core.api.dao.AccountDao;
import com.ushahidi.swiftriver.core.api.dao.ActivityDao;
import com.ushahidi.swiftriver.core.api.dao.ClientDao;
import com.ushahidi.swiftriver.core.api.dao.RoleDao;
import com.ushahidi.swiftriver.core.api.dao.UserDao;
import com.ushahidi.swiftriver.core.api.dao.UserTokenDao;
import com.ushahidi.swiftriver.core.api.dto.ActivateAccountDTO;
import com.ushahidi.swiftriver.core.api.dto.CreateAccountDTO;
import com.ushahidi.swiftriver.core.api.dto.CreateClientDTO;
import com.ushahidi.swiftriver.core.api.dto.FollowerDTO;
import com.ushahidi.swiftriver.core.api.dto.GetAccountDTO;
import com.ushahidi.swiftriver.core.api.dto.GetActivityDTO;
import com.ushahidi.swiftriver.core.api.dto.GetClientDTO;
import com.ushahidi.swiftriver.core.api.dto.ModifyAccountDTO;
import com.ushahidi.swiftriver.core.api.dto.ModifyClientDTO;
import com.ushahidi.swiftriver.core.api.exception.BadRequestException;
import com.ushahidi.swiftriver.core.api.exception.ErrorField;
import com.ushahidi.swiftriver.core.api.exception.ForbiddenException;
import com.ushahidi.swiftriver.core.api.exception.NotFoundException;
import com.ushahidi.swiftriver.core.mail.EmailHelper;
import com.ushahidi.swiftriver.core.mail.EmailType;
import com.ushahidi.swiftriver.core.model.Account;
import com.ushahidi.swiftriver.core.model.AccountActivity;
import com.ushahidi.swiftriver.core.model.AccountFollower;
import com.ushahidi.swiftriver.core.model.Activity;
import com.ushahidi.swiftriver.core.model.ActivityType;
import com.ushahidi.swiftriver.core.model.Bucket;
import com.ushahidi.swiftriver.core.model.BucketActivity;
import com.ushahidi.swiftriver.core.model.BucketCollaborator;
import com.ushahidi.swiftriver.core.model.BucketCollaboratorActivity;
import com.ushahidi.swiftriver.core.model.Client;
import com.ushahidi.swiftriver.core.model.Form;
import com.ushahidi.swiftriver.core.model.FormActivity;
import com.ushahidi.swiftriver.core.model.River;
import com.ushahidi.swiftriver.core.model.RiverActivity;
import com.ushahidi.swiftriver.core.model.RiverCollaborator;
import com.ushahidi.swiftriver.core.model.RiverCollaboratorActivity;
import com.ushahidi.swiftriver.core.model.Role;
import com.ushahidi.swiftriver.core.model.User;
import com.ushahidi.swiftriver.core.model.UserToken;
import com.ushahidi.swiftriver.core.util.ErrorUtil;
import com.ushahidi.swiftriver.core.util.TextUtil;

@Transactional(readOnly = true)
@Service
public class AccountService {

    final Logger logger = LoggerFactory.getLogger(AccountService.class);

    @Autowired
    private RiverService riverService;

    @Autowired
    private BucketService bucketService;

    @Autowired
    private UserDao userDao;

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private ActivityDao activityDao;

    @Autowired
    private UserTokenDao userTokenDao;

    @Autowired
    private ClientDao clientDao;

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private Mapper mapper;

    private String encryptionKey;

    private AuthenticationScheme authenticationScheme;

    private CrowdmapIDClient crowdmapIDClient;

    private EmailHelper emailHelper;

    private int riverQuota;

    public void setRiverService(RiverService riverService) {
        this.riverService = riverService;
    }

    public void setBucketService(BucketService bucketService) {
        this.bucketService = bucketService;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setActivityDao(ActivityDao activityDao) {
        this.activityDao = activityDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setUserTokenDao(UserTokenDao userTokenDao) {
        this.userTokenDao = userTokenDao;
    }

    public void setClientDao(ClientDao clientDao) {
        this.clientDao = clientDao;
    }

    public void setRoleDao(RoleDao roleDao) {
        this.roleDao = roleDao;
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }

    public String getEncryptionKey() {
        return encryptionKey;
    }

    public void setEncryptionKey(String key) {
        this.encryptionKey = key;
    }

    public AuthenticationScheme getAuthenticationScheme() {
        return authenticationScheme;
    }

    public void setAuthenticationScheme(AuthenticationScheme authenticationScheme) {
        this.authenticationScheme = authenticationScheme;
    }

    public void setCrowdmapIDClient(CrowdmapIDClient crowdmapIDClient) {
        this.crowdmapIDClient = crowdmapIDClient;
    }

    public void setEmailHelper(EmailHelper emailHelper) {
        this.emailHelper = emailHelper;
    }

    public void setRiverQuota(int riverQuota) {
        this.riverQuota = riverQuota;
    }

    /**
     * Get an account using its id
     * 
     * @param id
     * @return
     * @throws NotFoundException
     */
    public GetAccountDTO getAccountById(Long id, String authUser) throws NotFoundException {
        Account account = getAccount(id);

        return mapGetAccountDTO(account, accountDao.findByUsernameOrEmail(authUser));
    }

    /**
     * Get an account by username
     * 
     * @param username
     * @return
     * @throws NotFoundException
     */
    public GetAccountDTO getAccountByUsername(String username) throws NotFoundException {
        Account account = accountDao.findByUsernameOrEmail(username);

        if (account == null) {
            throw new NotFoundException("Account not found");
        }

        return mapGetAccountDTO(account, account);
    }

    /**
     * Get an account by account_path
     * 
     * @param accountPath
     * @return
     * @throws NotFoundException
     */
    public GetAccountDTO getAccountByAccountPath(String accountPath, String authUser) throws NotFoundException {
        Account account = accountDao.findByAccountPath(accountPath);

        if (account == null) {
            throw new NotFoundException("Account not found");
        }

        GetAccountDTO getAccountDTO = mapGetAccountDTO(account, accountDao.findByUsernameOrEmail(authUser));
        return getAccountDTO;
    }

    /**
     * Get an account by email
     * @param username
     * 
     * @return
     * @throws NotFoundException
     */
    public GetAccountDTO getAccountByEmail(String email, String authUser) throws NotFoundException {
        Account account = accountDao.findByEmail(email);

        if (account == null) {
            throw new NotFoundException("Account not found");
        }

        GetAccountDTO getAccountDTO = mapGetAccountDTO(account, accountDao.findByUsernameOrEmail(authUser));
        return getAccountDTO;
    }

    /**
     * Search accounts
     * 
     * @param query
     * @return
     * @throws NotFoundException
     */
    public List<GetAccountDTO> searchAccounts(String query, String authUser) throws NotFoundException {
        List<Account> accounts = accountDao.search(query);

        if (accounts == null) {
            throw new NotFoundException("No accounts found");
        }

        Account queryingAccount = accountDao.findByUsernameOrEmail(authUser);
        List<GetAccountDTO> getAccountTOs = new ArrayList<GetAccountDTO>();
        for (Account account : accounts) {
            getAccountTOs.add(mapGetAccountDTO(account, queryingAccount));
        }

        return getAccountTOs;
    }

    /**
     * Create an account
     * 
     * @param createAccountTO
     * @return
     */
    @Transactional(readOnly = false)
    public GetAccountDTO createAccount(CreateAccountDTO createAccountTO) {
        if (accountDao.findByAccountPath(createAccountTO.getAccountPath()) != null) {
            BadRequestException ex = new BadRequestException("Account already exists");
            List<ErrorField> errors = new ArrayList<ErrorField>();
            errors.add(new ErrorField("account_path", "duplicate"));
            ex.setErrors(errors);
            throw ex;
        }

        if (accountDao.findByEmail(createAccountTO.getEmail()) != null) {
            BadRequestException ex = new BadRequestException("Account already exists");
            List<ErrorField> errors = new ArrayList<ErrorField>();
            errors.add(new ErrorField("email", "duplicate"));
            ex.setErrors(errors);
            throw ex;
        }

        User user = new User();
        user.setActive(false);
        user.setName(createAccountTO.getName());
        user.setEmail(createAccountTO.getEmail());
        user.setUsername(createAccountTO.getEmail());

        String password = createAccountTO.getPassword();

        // Check the authentication scheme
        switch (this.authenticationScheme) {
        case DEFAULT:
            user.setPassword(passwordEncoder.encode(password));
            break;

        case CROWDMAPID:
            createCrowdmapIDAccount(user, password);
            break;
        }
        userDao.create(user);

        Account account = new Account();
        account.setAccountPath(createAccountTO.getAccountPath());
        account.setAccountPrivate(createAccountTO.isAccountPrivate());
        account.setOwner(user);
        account.setRiverQuotaRemaining(riverQuota);
        accountDao.create(account);

        UserToken userToken = createUserToken(user);

        // Send activation email
        emailHelper.sendAccountActivationEmail(user, userToken);

        return mapper.map(account, GetAccountDTO.class);
    }

    /**
     * Creates a user on CrowdmapID
     * 
     * @param user
     * @param password
     */
    private void createCrowdmapIDAccount(User user, String password) {
        // Check if the user exists
        String email = user.getEmail();

        // Is the email registered on CrowdmapID?
        if (crowdmapIDClient.isRegistered(email)) {
            throw new BadRequestException(String.format("'%s' is already registered to another user", email));
        }

        String crowdmapUUID = crowdmapIDClient.register(email, password);
        if (crowdmapUUID == null) {
            throw new BadRequestException(String.format("An error occurred while registering '%s'", email));
        }

        logger.debug("{} successfully created with unique ID {}", email, crowdmapUUID);
    }

    /**
     * Modify an existing account
     * 
     * @param accountId
     * @param modifyAccountTO
     * @param authUser
     * @return
     */
    @Transactional(readOnly = false)
    public GetAccountDTO modifyAccount(Long accountId, ModifyAccountDTO modifyAccountTO, String authUser) {

        Account account = accountDao.findById(accountId);
        Account queryingAccount = accountDao.findByUsernameOrEmail(authUser);

        if (account == null)
            throw new NotFoundException("Account not found.");

        // If the querying account is not the the same as the account being
        // modified
        // raise an error
        if (!account.equals(queryingAccount)) {
            throw new ForbiddenException("Permission denied");
        }

        ModifyAccountDTO.User modifyAcOwner = modifyAccountTO.getOwner();

        // If another account already has the specified account path, raise an
        // error
        if (modifyAccountTO.getAccountPath() != null) {
            String accountPath = modifyAccountTO.getAccountPath();
            Account otherAccount = accountDao.findByAccountPath(accountPath);
            if (otherAccount != null && !otherAccount.equals(account)) {
                throw ErrorUtil.getBadRequestException("account_path", "duplicate");
            }
        }

        // > Account Owner properties
        if (modifyAcOwner != null && modifyAcOwner.getEmail() != null) {
            String email = modifyAcOwner.getEmail();
            Account otherAccount = accountDao.findByEmail(email);
            if (otherAccount != null && !otherAccount.equals(account)) {
                throw ErrorUtil.getBadRequestException("owner.email", "duplicate");
            }
        }

        // Password change
        if (modifyAcOwner != null && modifyAcOwner.getCurrentPassword() != null) {

            // Check for new password
            if (modifyAcOwner.getPassword() == null) {
                throw ErrorUtil.getBadRequestException("password", "missing");
            }

            String newPassword = modifyAcOwner.getPassword();
            String currentPassword = modifyAcOwner.getCurrentPassword();

            // Check the authentication scheme
            switch (this.authenticationScheme) {
            case CROWDMAPID:
                crowdmapIDClient.changePassword(account.getOwner().getEmail(), currentPassword, newPassword);
                break;

            default:
                // Current password
                if (!passwordEncoder.matches(currentPassword, account.getOwner().getPassword())) {
                    throw ErrorUtil.getBadRequestException("password", "invalid");
                }
                modifyAcOwner.setPassword(passwordEncoder.encode(newPassword));
            }

        }

        mapper.map(modifyAccountTO, account);
        accountDao.update(account);
        return mapGetAccountDTO(account, account);
    }

    /**
     * Convert the given account into a GetAccountDTO and filter out rivers that
     * queryingAccount has no access to.
     * 
     * @param account
     * @param queryingAccount
     * @return
     */
    public GetAccountDTO mapGetAccountDTO(Account account, Account queryingAccount) {

        if (!account.equals(queryingAccount)) {
            logger.debug("Getting list of accessible rivers");

            account.setRivers(riverService.filterVisible(account.getRivers(), queryingAccount));
            account.setCollaboratingRivers(
                    riverService.filterVisible(account.getCollaboratingRivers(), queryingAccount));
            account.setFollowingRivers(riverService.filterVisible(account.getFollowingRivers(), queryingAccount));

            // Filter out private buckets queryingAccount has no access to
            logger.debug("Getting the list of accessible buckets");
            account.setBuckets(bucketService.filterVisible(account.getBuckets(), queryingAccount));
            account.setCollaboratingBuckets(
                    bucketService.filterVisible(account.getCollaboratingBuckets(), queryingAccount));
            account.setFollowingBuckets(
                    bucketService.filterVisible(account.getFollowingBuckets(), queryingAccount));
        }

        GetAccountDTO accountDTO = mapper.map(account, GetAccountDTO.class);

        logger.debug("Setting the follow counts");
        int followerCount = account.getFollowers() == null ? 0 : account.getFollowers().size();
        accountDTO.setFollowerCount(followerCount);

        int followingCount = account.getFollowing() == null ? 0 : account.getFollowing().size();
        accountDTO.setFollowingCount(followingCount);

        logger.debug("Completed mapping Account POJO to DTO");
        return accountDTO;
    }

    /**
     * Returns true if the given token is valid for the user.
     * 
     * @param tokenString
     * @param user
     * @return
     */
    @Transactional(readOnly = false)
    public boolean isValidToken(String tokenString, User user) {
        // Activate account is matching user token found
        UserToken token = userTokenDao.findByToken(tokenString);

        if (token == null || token.getUser().getId() != user.getId()
                || token.getExpires().getTime() < (new Date()).getTime())
            return false;

        return true;
    }

    /**
     * Create a token for the given user.
     * 
     * @param user
     * @return
     */
    @Transactional(readOnly = false)
    public UserToken createUserToken(User user) {
        UserToken token = new UserToken();
        token.setToken(UUID.randomUUID().toString());
        Date dateCreated = new Date();
        long expiryDate = dateCreated.getTime() + 86400000L;
        token.setExpires(new Date(expiryDate));
        token.setUser(user);
        token.setCreated(dateCreated);

        userTokenDao.create(token);

        return token;
    }

    /**
     * Get clients in the given account
     * 
     * @param accountId
     * @param authUser
     * @return
     */
    public List<GetClientDTO> getClients(Long accountId, String authUser) {

        Account account = accountDao.findById(accountId);

        if (account == null)
            throw new NotFoundException("Account not found");

        Account authAccount = accountDao.findByUsernameOrEmail(authUser);

        if (!authAccount.equals(account))
            throw new ForbiddenException("Permission Denied");

        List<GetClientDTO> clients = new ArrayList<GetClientDTO>();
        for (Client client : account.getClients()) {
            GetClientDTO dto = mapper.map(client, GetClientDTO.class);
            dto.setClientSecret(decryptClientSecret(client));

            clients.add(dto);
        }

        return clients;
    }

    /**
     * Create a new client under the given account.
     * 
     * @param accountId
     * @param createClientTO
     * @param name
     * @return
     */
    @Transactional(readOnly = false)
    public GetClientDTO createClient(Long accountId, CreateClientDTO createClientTO, String name) {
        Account account = accountDao.findById(accountId);

        if (account == null)
            throw new NotFoundException("Account not found");

        if (!account.equals(accountDao.findByUsernameOrEmail(name)))
            throw new ForbiddenException("Permission Denied");

        Client client = mapper.map(createClientTO, Client.class);
        String secret = resetClientCredentials(client);
        client.setAccount(account);
        client.setRoles(new HashSet<Role>());
        client.getRoles().add(roleDao.findByName("client"));
        client.setActive(true);
        clientDao.create(client);

        GetClientDTO dto = mapper.map(client, GetClientDTO.class);
        dto.setClientSecret(secret);
        return dto;
    }

    @Transactional(readOnly = false)
    public void deleteApp(Long accountId, Long clientId, String authUser) {
        Account account = accountDao.findById(accountId);

        if (account == null)
            throw new NotFoundException("Account not found");

        if (!account.equals(accountDao.findByUsernameOrEmail(authUser)))
            throw new ForbiddenException("Permission Denied");

        Client client = clientDao.findById(clientId);
        if (client == null)
            throw new NotFoundException("Client not found");

        clientDao.delete(client);
    }

    /**
     * Mofify the given client app.
     * 
     * @param accountId
     * @param dbClientId
     * @param modifyClientDTO
     * @param authUser
     * @return
     */
    @Transactional(readOnly = false)
    public GetClientDTO modifyClient(Long accountId, Long dbClientId, ModifyClientDTO modifyClientDTO,
            String authUser) {

        Account account = accountDao.findById(accountId);

        if (account == null)
            throw new NotFoundException("Account not found");

        if (!account.equals(accountDao.findByUsernameOrEmail(authUser)))
            throw new ForbiddenException("Permission Denied");

        Client client = clientDao.findById(dbClientId);
        if (client == null)
            throw new NotFoundException("Client not found");

        mapper.map(modifyClientDTO, client);

        String secret = null;
        if (modifyClientDTO.getClientId() != null) {
            secret = resetClientCredentials(client);
        }

        clientDao.update(client);

        GetClientDTO getClientDTO = mapper.map(client, GetClientDTO.class);
        if (secret != null) {
            // We present the plain text secret
            getClientDTO.setClientSecret(secret);
        } else {
            getClientDTO.setClientSecret(decryptClientSecret(client));
        }

        return getClientDTO;
    }

    /**
     * Sets a random client_id and client_secret in the given Client object.
     * 
     * @param client
     * @return The plain text secret.
     */
    private String resetClientCredentials(Client client) {
        // Reset the client credentials
        String clientId = UUID.randomUUID().toString();
        String secret = UUID.randomUUID().toString();

        // Encrypt the secret
        TextEncryptor encryptor = Encryptors.text(TextUtil.convertStringToHex(encryptionKey),
                TextUtil.convertStringToHex(clientId));
        client.setClientSecret(encryptor.encrypt(secret));
        client.setClientId(clientId);

        return secret;
    }

    /**
     * Get the plain text client_secret for the given client.
     * 
     * @param client
     * @return
     */
    private String decryptClientSecret(Client client) {
        TextEncryptor encryptor = Encryptors.text(TextUtil.convertStringToHex(encryptionKey),
                TextUtil.convertStringToHex(client.getClientId()));
        return encryptor.decrypt(client.getClientSecret());
    }

    /**
     * Gets and returns the list of {@link Account} entities that are following
     * the {@Account} specified in <code>id</code>.
     * <code>accountId</code> is an optional parameter and when specified,
     * verifies whether the {@link Account} with that id (<code>accountId</code>
     * ) is a follower
     * 
     * @param id
     * @param accountId
     * @return {@link java.util.List}
     */
    @Transactional
    public List<FollowerDTO> getFollowers(Long id, Long accountId) {
        Account account = getAccount(id);

        List<FollowerDTO> followers = new ArrayList<FollowerDTO>();
        if (accountId == null) {
            for (AccountFollower accountFollower : account.getFollowers()) {
                followers.add(mapFollowerDTO(accountFollower.getFollower()));
            }
        } else {
            Account follower = accountDao.getFollower(account, accountId);
            if (follower == null) {
                throw new NotFoundException(String.format("Account %d does not follow %d", accountId, id));
            }

            followers.add(mapFollowerDTO(follower));
        }

        return followers;
    }

    /**
     * Converts the given {@link Account} to a {@link FollowerDTO}
     * 
     * @param account
     * @return
     */
    private FollowerDTO mapFollowerDTO(Account account) {
        FollowerDTO followerDTO = new FollowerDTO();

        followerDTO.setId(account.getId());
        followerDTO.setAccountPath(account.getAccountPath());
        followerDTO.setEmail(account.getOwner().getEmail());
        followerDTO.setName(account.getOwner().getName());
        followerDTO.setFollowerCount(account.getFollowers().size());
        followerDTO.setFollowingCount(account.getFollowing().size());

        return followerDTO;
    }

    /**
     * Adds the {@link Account} with the specified <code>accountId</code> to the
     * list of followers for the {@link Account} specified in <code>id</code>
     * 
     * @param id
     * @param accountId
     */
    @Transactional
    public void addFollower(Long id, Long accountId) {
        if (id.equals(accountId)) {
            throw new BadRequestException("An account cannot follow itself");
        }

        Account account = getAccount(id);

        if (accountDao.getFollower(account, accountId) != null) {
            throw new BadRequestException(
                    String.format("Account %d is already following account %d", accountId, id));
        }

        Account follower = getAccount(accountId);
        accountDao.addFollower(account, follower);

        logActivity(follower, ActivityType.FOLLOW, account);
    }

    /**
     * Internal helper method for retrieving a {@link Account} entity using its
     * unique database ID
     * 
     * @param accountId
     * @return
     */
    private Account getAccount(Long accountId) {
        Account account = accountDao.findById(accountId);
        if (account == null) {
            throw new NotFoundException(String.format("Account %d does not exist", accountId));
        }

        return account;
    }

    /**
     * Deletes the {@link Account} specified in <code>accountId</code> from the
     * list of followers for the {@link Account} specified in <code>id</code>
     * 
     * @param id
     * @param accountId
     */
    @Transactional
    public void deleteFollower(Long id, Long accountId) {
        Account account = getAccount(id);
        if (!accountDao.deleteFollower(account, getAccount(accountId))) {
            throw new NotFoundException(String.format("Account %d does not follow account %d", accountId, id));
        }
    }

    /**
     * Search accounts
     * 
     * @param searchTerm
     * @param count
     * @param page
     * @return
     */
    @Transactional
    public List<GetAccountDTO> searchAccounts(String searchTerm, int count, int page) {
        List<Account> accounts = accountDao.search(searchTerm, count, page);

        for (Account account : accounts) {
            if (account.isAccountPrivate()) {
                account.setRivers(new ArrayList<River>());
                account.setCollaboratingRivers(new ArrayList<River>());
                account.setBuckets(new ArrayList<Bucket>());
                account.setCollaboratingBuckets(new ArrayList<Bucket>());
                continue;
            }
            // Remove private buckets (owned and collaborating)
            List<Bucket> privateOwnerBuckets = new ArrayList<Bucket>();
            for (Bucket bucket : account.getBuckets()) {
                if (!bucket.isPublished()) {
                    privateOwnerBuckets.add(bucket);
                }
            }
            account.getBuckets().removeAll(privateOwnerBuckets);

            // Private buckets the current account is collaborating on
            List<Bucket> privateCollaboratingBuckets = new ArrayList<Bucket>();
            for (Bucket bucket : account.getCollaboratingBuckets()) {
                if (!bucket.isPublished()) {
                    privateCollaboratingBuckets.add(bucket);
                }
            }
            account.getCollaboratingBuckets().removeAll(privateCollaboratingBuckets);

            // Remove private rivers (owned and collaborating)
            List<River> privateOwnerRivers = new ArrayList<River>();
            for (River river : account.getRivers()) {
                if (!river.getRiverPublic()) {
                    privateOwnerRivers.add(river);
                }
            }
            account.getRivers().removeAll(privateOwnerRivers);

            // Private rivers the current account is collaborating on
            List<River> privateCollaboratingRivers = new ArrayList<River>();
            for (River river : account.getCollaboratingRivers()) {
                if (!river.getRiverPublic()) {
                    privateCollaboratingRivers.add(river);
                }
            }
            account.getCollaboratingRivers().removeAll(privateCollaboratingRivers);
        }

        List<GetAccountDTO> getAccountDTOs = new ArrayList<GetAccountDTO>();
        for (Account account : accounts) {
            getAccountDTOs.add(mapper.map(account, GetAccountDTO.class));
        }

        return getAccountDTOs;
    }

    /**
     * Create an activity for the given account action.
     * 
     * @param account
     * @param action
     * @param actionOn
     */
    @Transactional(readOnly = true)
    public void logActivity(Account account, ActivityType action, Object actionOn) {

        Activity activity = null;
        if (actionOn instanceof River) {
            activity = new RiverActivity();
            ((RiverActivity) activity).setActionOnObj((River) actionOn);
        } else if (actionOn instanceof Bucket) {
            activity = new BucketActivity();
            ((BucketActivity) activity).setActionOnObj((Bucket) actionOn);
        } else if (actionOn instanceof Form) {
            activity = new FormActivity();
            ((FormActivity) activity).setActionOnObj((Form) actionOn);
        } else if (actionOn instanceof Account) {
            activity = new AccountActivity();
            ((AccountActivity) activity).setActionOnObj((Account) actionOn);
        } else if (actionOn instanceof RiverCollaborator) {
            activity = new RiverCollaboratorActivity();
            ((RiverCollaboratorActivity) activity).setActionOnObj((RiverCollaborator) actionOn);
        } else if (actionOn instanceof BucketCollaborator) {
            activity = new BucketCollaboratorActivity();
            ((BucketCollaboratorActivity) activity).setActionOnObj((BucketCollaborator) actionOn);
        }

        if (activity == null)
            throw new RuntimeException("Unknown actionOn obj provided");

        activity.setAccount(account);
        activity.setActionDateAdd(new Date());

        switch (action) {
        case CREATE:
            activity.setAction("create");
            break;
        case FOLLOW:
            activity.setAction("follow");
            break;
        case INVITE:
            activity.setAction("invite");
            break;
        case COMMENT:
            activity.setAction("comment");
            break;
        }

        activityDao.create(activity);
    }

    /**
     * Get an activity stream
     * 
     * When the followers parameter is true, get activities for 
     * the accounts <code>accountId</code> is following otherwise 
     * get <code>accountId</code>'s own activities.
     * 
     * @param accountId
     * @param count
     * @param lastId
     * @param newer
     * @param followers
     * @param authUser
     * @return
     */
    public List<GetActivityDTO> getActivities(Long accountId, Integer count, Long lastId, Boolean newer,
            Boolean followers, Account authAccount) {

        List<Activity> activities = activityDao.find(accountId, count, lastId, newer, followers);

        if (activities == null)
            throw new NotFoundException("No activities found.");

        List<GetActivityDTO> activityDtos = new ArrayList<GetActivityDTO>();
        for (Activity activity : activities) {

            // Update last id
            if (lastId != null) {
                lastId = (newer != null && newer) ? Math.max(lastId, activity.getId())
                        : Math.min(lastId, activity.getId());
            } else {
                lastId = activity.getId();
            }

            // Filter out activities on objects the authenticated user has
            // no access to
            if (activity instanceof RiverActivity) {
                River river = ((RiverActivity) activity).getActionOnObj();

                if (!river.getRiverPublic() && !riverService.isOwner(river, authAccount))
                    continue;
            } else if (activity instanceof BucketActivity) {
                Bucket bucket = ((BucketActivity) activity).getActionOnObj();

                if (!bucket.isPublished() && !bucketService.isOwner(bucket, authAccount))
                    continue;
            }

            activityDtos.add(mapper.map(activity, GetActivityDTO.class));
        }

        // If all activities removed due to permissions, repeat
        if (activityDtos.size() == 0)
            return getActivities(accountId, count, lastId, newer, followers, authAccount);

        return activityDtos;
    }

    /**
     * Get Activities for the given account.
     * 
     * @param accountId
     * @param authUser
     * @return
     */
    public List<GetActivityDTO> getActivities(Long accountId, String authUser) {
        Account account = accountDao.findByUsernameOrEmail(authUser);
        return getActivities(accountId, 50, Long.MAX_VALUE, false, false, account);
    }

    /**
     * Get Activities by the Account that authUser is following.
     * 
     * @param count
     * @param lastId
     * @param newer
     * @param authUser
     * @return
     */
    public List<GetActivityDTO> getTimeline(Integer count, Long lastId, Boolean newer, String authUser) {
        Account account = accountDao.findByUsernameOrEmail(authUser);
        return getActivities(account.getId(), count, lastId, newer, true, account);
    }

    /**
     * Activates a newly created account.
     * 
     * @param activateAccountDTO
     */
    @Transactional(readOnly = false)
    public void activateAccount(ActivateAccountDTO activateAccountDTO) {
        String email = activateAccountDTO.getEmail();
        String tokenString = activateAccountDTO.getToken();
        Account account = accountDao.findByEmail(email);

        if (account == null) {
            throw new NotFoundException(String.format("Account %s does not exist", email));
        }

        // If the user is active, return early
        User user = account.getOwner();
        if (user.getActive()) {
            logger.info("Ignoring account activation request. '{}' is already active.", email);
            return;
        }

        // Validate the token
        if (!isValidToken(tokenString, user)) {
            logger.debug("Invalid token {}", tokenString);
            throw new BadRequestException(
                    String.format("Invalid activation token specified - %s", activateAccountDTO.getToken()));
        }

        account.setActive(true);
        user.setActive(true);
        user.setExpired(false);
        user.setLocked(false);
        user.setRoles(new HashSet<Role>());
        user.getRoles().add(roleDao.findByName("user"));

        accountDao.update(account);
        userDao.update(user);

        userTokenDao.delete(tokenString);
    }

    /**
     * Sets a new password for the user associated with the email
     * specified in <code>email</code>  
     * @param resetToken
     * @param email
     * @param password
     */
    @Transactional(readOnly = false)
    public void resetPassword(String resetToken, String email, String password) {
        Account account = accountDao.findByEmail(email);
        if (account == null) {
            throw new NotFoundException(String.format("Account '%s' does not exist", email));
        }

        // Reset the password
        switch (authenticationScheme) {
        case CROWDMAPID:
            boolean success = crowdmapIDClient.setPassword(resetToken, email, password);
            if (!success) {
                throw new BadRequestException("Password reset failed");
            }
            break;

        default:
            if (!isValidToken(resetToken, account.getOwner())) {
                throw new BadRequestException(String.format("Invalid token specified - '%s'", resetToken));
            }
            User user = account.getOwner();
            user.setPassword(passwordEncoder.encode(password));
            userDao.update(user);

            // Delete the token
            userTokenDao.delete(resetToken);
        }

    }

    /**
     * Creates and sends a password reset token to the user
     * with the email address specified in <code>email</code>
     * 
     * @param email
     */
    public void forgotPassword(String email) {
        Account account = accountDao.findByEmail(email);
        if (account == null) {
            throw new NotFoundException(String.format("'%s' does not belong to a registered account", email));
        }

        // Check the configured authentication scheme
        User user = account.getOwner();
        switch (authenticationScheme) {
        case CROWDMAPID:
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("email", email);
            params.put("token", "%token%");
            String mailBody = emailHelper.getEmailBody(EmailType.RESET_PASSWORD, params, user.getName());
            crowdmapIDClient.requestPassword(email, mailBody);
            break;

        case DEFAULT:
            UserToken userToken = createUserToken(user);
            emailHelper.sendPasswordResetEmail(user, userToken);
        }
    }

}