com.squid.kraken.v4.persistence.dao.UserDAO.java Source code

Java tutorial

Introduction

Here is the source code for com.squid.kraken.v4.persistence.dao.UserDAO.java

Source

/*******************************************************************************
 * Copyright  Squid Solutions, 2016
 *
 * This file is part of Open Bouquet software.
 *  
 * 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 (version 3 of the License).
 *
 * There is a special FOSS exception to the terms and conditions of the 
 * licenses as they are applied to this program. See LICENSE.txt in
 * the directory of this program distribution.
 *
 * 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.
 *
 * Squid Solutions also offers commercial licenses with additional warranties,
 * professional functionalities or services. If you purchase a commercial
 * license, then it supersedes and replaces any other agreement between
 * you and Squid Solutions (above licenses and LICENSE.txt included).
 * See http://www.squidsolutions.com/EnterpriseBouquet/
 *******************************************************************************/
package com.squid.kraken.v4.persistence.dao;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.RandomStringUtils;

import com.google.common.base.Optional;
import com.squid.kraken.v4.api.core.APIException;
import com.squid.kraken.v4.api.core.APIException.ApiError;
import com.squid.kraken.v4.api.core.AccessRightsUtils;
import com.squid.kraken.v4.api.core.InvalidCredentialsAPIException;
import com.squid.kraken.v4.api.core.RolePriorityMapper;
import com.squid.kraken.v4.api.core.ServiceUtils;
import com.squid.kraken.v4.caching.Cache;
import com.squid.kraken.v4.caching.CacheFactoryEHCache;
import com.squid.kraken.v4.model.AccessRight;
import com.squid.kraken.v4.model.AccessRight.Role;
import com.squid.kraken.v4.model.AccessToken;
import com.squid.kraken.v4.model.Customer;
import com.squid.kraken.v4.model.CustomerPK;
import com.squid.kraken.v4.model.GenericPK;
import com.squid.kraken.v4.model.Persistent;
import com.squid.kraken.v4.model.User;
import com.squid.kraken.v4.model.UserGroup;
import com.squid.kraken.v4.model.UserGroupPK;
import com.squid.kraken.v4.model.UserPK;
import com.squid.kraken.v4.persistence.AccessRightsPersistentDAO;
import com.squid.kraken.v4.persistence.AppContext;
import com.squid.kraken.v4.persistence.DAOFactory;
import com.squid.kraken.v4.persistence.DataStore;
import com.squid.kraken.v4.persistence.DataStoreEvent;
import com.squid.kraken.v4.persistence.DataStoreQueryField;

public class UserDAO extends AccessRightsPersistentDAO<User, UserPK> {

    public static final String PASSWORD_REGEX = "((?=[\\S]+$)).+";

    public static final String PASSWORD_REGEX2 = "(?=.*[0-9A-Z]).+";

    private Cache<CustomerPK, List<UserPK>> findByCustomerCache;

    public UserDAO(DataStore ds) {
        super(User.class, ds);
        this.findByCustomerCache = CacheFactoryEHCache.getCacheFactory().getCollectionsCache(UserPK.class,
                "findByCustomer");
    }

    public Optional<User> findByLogin(AppContext app, String login) {
        List<DataStoreQueryField> queryFields = new LinkedList<DataStoreQueryField>();
        queryFields.add(new DataStoreQueryField("login", login.toLowerCase()));
        List<User> find = ds.find(app, type, queryFields, null);
        if (find.isEmpty()) {
            return Optional.absent();
        } else {
            return Optional.of(find.get(0));
        }
    }

    public Optional<User> findByEmail(AppContext app, String email) {
        List<DataStoreQueryField> queryFields = new LinkedList<DataStoreQueryField>();
        queryFields.add(new DataStoreQueryField("email", email));
        List<User> find = ds.find(app, type, queryFields, null);
        if (find.isEmpty()) {
            return Optional.absent();
        } else {
            return Optional.of(find.get(0));
        }
    }

    public List<User> findByCustomer(AppContext app, CustomerPK customerId) {
        return super.find(app, customerId, null, findByCustomerCache);
    }

    @Override
    public Optional<User> read(AppContext ctx, UserPK id) {
        Optional<User> object = ds.read(ctx, type, id);
        if (object.isPresent()) {
            applyUserRigths(ctx, object.get());
        }
        return object;
    }

    @Override
    public User readNotNull(AppContext ctx, UserPK id) {
        User object = ds.readNotNull(ctx, type, id);
        applyUserRigths(ctx, object);
        return object;
    }

    /**
     * Implement specific access rules :
     * <ul>
     * <li>READ right on Customer is required</li>
     * <li>if the current user if the user read, it has OWNER rights, otherwise
     * it will inherit the right from its parent</li>
     * </ul>
     */
    private void applyUserRigths(AppContext ctx, User object) {
        if (!object.getId().equals(ctx.getUser().getId())) {
            Persistent<? extends GenericPK> parent = object.getParentObject(ctx);
            // set the parent's role as userRole
            object.setUserRole(AccessRightsUtils.getInstance().getRole(ctx.getUser(), parent));
            // check the READ access rights
            AccessRightsUtils.getInstance().checkRole(ctx, parent, Role.READ);
        } else {
            // the calling user is itself the user
            object.setUserRole(Role.OWNER);
        }
    }

