/* * 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.io.InputStream; import java.util.Calendar; import java.util.List; import java.util.Set; import org.apache.commons.lang.Validate; import org.linagora.linshare.core.business.service.DocumentEntryBusinessService; import org.linagora.linshare.core.business.service.ShareEntryBusinessService; import org.linagora.linshare.core.domain.constants.AuditLogEntryType; import org.linagora.linshare.core.domain.constants.LogAction; import org.linagora.linshare.core.domain.constants.LogActionCause; import org.linagora.linshare.core.domain.entities.Account; import org.linagora.linshare.core.domain.entities.DocumentEntry; import org.linagora.linshare.core.domain.entities.Guest; import org.linagora.linshare.core.domain.entities.RecipientFavourite; import org.linagora.linshare.core.domain.entities.ShareEntry; import org.linagora.linshare.core.domain.entities.ShareEntryGroup; import org.linagora.linshare.core.domain.entities.User; import org.linagora.linshare.core.domain.objects.MailContainer; import org.linagora.linshare.core.domain.objects.MailContainerWithRecipient; import org.linagora.linshare.core.domain.objects.ShareContainer; import org.linagora.linshare.core.domain.objects.TimeUnitValueFunctionality; import org.linagora.linshare.core.exception.BusinessErrorCode; import org.linagora.linshare.core.exception.BusinessException; import org.linagora.linshare.core.rac.ShareEntryResourceAccessControl; import org.linagora.linshare.core.repository.FavouriteRepository; import org.linagora.linshare.core.repository.GuestRepository; import org.linagora.linshare.core.service.FunctionalityReadOnlyService; import org.linagora.linshare.core.service.LogEntryService; import org.linagora.linshare.core.service.MailBuildingService; import org.linagora.linshare.core.service.NotifierService; import org.linagora.linshare.core.service.ShareEntryService; import org.linagora.linshare.mongo.entities.EventNotification; import org.linagora.linshare.mongo.entities.logs.DocumentEntryAuditLogEntry; import org.linagora.linshare.mongo.entities.logs.ShareEntryAuditLogEntry; import org.linagora.linshare.mongo.entities.mto.ShareEntryMto; import com.google.common.collect.Sets; public class ShareEntryServiceImpl extends GenericEntryServiceImpl<Account, ShareEntry> implements ShareEntryService { private final GuestRepository guestRepository; private final FunctionalityReadOnlyService functionalityService; private final ShareEntryBusinessService shareEntryBusinessService; private final LogEntryService logEntryService; private final DocumentEntryBusinessService documentEntryBusinessService; private final NotifierService notifierService; private final MailBuildingService mailBuildingService; private final FavouriteRepository<String, User, RecipientFavourite> recipientFavouriteRepository; public ShareEntryServiceImpl( GuestRepository guestRepository, FunctionalityReadOnlyService functionalityService, ShareEntryBusinessService shareEntryBusinessService, LogEntryService logEntryService, DocumentEntryBusinessService documentEntryBusinessService, NotifierService notifierService, MailBuildingService mailBuildingService, FavouriteRepository<String, User, RecipientFavourite> recipientFavouriteRepository, ShareEntryResourceAccessControl rac) { super(rac); this.guestRepository = guestRepository; this.functionalityService = functionalityService; this.shareEntryBusinessService = shareEntryBusinessService; this.logEntryService = logEntryService; this.documentEntryBusinessService = documentEntryBusinessService; this.notifierService = notifierService; this.mailBuildingService = mailBuildingService; this.recipientFavouriteRepository = recipientFavouriteRepository; } @Override public ShareEntry find(Account actor, Account owner, String uuid) throws BusinessException { preChecks(actor, owner); Validate.notEmpty(uuid, "Missing share entry uuid"); ShareEntry entry = shareEntryBusinessService.find(uuid); if (entry == null) { logger.error("Current actor " + actor.getAccountRepresentation() + " is looking for a misssing share entry (" + uuid + ") owned by : " + owner.getAccountRepresentation()); String message = "Can not find share entry with uuid : " + uuid; throw new BusinessException( BusinessErrorCode.SHARE_ENTRY_NOT_FOUND, message); } checkReadPermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, null); return entry; } /** * This method delete a share without specifying LogActionCause. TODO Should be removed. * @param actor * @param owner * @param uuid * @throws BusinessException */ @Deprecated @Override public void delete(Account actor, Account owner, String uuid) throws BusinessException { this.delete(actor, owner, uuid, null); } @Override public void delete(Account actor, Account owner, String uuid, LogActionCause cause) throws BusinessException { Validate.notEmpty(uuid, "Missing share entry uuid"); ShareEntry share = find(actor, owner, uuid); checkDeletePermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, share); logger.info("Share deleted : " + share.getUuid()); shareEntryBusinessService.delete(share); ShareEntryAuditLogEntry log = new ShareEntryAuditLogEntry(actor, owner, LogAction.DELETE, share, AuditLogEntryType.SHARE_ENTRY); if (cause != null) { log.setCause(cause); } if (share.getRecipient().equals(owner)) { // If the modified account (aka owner parameter) is the recipient of this share. // We does not need to send him a notification. // But we need to warn the sender String senderUuid = share.getEntryOwner().getLsUuid(); log.addRelatedAccounts(senderUuid); EventNotification event = new EventNotification(log, senderUuid); logEntryService.insert(log, event); } else { // The sender is deleting the current share, we need to warn the recipient. String recipientUuid = share.getRecipient().getLsUuid(); log.addRelatedAccounts(recipientUuid); EventNotification event = new EventNotification(log, recipientUuid); logEntryService.insert(log, event); MailContainerWithRecipient mail = mailBuildingService .buildSharedDocDeleted(share.getRecipient(), share); notifierService.sendNotification(mail); } } @Override public DocumentEntry copy(Account actor, Account owner, String shareUuid) throws BusinessException { Validate.notEmpty(shareUuid, "Missing share entry uuid"); // step1 : find the resource, and it does the preChecks(actor, owner); ShareEntry share = find(actor, owner, shareUuid); checkDownloadPermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, share); /* * This check already exists in DocumentEntry rac, but we do it it to avoid to go deeper in this method (performance). */ if (!((User) owner).getCanUpload()) { throw new BusinessException(BusinessErrorCode.NO_UPLOAD_RIGHTS_FOR_ACTOR, "Actor do not have upload rights."); } // Check if we have the right to download the specified document entry DocumentEntry documentEntry = null; // step2 : copy the resource Calendar expiryTime = functionalityService.getDefaultFileExpiryTime(owner.getDomain()); documentEntry = documentEntryBusinessService.copyFromShareEntry(owner, share, expiryTime); // step3 : log the document creation DocumentEntryAuditLogEntry docLog = new DocumentEntryAuditLogEntry(actor, owner, documentEntry, LogAction.CREATE); docLog.setCause(LogActionCause.COPY); docLog.setFromResourceUuid(shareUuid); // step4 : remove the share logger.info("delete share : " + share.getUuid()); // step 5 : notification if (share.getDownloaded() < 1) { MailContainerWithRecipient mail = mailBuildingService .buildRegisteredDownload(share); notifierService.sendNotification(mail); } // The share is now useless. We can delete it. shareEntryBusinessService.delete(share); // step6 : log the share deletion ShareEntryAuditLogEntry shareLog = new ShareEntryAuditLogEntry(actor, owner, LogAction.DELETE, share, AuditLogEntryType.SHARE_ENTRY); String senderUuid = share.getEntryOwner().getLsUuid(); shareLog.addRelatedAccounts(senderUuid); shareLog.setCause(LogActionCause.COPY); // step create an event to notify the sender of share deletion. EventNotification event = new EventNotification(shareLog, senderUuid); logEntryService.insert(docLog); logEntryService.insert(shareLog, event); return documentEntry; } @Override public ShareEntry update(Account actor, Account owner, ShareEntry dto) throws BusinessException { Validate.notNull(dto, "Missing share entry"); String uuid = dto.getUuid(); Validate.notEmpty(uuid, "Missing share entry uuid"); ShareEntry share = find(actor, owner, uuid); /* * Actually the owner have the right to update his own shareEntry. Is it * really useful ? */ checkUpdatePermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, share); ShareEntryAuditLogEntry log = new ShareEntryAuditLogEntry(actor, owner, LogAction.UPDATE, share, AuditLogEntryType.SHARE_ENTRY); share.setComment(dto.getComment()); share = shareEntryBusinessService.update(share); log.setResourceUpdated(new ShareEntryMto(share)); logEntryService.insert(log); return share; } @Override public InputStream getThumbnailStream(Account actor, Account owner, String uuid) throws BusinessException { Validate.notEmpty(uuid, "Missing share entry uuid"); ShareEntry share = find(actor, owner, uuid); checkThumbNailDownloadPermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, share); return documentEntryBusinessService.getDocumentThumbnailStream(share .getDocumentEntry()); } @Override public InputStream getStream(Account actor, Account owner, String uuid) throws BusinessException { Validate.notEmpty(uuid, "Missing share entry uuid"); ShareEntry share = find(actor, owner, uuid); checkDownloadPermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, share); if (share.getDownloaded() <= 0) { MailContainerWithRecipient mail = mailBuildingService .buildRegisteredDownload(share); notifierService.sendNotification(mail); } share = shareEntryBusinessService.updateDownloadCounter(share.getUuid()); ShareEntryAuditLogEntry log = new ShareEntryAuditLogEntry(actor, owner, LogAction.DOWNLOAD, share, AuditLogEntryType.SHARE_ENTRY); String senderUuid = share.getEntryOwner().getLsUuid(); log.addRelatedAccounts(senderUuid); EventNotification event = new EventNotification(log, senderUuid); logEntryService.insert(log, event); return documentEntryBusinessService.getDocumentStream(share .getDocumentEntry()); } @Override public List<ShareEntry> findAllMyRecievedShareEntries(Account actor, Account owner) { preChecks(actor, owner); checkListPermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, null); return shareEntryBusinessService.findAllMyRecievedShareEntries((User) owner); } @Override public Set<ShareEntry> create(Account actor, User owner, ShareContainer sc, ShareEntryGroup shareEntryGroup) { preChecks(actor, owner); Validate.notNull(sc); checkCreatePermission(actor, owner, ShareEntry.class, BusinessErrorCode.SHARE_ENTRY_FORBIDDEN, null); Set<ShareEntry> entries = Sets.newHashSet(); for (User recipient : sc.getShareRecipients()) { MailContainer mailContainer = new MailContainer( recipient.getExternalMailLocale(), sc.getMessage(), sc.getSubject()); Set<ShareEntry> shares = Sets.newHashSet(); for (DocumentEntry documentEntry : sc.getDocuments()) { ShareEntry createShare = shareEntryBusinessService.create( documentEntry, owner, recipient, sc.getExpiryCalendar(), shareEntryGroup, sc.getSharingNote()); updateGuestExpiryDate(recipient, (User) recipient.getOwner()); shares.add(createShare); recipientFavouriteRepository.incAndCreate(owner, recipient.getMail()); ShareEntryAuditLogEntry log = new ShareEntryAuditLogEntry(actor, owner, LogAction.CREATE, createShare, AuditLogEntryType.SHARE_ENTRY); String recipientUuid = recipient.getLsUuid(); log.addRelatedAccounts(recipientUuid); sc.addLog(log); sc.addEvent(new EventNotification(log, recipientUuid)); } entries.addAll(shares); MailContainerWithRecipient mail = null; if (sc.isEncrypted()) { mail = mailBuildingService.buildNewSharingCyphered(owner, mailContainer, recipient, shares); } else { mail = mailBuildingService.buildNewSharing(owner, mailContainer, recipient, shares); } sc.addMailContainer(mail); } // if there is no shares, ie anonymous shares only, there is no logs neither events. if (!sc.getLogs().isEmpty()) { // logs all logs and events in share container, create them and reset lists. logEntryService.insert(sc.getLogs(), sc.getEvents()); sc.getLogs().clear(); sc.getEvents().clear(); } return entries; } private void updateGuestExpiryDate(User recipient, User recipientOwner) { // update guest account expiry date if (recipient.isGuest()) { // get new guest expiry date Calendar guestExpiryDate = Calendar.getInstance(); TimeUnitValueFunctionality guestFunctionality = functionalityService .getGuestsExpiration(recipientOwner.getDomain()); guestExpiryDate.add(guestFunctionality.toCalendarValue(), guestFunctionality.getValue()); Guest guest = guestRepository.findByMail(recipient.getLogin()); guest.setExpirationDate(guestExpiryDate.getTime()); try { guestRepository.update(guest); } catch (IllegalArgumentException e) { logger.error("Can't update expiration date of guest : " + guest.getAccountRepresentation() + ":" + e.getMessage()); } catch (BusinessException e) { logger.error("Can't update expiration date of guest : " + guest.getAccountRepresentation() + ":" + e.getMessage()); } } } @Override public List<String> findAllExpiredEntries(Account actor, Account owner) { preChecks(actor, owner); if (!actor.hasAllRights()) { throw new BusinessException(BusinessErrorCode.FORBIDDEN, "You do not have the right to use this method."); } return shareEntryBusinessService.findAllExpiredEntries(); } }