/* * LinShare is an open source filesharing software, part of the LinPKI software * suite, developed by Linagora. * * Copyright (C) 2015 LINAGORA * * 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, provided you comply with the Additional Terms applicable for * LinShare software by Linagora pursuant to Section 7 of the GNU Affero General * Public License, subsections (b), (c), and (e), pursuant to which you must * notably (i) retain the display of the “LinShare™” trademark/logo at the top * of the interface window, the display of the “You are using the Open Source * and free version of LinShare™, powered by Linagora © 2009–2015. Contribute to * Linshare R&D by subscribing to an Enterprise offer!” infobox and in the * e-mails sent with the Program, (ii) retain all hypertext links between * LinShare and linshare.org, between linagora.com and Linagora, and (iii) * refrain from infringing Linagora intellectual property rights over its * trademarks and commercial brands. Other Additional Terms apply, see * <http://www.linagora.com/licenses/> for more details. * * 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 and * its applicable Additional Terms for LinShare along with this program. If not, * see <http://www.gnu.org/licenses/> for the GNU Affero General Public License * version 3 and <http://www.linagora.com/licenses/> for the Additional Terms * applicable to LinShare software. */ package org.linagora.linshare.core.service.impl; import java.util.List; import org.apache.commons.lang.Validate; import org.linagora.linshare.core.business.service.AccountQuotaBusinessService; import org.linagora.linshare.core.business.service.DocumentEntryBusinessService; import org.linagora.linshare.core.business.service.ContainerQuotaBusinessService; import org.linagora.linshare.core.domain.constants.AuditLogEntryType; import org.linagora.linshare.core.domain.constants.ContainerQuotaType; import org.linagora.linshare.core.domain.constants.LogAction; import org.linagora.linshare.core.domain.constants.Role; import org.linagora.linshare.core.domain.entities.Account; import org.linagora.linshare.core.domain.entities.AccountQuota; import org.linagora.linshare.core.domain.entities.ContainerQuota; import org.linagora.linshare.core.domain.entities.Functionality; import org.linagora.linshare.core.domain.entities.Thread; import org.linagora.linshare.core.domain.entities.ThreadLogEntry; import org.linagora.linshare.core.domain.entities.ThreadMember; import org.linagora.linshare.core.domain.entities.User; import org.linagora.linshare.core.exception.BusinessErrorCode; import org.linagora.linshare.core.exception.BusinessException; import org.linagora.linshare.core.rac.ThreadMemberResourceAccessControl; import org.linagora.linshare.core.rac.ThreadResourceAccessControl; import org.linagora.linshare.core.repository.ThreadMemberRepository; import org.linagora.linshare.core.repository.ThreadRepository; import org.linagora.linshare.core.repository.UserRepository; import org.linagora.linshare.core.service.FunctionalityReadOnlyService; import org.linagora.linshare.core.service.LogEntryService; import org.linagora.linshare.core.service.ThreadService; import org.linagora.linshare.mongo.entities.logs.ThreadAuditLogEntry; import org.linagora.linshare.mongo.entities.logs.ThreadMemberAuditLogEntry; import org.linagora.linshare.mongo.entities.mto.ThreadMemberMto; import org.linagora.linshare.mongo.entities.mto.ThreadMto; import org.linagora.linshare.mongo.repository.AuditUserMongoRepository; public class ThreadServiceImpl extends GenericServiceImpl<Account, Thread> implements ThreadService { private final ThreadRepository threadRepository; private final ThreadMemberRepository threadMemberRepository; private final DocumentEntryBusinessService documentEntryBusinessService; private final LogEntryService logEntryService; private final ThreadMemberResourceAccessControl threadMemberAC; private final UserRepository<User> userRepository; private final FunctionalityReadOnlyService functionalityReadOnlyService; private final AuditUserMongoRepository auditMongoRepository; private final AccountQuotaBusinessService accountQuotaBusinessService; private final ContainerQuotaBusinessService containerQuotaBusinessService; public ThreadServiceImpl( ThreadRepository threadRepository, ThreadMemberRepository threadMemberRepository, DocumentEntryBusinessService documentEntryBusinessService, LogEntryService logEntryService, ThreadResourceAccessControl rac, ThreadMemberResourceAccessControl threadMemberResourceAccessControl, UserRepository<User> userRepository, FunctionalityReadOnlyService functionalityReadOnlyService, AuditUserMongoRepository auditMongoRepository, AccountQuotaBusinessService accountQuotaBusinessService, ContainerQuotaBusinessService containerQuotaBusinessService) { super(rac); this.threadRepository = threadRepository; this.threadMemberRepository = threadMemberRepository; this.documentEntryBusinessService = documentEntryBusinessService; this.logEntryService = logEntryService; this.threadMemberAC = threadMemberResourceAccessControl; this.userRepository = userRepository; this.functionalityReadOnlyService = functionalityReadOnlyService; this.auditMongoRepository = auditMongoRepository; this.accountQuotaBusinessService = accountQuotaBusinessService; this.containerQuotaBusinessService = containerQuotaBusinessService; } @Override public Thread find(Account actor, Account owner, String uuid) { preChecks(actor, owner); Validate.notEmpty(uuid, "Missing thread uuid"); Thread thread = threadRepository.findByLsUuid(uuid); if (thread == null) { logger.error("Can't find thread : " + uuid); logger.error("Current actor " + actor.getAccountRepresentation() + " is looking for a misssing thread (" + uuid + ") owned by : " + owner.getAccountRepresentation()); String message = "Can not find thread with uuid : " + uuid; throw new BusinessException( BusinessErrorCode.THREAD_NOT_FOUND, message); } checkReadPermission(actor, owner, Thread.class, BusinessErrorCode.THREAD_FORBIDDEN, thread, owner); // ThreadAuditLogEntry log = new ThreadAuditLogEntry(actor, owner, LogAction.GET, AuditLogEntryType.THREAD, // new ThreadMto(thread, false)); // auditMongoRepository.insert(log); return thread; } @Override public Thread findByLsUuidUnprotected(String uuid) { Thread thread = threadRepository.findByLsUuid(uuid); if (thread == null) { logger.error("Can't find thread : " + uuid); } return thread; } @Override public List<Thread> findAll(Account actor, Account owner) { checkListPermission(actor, owner, Thread.class, BusinessErrorCode.THREAD_FORBIDDEN, null); return threadRepository.findAll(); } @Override public Thread create(Account actor, Account owner, String name) throws BusinessException { Functionality threadFunc = functionalityReadOnlyService.getWorkGroupFunctionality(owner.getDomain()); Functionality threadCreation = functionalityReadOnlyService.getWorkGroupCreationRight(owner.getDomain()); if (!threadFunc.getActivationPolicy().getStatus() || !threadCreation.getActivationPolicy().getStatus()) { throw new BusinessException(BusinessErrorCode.THREAD_FORBIDDEN, "Functionality forbideen."); } checkCreatePermission(actor, owner, Thread.class, BusinessErrorCode.THREAD_FORBIDDEN, null); Thread thread = null; ThreadMember member = null; logger.debug("User " + owner.getAccountRepresentation() + " trying to create new thread named " + name); thread = new Thread(owner.getDomain(), owner, name); threadRepository.create(thread); createQuotaThread(thread); logEntryService.create(new ThreadLogEntry(owner, thread, LogAction.THREAD_CREATE, "Creation of a new thread.")); // creator = first member = default admin member = new ThreadMember(true, true, (User) owner, thread); thread.getMyMembers().add(member); thread = threadRepository.update(thread); logEntryService.create(new ThreadLogEntry(owner, member, LogAction.THREAD_ADD_MEMBER, "Creating the first member of the newly created thread.")); ThreadAuditLogEntry log = new ThreadAuditLogEntry(actor, owner, LogAction.CREATE, AuditLogEntryType.THREAD, new ThreadMto(thread, false)); auditMongoRepository.insert(log); return thread; } @Override public ThreadMember getThreadMemberById(long id) throws BusinessException { return threadMemberRepository.findById(id); } @Override public ThreadMember getMemberFromUser(Thread thread, User user) throws BusinessException { return threadMemberRepository.findUserThreadMember(thread, user); } @Override public List<ThreadMember> findAllThreadMembers(Account actor, User owner, Thread thread) throws BusinessException { threadMemberAC.checkListPermission(actor, owner, ThreadMember.class, BusinessErrorCode.THREAD_MEMBER_FORBIDDEN, null, thread); return threadMemberRepository.findAllThreadMembers(thread); } @Override public List<ThreadMember> findAllInconsistentMembers(Account actor, User owner, Thread thread) throws BusinessException { threadMemberAC.checkListPermission(actor, owner, ThreadMember.class, BusinessErrorCode.THREAD_MEMBER_FORBIDDEN, null, thread); return threadMemberRepository.findAllInconsistentThreadMembers(thread); } @Override public List<Thread> findAllWhereMember(User user) { return threadRepository.findAllWhereMember(user); } @Override public List<Thread> findAllWhereAdmin(User user) { return threadRepository.findAllWhereAdmin(user); } @Override public List<Thread> findAllWhereCanUpload(User user) { return threadRepository.findAllWhereCanUpload(user); } @Override public boolean hasAnyWhereAdmin(User user) { return threadMemberRepository.isUserAdminOfAny(user); } @Override public boolean isUserAdmin(User user, Thread thread) { return threadMemberRepository.isUserAdmin(user, thread); } @Override public long countMembers(Thread thread) { return threadMemberRepository.count(thread); } @Override public long countEntries(Thread thread) { return documentEntryBusinessService.countThreadEntries(thread); } @Override public ThreadMember addMember(Account actor, Account owner, Thread thread, User user, boolean admin, boolean canUpload) throws BusinessException { ThreadMember member = new ThreadMember(canUpload, admin, user, thread); threadMemberAC.checkCreatePermission(actor, owner, ThreadMember.class, BusinessErrorCode.THREAD_MEMBER_FORBIDDEN, member, thread); if (getMemberFromUser(thread, user) != null) { logger.warn("The current " + user.getAccountRepresentation() + " user is already member of the thread : " + thread.getAccountRepresentation()); throw new BusinessException( "You are not authorized to add member to this thread. Already exists."); } thread.getMyMembers().add(member); threadRepository.update(thread); logEntryService.create(new ThreadLogEntry(owner, member, LogAction.THREAD_ADD_MEMBER, "Adding a new member to a thread : " + member.getUser().getAccountRepresentation())); ThreadMemberAuditLogEntry log = new ThreadMemberAuditLogEntry(actor, owner, LogAction.CREATE, AuditLogEntryType.THREAD_MEMBER, member); auditMongoRepository.insert(log); return member; } @Override public ThreadMember updateMember(Account actor, Account owner, String threadUuid, String userUuid, boolean admin, boolean canUpload) throws BusinessException { Thread thread = find(actor, owner, threadUuid); User user = getUserMember(userUuid); ThreadMember member = getMemberFromUser(thread, user); threadMemberAC.checkUpdatePermission(actor, owner, ThreadMember.class, BusinessErrorCode.THREAD_MEMBER_FORBIDDEN, member); ThreadMemberAuditLogEntry log = new ThreadMemberAuditLogEntry(actor, owner, LogAction.UPDATE, AuditLogEntryType.THREAD_MEMBER, member); member.setAdmin(admin); member.setCanUpload(canUpload); ThreadMember res = threadMemberRepository.update(member); log.setMemberUpdated(new ThreadMemberMto(res)); auditMongoRepository.insert(log); return res; } @Override public ThreadMember deleteMember(Account actor, Account owner, String threadUuid, String userUuid) throws BusinessException { preChecks(actor, owner); Validate.notEmpty(userUuid); Validate.notEmpty(threadUuid); Thread thread = find(actor, owner, threadUuid); User user = getUserMember(userUuid); ThreadMember member = getMemberFromUser(thread, user); threadMemberAC.checkDeletePermission(actor, owner, ThreadMember.class, BusinessErrorCode.THREAD_MEMBER_FORBIDDEN, member); thread.getMyMembers().remove(member); threadRepository.update(thread); threadMemberRepository.delete(member); logEntryService.create(new ThreadLogEntry(owner, member, LogAction.THREAD_REMOVE_MEMBER, "Deleting a member in a thread.")); ThreadMemberAuditLogEntry log = new ThreadMemberAuditLogEntry(actor, owner, LogAction.DELETE, AuditLogEntryType.THREAD_MEMBER, member); auditMongoRepository.insert(log); return member; } private User getUserMember(String userUuid) { User user = userRepository.findByLsUuid(userUuid); if (user == null) { user = userRepository.findDeleted(userUuid); if (user == null) { throw new BusinessException(BusinessErrorCode.USER_NOT_FOUND, "Can not find user with uuid : " + userUuid); } else { logger.info("The member with uuid " + userUuid + " you are trying to delete is already deleted"); } } return user; } @Override public void deleteAllMembers(Account actor, Thread thread) throws BusinessException { // permission check checkUserIsAdmin(actor, thread); Object[] myMembers = thread.getMyMembers().toArray(); for (Object threadMember : myMembers) { thread.getMyMembers().remove(threadMember); threadRepository.update(thread); threadMemberRepository.delete((ThreadMember) threadMember); } logEntryService.create(new ThreadLogEntry(actor, thread, LogAction.THREAD_REMOVE_MEMBER, "Deleting all members in a thread.")); } @Override public void deleteAllUserMemberships(Account actor, User user) throws BusinessException { List<ThreadMember> memberships = threadMemberRepository .findAllUserMemberships(user); for (ThreadMember threadMember : memberships) { deleteMember(actor, actor, threadMember.getThread().getLsUuid(), threadMember.getUser().getLsUuid()); } } @Override public void deleteThread(User actor, Account owner, Thread thread) throws BusinessException { checkDeletePermission(actor, owner, Thread.class, BusinessErrorCode.THREAD_FORBIDDEN, thread); ThreadLogEntry log = new ThreadLogEntry(actor, thread, LogAction.THREAD_DELETE, "Deleting a thread."); // Delete all entries documentEntryBusinessService.deleteSetThreadEntry(thread.getEntries()); thread.setEntries(null); threadRepository.update(thread); // Deleting members this.deleteAllMembers(actor, thread); // Deleting the thread threadRepository.delete(thread); ThreadAuditLogEntry threadAuditLog = new ThreadAuditLogEntry(actor, owner, LogAction.DELETE, AuditLogEntryType.THREAD, new ThreadMto(thread, true)); auditMongoRepository.insert(threadAuditLog); logEntryService.create(log); } @Override public Thread update(User actor, Account owner, String threadUuid, String threadName) throws BusinessException { Thread thread = find(actor, owner, threadUuid); checkUpdatePermission(actor, owner, Thread.class, BusinessErrorCode.THREAD_FORBIDDEN, thread); ThreadAuditLogEntry log = new ThreadAuditLogEntry(actor, owner, LogAction.UPDATE, AuditLogEntryType.THREAD, new ThreadMto(thread, false)); String oldname = thread.getName(); thread.setName(threadName); Thread update = threadRepository.update(thread); logEntryService.create(new ThreadLogEntry(actor, thread, LogAction.THREAD_RENAME, "Renamed thread from " + oldname + " to " + threadName)); log.setResourceUpdated(new ThreadMto(update, false)); auditMongoRepository.insert(log); return update; } @Override public List<Thread> findLatestWhereMember(User actor, int limit) { return threadRepository.findLatestWhereMember(actor, limit); } @Override public List<Thread> searchByName(User actor, String pattern) { return threadRepository.searchByName(actor, pattern); } @Override public List<Thread> searchByMembers(User actor, String pattern) { return threadRepository.searchAmongMembers(actor, pattern); } private void createQuotaThread(Thread thread) throws BusinessException { Validate.notNull(thread, "Thread must be set."); ContainerQuota containerQuota = containerQuotaBusinessService.find(thread.getDomain(), ContainerQuotaType.WORK_GROUP); if (containerQuota == null) { throw new BusinessException("Missing container quota entity for current work_group"); } AccountQuota threadQuota = new AccountQuota( thread.getDomain(), thread.getDomain().getParentDomain(), thread, containerQuota); accountQuotaBusinessService.create(threadQuota); } /* *********************************************************** * Helpers ************************************************************ */ /** * Check if actor is admin of the thread and so has the right to perform any action. * Throw a BusinessException if the actor isn't authorized to modify the thread. */ private void checkUserIsAdmin(Account actor, Thread thread) throws BusinessException { if (actor.getRole().equals(Role.SUPERADMIN) || actor.getRole().equals(Role.SYSTEM)) { return; // superadmin or system accounts have all rights } if (!isUserAdmin((User) actor, thread)) { logger.error("Actor: " + actor.getAccountRepresentation() + " isn't admin of the Thread: " + thread.getAccountRepresentation()); throw new BusinessException(BusinessErrorCode.FORBIDDEN, "you are not authorized to perform this action on this thread."); } } }