Java tutorial
///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition 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; version 3 of the License. // // This community edition 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 this program; if not, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.business.user; import java.sql.Timestamp; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.hibernate.LockMode; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.projectforge.business.login.Login; import org.projectforge.business.multitenancy.TenantChecker; import org.projectforge.business.multitenancy.TenantService; import org.projectforge.framework.access.AccessChecker; import org.projectforge.framework.access.AccessException; import org.projectforge.framework.access.AccessType; import org.projectforge.framework.access.OperationType; import org.projectforge.framework.persistence.api.BaseDao; import org.projectforge.framework.persistence.api.BaseSearchFilter; import org.projectforge.framework.persistence.api.ModificationStatus; import org.projectforge.framework.persistence.api.QueryFilter; import org.projectforge.framework.persistence.history.DisplayHistoryEntry; import org.projectforge.framework.persistence.history.HistoryBaseDaoAdapter; import org.projectforge.framework.persistence.jpa.PfEmgrFactory; import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext; import org.projectforge.framework.persistence.user.entities.PFUserDO; import org.projectforge.framework.persistence.user.entities.TenantDO; import org.projectforge.framework.persistence.user.entities.UserRightDO; import org.projectforge.framework.utils.NumberHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import de.micromata.genome.jpa.Clauses; import de.micromata.genome.jpa.CriteriaUpdate; /** * * @author Kai Reinhard (k.reinhard@micromata.de) * */ @Repository public class UserDao extends BaseDao<PFUserDO> { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(UserDao.class); private static final short AUTHENTICATION_TOKEN_LENGTH = 20; private final List<UserChangedListener> userChangedListeners = new LinkedList<UserChangedListener>(); @Autowired private ApplicationContext applicationContext; public UserDao() { super(PFUserDO.class); } /** * Register given listener. The listener is called every time a user was inserted, updated or deleted. * * @param userChangedListener */ public void register(final UserChangedListener userChangedListener) { userChangedListeners.add(userChangedListener); } public QueryFilter getDefaultFilter() { final QueryFilter queryFilter = new QueryFilter(null, false); queryFilter.add(Restrictions.eq("deleted", false)); return queryFilter; } /** * @see org.projectforge.framework.persistence.api.BaseDao#createQueryFilter(org.projectforge.framework.persistence.api.BaseSearchFilter) */ @Override protected QueryFilter createQueryFilter(final BaseSearchFilter filter) { final boolean superAdmin = TenantChecker.isSuperAdmin(ThreadLocalUserContext.getUser()) == true; if (superAdmin == false) { return super.createQueryFilter(filter); } return new QueryFilter(filter, true); } @Override public List<PFUserDO> getList(final BaseSearchFilter filter) { final PFUserFilter myFilter; if (filter instanceof PFUserFilter) { myFilter = (PFUserFilter) filter; } else { myFilter = new PFUserFilter(filter); } final QueryFilter queryFilter = createQueryFilter(myFilter); if (myFilter.getDeactivatedUser() != null) { queryFilter.add(Restrictions.eq("deactivated", myFilter.getDeactivatedUser())); } if (Login.getInstance().hasExternalUsermanagementSystem() == true) { // Check hasExternalUsermngmntSystem because otherwise the filter is may-be preset for an user and the user can't change the filter // (because the fields aren't visible). if (myFilter.getRestrictedUser() != null) { queryFilter.add(Restrictions.eq("restrictedUser", myFilter.getRestrictedUser())); } if (myFilter.getLocalUser() != null) { queryFilter.add(Restrictions.eq("localUser", myFilter.getLocalUser())); } } if (myFilter.getHrPlanning() != null) { queryFilter.add(Restrictions.eq("hrPlanning", myFilter.getHrPlanning())); } queryFilter.addOrder(Order.asc("username")); List<PFUserDO> list = getList(queryFilter); if (myFilter.getIsAdminUser() != null) { final List<PFUserDO> origList = list; list = new LinkedList<PFUserDO>(); for (final PFUserDO user : origList) { if (myFilter.getIsAdminUser() == accessChecker.isUserMemberOfAdminGroup(user, false)) { list.add(user); } } } if (applicationContext.getBean(TenantService.class).isMultiTenancyAvailable() == true && TenantChecker.isSuperAdmin(ThreadLocalUserContext.getUser()) == false) { final List<PFUserDO> origList = list; list = new LinkedList<PFUserDO>(); for (final PFUserDO user : origList) { if (tenantChecker.isPartOfTenant(ThreadLocalUserContext.getUserContext().getCurrentTenant(), user) == true) { list.add(user); } } } return list; } public Collection<Integer> getAssignedGroups(final PFUserDO user) { return getUserGroupCache().getUserGroups(user); } public Collection<Integer> getAssignedTenants(final PFUserDO user) { final List<TenantDO> list = (List<TenantDO>) getHibernateTemplate() .find("from TenantDO t where ? member of t.assignedUsers", user); final Set<Integer> result = new HashSet<Integer>(); if (list != null) { for (final TenantDO tenant : list) { result.add(tenant.getId()); } } return result; } public List<UserRightDO> getUserRights(final Integer userId) { return getUserGroupCache().getUserRights(userId); } /** * @see org.projectforge.framework.persistence.api.BaseDao#onChange(org.projectforge.core.ExtendedBaseDO, * org.projectforge.core.ExtendedBaseDO) */ @Override protected void onChange(final PFUserDO obj, final PFUserDO dbObj) { super.onChange(obj, dbObj); } /** * @see org.projectforge.framework.persistence.api.BaseDao#afterSave(org.projectforge.core.ExtendedBaseDO) */ @Override protected void afterSave(final PFUserDO obj) { super.afterSave(obj); for (final UserChangedListener userChangedListener : userChangedListeners) { userChangedListener.afterUserChanged(obj, OperationType.INSERT); } } /** * @see org.projectforge.framework.persistence.api.BaseDao#afterUpdate(org.projectforge.core.ExtendedBaseDO, * org.projectforge.core.ExtendedBaseDO) */ @Override protected void afterUpdate(final PFUserDO obj, final PFUserDO dbObj) { super.afterUpdate(obj, dbObj); for (final UserChangedListener userChangedListener : userChangedListeners) { userChangedListener.afterUserChanged(obj, OperationType.UPDATE); } } /** * @see org.projectforge.framework.persistence.api.BaseDao#afterUndelete(org.projectforge.core.ExtendedBaseDO) */ @Override protected void afterUndelete(final PFUserDO obj) { super.afterUndelete(obj); for (final UserChangedListener userChangedListener : userChangedListeners) { userChangedListener.afterUserChanged(obj, OperationType.UPDATE); } } /** * @see org.projectforge.framework.persistence.api.BaseDao#afterDelete(org.projectforge.core.ExtendedBaseDO) */ @Override protected void afterDelete(final PFUserDO obj) { super.afterDelete(obj); for (final UserChangedListener userChangedListener : userChangedListeners) { userChangedListener.afterUserChanged(obj, OperationType.DELETE); } } /** * @see org.projectforge.framework.persistence.api.BaseDao#afterSaveOrModify(org.projectforge.core.ExtendedBaseDO) */ @Override protected void afterSaveOrModify(final PFUserDO obj) { if (obj.isMinorChange() == false) { getUserGroupCache().setExpired(); } } /** * @see org.projectforge.framework.persistence.api.BaseDao#hasAccess(Object, OperationType) */ @Override public boolean hasAccess(final PFUserDO user, final PFUserDO obj, final PFUserDO oldObj, final OperationType operationType, final boolean throwException) { return accessChecker.isUserMemberOfAdminGroup(user, throwException); } /** * @return false, if no admin user and the context user is not at minimum in one groups assigned to the given user or * false. Also deleted and deactivated users are only visible for admin users. * @see org.projectforge.framework.persistence.api.BaseDao#hasSelectAccess(org.projectforge.core.BaseDO, boolean) * @see AccessChecker#areUsersInSameGroup(PFUserDO, PFUserDO) */ @Override public boolean hasSelectAccess(final PFUserDO user, final PFUserDO obj, final boolean throwException) { boolean result = accessChecker.isUserMemberOfAdminGroup(user) || accessChecker.isUserMemberOfGroup(user, ProjectForgeGroup.FINANCE_GROUP, ProjectForgeGroup.CONTROLLING_GROUP); log.debug("UserDao hasSelectAccess. Check user member of admin, finance or controlling group: " + result); if (result == false && obj.hasSystemAccess() == true) { result = accessChecker.areUsersInSameGroup(user, obj); log.debug("UserDao hasSelectAccess. Caller user: " + user.getUsername() + " Check user: " + obj.getUsername() + " Check user in same group: " + result); } if (throwException == true && result == false) { throw new AccessException(user, AccessType.GROUP, OperationType.SELECT); } return result; } @Override public boolean hasSelectAccess(final PFUserDO user, final boolean throwException) { return true; } /** * @see org.projectforge.framework.persistence.api.BaseDao#hasInsertAccess(org.projectforge.framework.persistence.user.entities.PFUserDO) */ @Override public boolean hasInsertAccess(final PFUserDO user) { return accessChecker.isUserMemberOfAdminGroup(user, false); } @Override protected void onSaveOrModify(final PFUserDO obj) { obj.checkAndFixPassword(); } /** * Update user after login success. * * @param user the user */ public void updateUserAfterLoginSuccess(PFUserDO user) { PfEmgrFactory.get().runInTrans((emgr) -> { CriteriaUpdate<PFUserDO> cu = CriteriaUpdate.createUpdate(PFUserDO.class); cu.set("lastLogin", new Timestamp(new Date().getTime())).set("loginFailures", 0) .addWhere(Clauses.equal("id", user.getId())); return emgr.update(cu); }); } public void updateIncrementLoginFailure(String userName) { PfEmgrFactory.get().runInTrans((emgr) -> { CriteriaUpdate<PFUserDO> cu = CriteriaUpdate.createUpdate(PFUserDO.class); cu.setExpression("loginFailures", "loginFailures + 1").addWhere(Clauses.equal("username", userName)); return emgr.update(cu); }); } @SuppressWarnings("unchecked") public PFUserDO getUserByStayLoggedInKey(final String username, final String stayLoggedInKey) { final List<PFUserDO> list = (List<PFUserDO>) getHibernateTemplate().find( "from PFUserDO u where u.username = ? and u.stayLoggedInKey = ?", new Object[] { username, stayLoggedInKey }); PFUserDO user = null; if (list != null && list.isEmpty() == false && list.get(0) != null) { user = list.get(0); } if (user != null && user.hasSystemAccess() == false) { log.warn("Deleted/deactivated user tried to login (via stay-logged-in): " + user); return null; } return user; } /** * Does an user with the given username already exists? Works also for existing users (if username was modified). * * @param user * @return */ @SuppressWarnings("unchecked") public boolean doesUsernameAlreadyExist(final PFUserDO user) { Validate.notNull(user); List<PFUserDO> list = null; if (user.getId() == null) { // New user list = (List<PFUserDO>) getHibernateTemplate().find("from PFUserDO u where u.username = ?", user.getUsername()); } else { // user already exists. Check maybe changed username: list = (List<PFUserDO>) getHibernateTemplate().find("from PFUserDO u where u.username = ? and pk <> ?", new Object[] { user.getUsername(), user.getId() }); } if (CollectionUtils.isNotEmpty(list) == true) { return true; } return false; } /** * Get authentication key by user. ; ) * * @param userName * @param authKey * @return */ @SuppressWarnings("unchecked") public PFUserDO getUserByAuthenticationToken(final Integer userId, final String authKey) { final List<PFUserDO> list = (List<PFUserDO>) getHibernateTemplate().find( "from PFUserDO u where u.id = ? and u.authenticationToken = ?", new Object[] { userId, authKey }); PFUserDO user = null; if (list != null && list.isEmpty() == false && list.get(0) != null) { user = list.get(0); } if (user != null && user.hasSystemAccess() == false) { log.warn("Deleted user tried to login (via authentication token): " + user); return null; } return user; } /** * Returns the user's authentication token if exists (must be not blank with a size >= 10). If not, a new token key * will be generated. * * @param userId * @return */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public String getAuthenticationToken(final Integer userId) { final PFUserDO user = internalGetById(userId); if (StringUtils.isBlank(user.getAuthenticationToken()) || user.getAuthenticationToken().trim().length() < 10) { user.setAuthenticationToken(createAuthenticationToken()); log.info("Authentication token renewed for user: " + userId + " - " + user.getUsername()); } return user.getAuthenticationToken(); } /** * Renews the user's authentication token (random string sequence). */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void renewAuthenticationToken(final Integer userId) { if (ThreadLocalUserContext.getUserId().equals(userId) == false) { // Only admin users are able to renew authentication token of other users: accessChecker.checkIsLoggedInUserMemberOfAdminGroup(); } accessChecker.checkRestrictedOrDemoUser(); // Demo users are also not allowed to do this. final PFUserDO user = internalGetById(userId); user.setAuthenticationToken(createAuthenticationToken()); log.info("Authentication token renewed for user: " + userId + " - " + user.getUsername()); } private String createAuthenticationToken() { return NumberHelper.getSecureRandomUrlSaveString(AUTHENTICATION_TOKEN_LENGTH); } @SuppressWarnings("unchecked") @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public PFUserDO getInternalByName(final String username) { final List<PFUserDO> list = (List<PFUserDO>) getHibernateTemplate() .find("from PFUserDO u where u.username = ?", username); if (list != null && list.size() > 0) { return list.get(0); } return null; } /** * User can modify own setting, this method ensures that only such properties will be updated, the user's are allowed * to. * * @param user */ @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ) public void updateMyAccount(final PFUserDO user) { accessChecker.checkRestrictedOrDemoUser(); final PFUserDO contextUser = ThreadLocalUserContext.getUser(); Validate.isTrue(user.getId().equals(contextUser.getId()) == true); final PFUserDO dbUser = getHibernateTemplate().load(clazz, user.getId(), LockMode.PESSIMISTIC_WRITE); final String[] ignoreFields = { "deleted", "password", "lastLogin", "loginFailures", "username", "stayLoggedInKey", "authenticationToken", "rights" }; final ModificationStatus result = HistoryBaseDaoAdapter.wrappHistoryUpdate(dbUser, () -> copyValues(user, dbUser, ignoreFields)); if (result != ModificationStatus.NONE) { dbUser.setLastUpdate(); log.info("Object updated: " + dbUser.toString()); copyValues(user, contextUser, ignoreFields); } else { log.info("No modifications detected (no update needed): " + dbUser.toString()); } getUserGroupCache().updateUser(user); } /** * Gets history entries of super and adds all history entries of the AuftragsPositionDO childs. * * @see org.projectforge.framework.persistence.api.BaseDao#getDisplayHistoryEntries(org.projectforge.core.ExtendedBaseDO) */ @Override public List<DisplayHistoryEntry> getDisplayHistoryEntries(final PFUserDO obj) { final List<DisplayHistoryEntry> list = super.getDisplayHistoryEntries(obj); if (hasLoggedInUserHistoryAccess(obj, false) == false) { return list; } if (CollectionUtils.isNotEmpty(obj.getRights()) == true) { for (final UserRightDO right : obj.getRights()) { final List<DisplayHistoryEntry> entries = internalGetDisplayHistoryEntries(right); for (final DisplayHistoryEntry entry : entries) { final String propertyName = entry.getPropertyName(); if (propertyName != null) { entry.setPropertyName(right.getRightIdString() + ":" + entry.getPropertyName()); // Prepend number of positon. } else { entry.setPropertyName(String.valueOf(right.getRightIdString())); } } list.addAll(entries); } } Collections.sort(list, new Comparator<DisplayHistoryEntry>() { @Override public int compare(final DisplayHistoryEntry o1, final DisplayHistoryEntry o2) { return (o2.getTimestamp().compareTo(o1.getTimestamp())); } }); return list; } @Override public boolean hasHistoryAccess(final PFUserDO user, final boolean throwException) { return accessChecker.isUserMemberOfAdminGroup(user, throwException); } /** * Re-index all dependent objects only if the username, first or last name was changed. * * @see org.projectforge.framework.persistence.api.BaseDao#wantsReindexAllDependentObjects(org.projectforge.core.ExtendedBaseDO, * org.projectforge.core.ExtendedBaseDO) */ @Override protected boolean wantsReindexAllDependentObjects(final PFUserDO obj, final PFUserDO dbObj) { if (super.wantsReindexAllDependentObjects(obj, dbObj) == false) { return false; } return StringUtils.equals(obj.getUsername(), dbObj.getUsername()) == false || StringUtils.equals(obj.getFirstname(), dbObj.getFirstname()) == false || StringUtils.equals(obj.getLastname(), dbObj.getLastname()) == false; } @Override public PFUserDO newInstance() { return new PFUserDO(); } public List<PFUserDO> findByUsername(String username) { return (List<PFUserDO>) getHibernateTemplate().find("from PFUserDO u where u.username = ?", username); } }