    @Override
    public void notifyEvent(DataStoreEvent event) {
        UserPK id = null;
        if (event.getSource() instanceof UserPK) {
            // deletion
            id = (UserPK) event.getSource();
            instanceCache.remove(id);
        }
        if (event.getSource() instanceof User) {
            // creation or update
            User source = (User) event.getSource();
            id = source.getId();
            instanceCache.put(id, source);
        }
        if (id != null) {
            // finder cache invalidation
            findByCustomerCache.remove(new CustomerPK(id.getCustomerId()));
        }
    }

    @Override
    public User create(AppContext ctx, User userData) {
        if (userData.getPassword() == null) {
            // set a random password
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 8; i++) {
                sb.append(RandomStringUtils.randomAlphanumeric(8));
            }
            userData.setPassword(sb.toString());
        }
        // hash the password
        userData.setPassword(hashPassword(ctx, userData.getPassword()));

        // check for duplicates
        if (findByEmail(ctx, userData.getEmail()).isPresent()) {
            throw new APIException("Duplicate email", ctx.isNoError(), ApiError.DUPLICATE_EMAIL);
        }
        if (findByLogin(ctx, userData.getLogin()).isPresent()) {
            throw new APIException("Duplicate login", ctx.isNoError(), ApiError.DUPLICATE_LOGIN);
        }

        // check the access rights
        Persistent<? extends GenericPK> parent = userData.getParentObject(ctx);

        // need write role on parent
        AccessRightsUtils.getInstance().checkRole(ctx, parent, Role.WRITE);

        checkUserGroupAccess(ctx, userData.getGroups());

        return ds.create(ctx, userData);
    }

    @Override
    public void update(AppContext ctx, User userData) {
        User existingUser = DAOFactory.getDAOFactory().getDAO(User.class).readNotNull(ctx, userData.getId());
        if (userData.getPassword() != null) {

            AppContext rootUserContext = ServiceUtils.getInstance().getRootUserContext(ctx.getCustomerId());
            Customer customer = DAOFactory.getDAOFactory().getDAO(Customer.class).readNotNull(rootUserContext,
                    ctx.getCustomerPk());
            boolean doUpdate = false;
            // check for conditions to update the password
            if ((ctx.getToken() != null) && (ctx.getToken().getType().equals(AccessToken.Type.RESET_PWD))) {
                // the token is a reset pwd token
                doUpdate = true;
            } else if (AccessRightsUtils.getInstance().hasRole(ctx, customer, Role.WRITE)) {
                // the user is an admin
                doUpdate = true;
            } else {
                int index = userData.getPassword().indexOf(" ");
                if (index >= 0) {
                    String oldPassword = userData.getPassword().substring(0, index);
                    if (ServiceUtils.getInstance().matchPassword(ctx, existingUser, oldPassword)) {
                        // old and new passwords match
                        doUpdate = true;
                    }
                } else {
                    throw new InvalidCredentialsAPIException(
                            "Sent password string should be '[new-password] [old-password]'", ctx.isNoError());
                }
            }
            if (doUpdate) {
                // update the password
                String newPassword = userData.getPassword().substring(userData.getPassword().indexOf(" ") + 1);
                userData.setPassword(hashPassword(ctx, newPassword));
            } else {
                throw new InvalidCredentialsAPIException("Invalid old password", ctx.isNoError());
            }
        } else {
            // do not update the password
            userData.setPassword(existingUser.getPassword());
        }

        // check for duplicates
        Optional<User> findByEmail = findByEmail(ctx, userData.getEmail());
        if (findByEmail.isPresent()) {
            if (!findByEmail.get().getId().equals(userData.getId())) {
                throw new APIException("Duplicate email", ctx.isNoError(), ApiError.DUPLICATE_EMAIL);
            }
        }
        Optional<User> findByLogin = findByLogin(ctx, userData.getLogin());
        if (findByLogin.isPresent()) {
            if (!findByLogin.get().getId().equals(userData.getId())) {
                throw new APIException("Duplicate login", ctx.isNoError(), ApiError.DUPLICATE_LOGIN);
            }
        }

        // check the access rights
        // prepare neccessary information

        String ctxUserId = ctx.getUser().getId().getUserId();
        String newUserId = userData.getId().getUserId();

        // caller != callee
        if (!ctxUserId.equalsIgnoreCase(newUserId)) {
            // get the customer and its access rights
            Customer customer = DAOFactory.getDAOFactory().getDAO(Customer.class).readNotNull(ctx,
                    ctx.getCustomerPk());
            Set<AccessRight> accessRights = customer.getAccessRights();

            // retrieve access rights of the context user and the new user from
            // access rights of customer
            List<RolePriorityMapper> ctxAccessRights = new ArrayList<RolePriorityMapper>();
            List<RolePriorityMapper> newUserAccessRights = new ArrayList<RolePriorityMapper>();
            for (AccessRight ar : accessRights) {
                if ((ar.getUserId() != null) && (ar.getUserId().equals(ctxUserId))) {
                    ctxAccessRights.add(new RolePriorityMapper(ar.getRole()));
                }
                if ((ar.getUserId() != null) && (ar.getUserId().equals(newUserId))) {
                    newUserAccessRights.add(new RolePriorityMapper(ar.getRole()));
                }
            }

            // compare access right with new user
            RolePriorityMapper ctxHighestRole = getHighestRole(ctxAccessRights);
            RolePriorityMapper newUserHighestRole = getHighestRole(newUserAccessRights);
            if ((newUserHighestRole != null) && (ctxHighestRole != null)) {
                if ((newUserHighestRole.getRole() != Role.OWNER) || (ctxHighestRole.getRole() != Role.OWNER)) {
                    if (newUserHighestRole.getPriority() >= ctxHighestRole.getPriority()) {
                        throw new InvalidCredentialsAPIException("Insufficient privileges : caller hasn't "
                                + newUserHighestRole.getRole().name() + " role on " + ctxUserId, ctx.isNoError());
                    }
                }
            } else {
                if ((ctxHighestRole != null) && (ctxHighestRole.getRole() != Role.WRITE)
                        && (ctxHighestRole.getRole() != Role.OWNER)) {
                    throw new InvalidCredentialsAPIException(
                            "Insufficient privileges : caller hasn't WRITE/OWNER role on" + ctxUserId,
                            ctx.isNoError());
                }
            }
        }

        // check for groups membership update
        if (!userData.getGroups().equals(existingUser.getGroups())) {
            List<String> added = new ArrayList<String>(userData.getGroups());
            added.removeAll(existingUser.getGroups());
            checkUserGroupAccess(ctx, added);
        }
        ds.update(ctx, userData);
    }

    private String hashPassword(AppContext ctx, String pwd) {
        if (pwd != null) {
            // validate password
            if (!(pwd.length() >= 8 && pwd.length() <= 100)) {
                throw new APIException("Invalid password (must be from 8 to 100 chars)", ctx.isNoError(),
                        ApiError.PASSWORD_INVALID_SIZE);
            } else if (!pwd.matches(PASSWORD_REGEX)) {
                throw new APIException("Invalid password (cannot contain whitespaces or any special chars)",
                        ctx.isNoError(), ApiError.PASSWORD_INVALID_CHAR);
            } else if (!pwd.matches(PASSWORD_REGEX2)) {
                throw new APIException("Invalid password (should contain at least a capital letter or a digit)",
                        ctx.isNoError(), ApiError.PASSWORD_INVALID_RULES);
            }
        }
        // hash using customer's salt
        Customer customer = ServiceUtils.getInstance().getContextCustomer(ctx);
        String hashedPassword = ServiceUtils.getInstance().md5(customer.getMD5Salt(), pwd);
        return hashedPassword;
    }

    @Override
    public void delete(AppContext ctx, UserPK id) {
        User user = DAOFactory.getDAOFactory().getDAO(User.class).readNotNull(ctx, id);
        Persistent<? extends GenericPK> parent = user.getParentObject(ctx);
        // check specific access rights
        Role currentUserRole = AccessRightsUtils.getInstance().getRole(ctx.getUser(), parent);
        Role targetUserRole = AccessRightsUtils.getInstance().getRole(user, parent);
        boolean doDelete = false;
        if (ctx.getUser().isSuperUser()) {
            doDelete = true;
        }
        if ((!doDelete) && ((currentUserRole != null) && (currentUserRole.ordinal() > Role.READ.ordinal())
                && (targetUserRole == null || (currentUserRole.ordinal() > targetUserRole.ordinal())))) {
            // of we can delete
            doDelete = true;
        }
        if (doDelete) {
            ds.delete(ctx, type, id);
        } else {
            throw new InvalidCredentialsAPIException("Insufficient privileges", ctx.isNoError());
        }

    }

    /**
     * Get the highest priority role from a list of assigned roles.
     * 
     * @param roles
     *            access right list
     * @return the highest priority role, NULL if the list in argument is empty
     *         or null
     */
    private RolePriorityMapper getHighestRole(List<RolePriorityMapper> roles) {
        if ((roles != null) && !roles.isEmpty()) {
            Collections.sort(roles);
            return roles.get(0);
        }
        return null;
    }

    /**
     * Check if current user has write role on usergroups
     * 
     * @param ctx
     * @param groups
     */
    private void checkUserGroupAccess(AppContext ctx, List<String> groups) {
        for (String addedGroupId : groups) {
            UserGroupDAO dao = (UserGroupDAO) DAOFactory.getDAOFactory().getDAO(UserGroup.class);
            UserGroup userGroup = dao.readNotNull(ctx, new UserGroupPK(ctx.getCustomerId(), addedGroupId));
            AccessRightsUtils.getInstance().checkRole(ctx, userGroup, Role.WRITE);
        }
    }
}