Search in sources :

Example 1 with PrefCalendarApptVisibility

use of com.zimbra.common.account.ZAttrProvisioning.PrefCalendarApptVisibility in project zm-mailbox by Zimbra.

the class Message method processInvitesAfterCreate.

/**
 * This has to be done as a separate step, after the MailItem has been added, because of foreign key constraints on
 * the CalendarItems table.
 */
private void processInvitesAfterCreate(String method, int folderId, boolean applyToCalendar, ParsedMessage pm, List<Invite> invites) throws ServiceException {
    if (pm == null) {
        throw ServiceException.INVALID_REQUEST("null ParsedMessage while processing invite in message " + mId, null);
    }
    Account acct = getAccount();
    AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
    OperationContext octxt = getMailbox().getOperationContext();
    ProcessInvitesStatus status = new ProcessInvitesStatus(acct, pm);
    status.initAutoAddNew(octxt);
    boolean isOrganizerMethod = Invite.isOrganizerMethod(method);
    if (isOrganizerMethod && !invites.isEmpty() && status.intendedForMe) {
        // Check if the sender is allowed to invite this user.  Only do this for invite-type methods,
        // namely REQUEST/PUBLISH/CANCEL/ADD/DECLINECOUNTER.  REPLY/REFRESH/COUNTER don't undergo
        // the check because they are not organizer-to-attendee methods.
        String senderEmail;
        Account senderAcct = null;
        boolean onBehalfOf = false;
        boolean canInvite;
        AccessManager accessMgr = AccessManager.getInstance();
        if (octxt != null && octxt.getAuthenticatedUser() != null) {
            onBehalfOf = octxt.isDelegatedRequest(getMailbox());
            senderAcct = octxt.getAuthenticatedUser();
            senderEmail = senderAcct.getName();
            canInvite = accessMgr.canDo(senderAcct, acct, User.R_invite, octxt.isUsingAdminPrivileges());
        } else {
            senderEmail = pm.getSenderEmail(false);
            if (senderEmail != null) {
                senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
            }
            canInvite = accessMgr.canDo(senderEmail, acct, User.R_invite, false);
        }
        if (!canInvite) {
            Invite invite = invites.get(0);
            CalendarMailSender.handleInviteAutoDeclinedNotification(octxt, getMailbox(), acct, senderEmail, senderAcct, onBehalfOf, applyToCalendar, getId(), invite);
            String inviteSender = senderEmail != null ? senderEmail : "unknown sender";
            ZimbraLog.calendar.info("Calendar invite from %s to %s is not allowed", inviteSender, acct.getName());
            // Turn off auto-add.  We still have to run through the code below to save the invite's
            // data in message's metadata.
            status.autoAddNew = false;
        }
    }
    // Override CLASS property if preference says to mark everything as private.
    PrefCalendarApptVisibility prefClass = acct.getPrefCalendarApptVisibility();
    boolean forcePrivateClass = prefClass != null && !prefClass.equals(PrefCalendarApptVisibility.public_);
    // Ignore alarms set by organizer.
    boolean allowOrganizerAlarm = DebugConfig.calendarAllowOrganizerSpecifiedAlarms;
    if (calendarItemInfos == null) {
        calendarItemInfos = new ArrayList<CalendarItemInfo>();
    }
    // properties.
    if (invites.size() > 1) {
        boolean hasSeries = false;
        ZOrganizer seriesOrganizer = null;
        boolean seriesIsOrganizer = false;
        List<ZAttendee> seriesAttendees = null;
        ParsedDateTime seriesDtStart = null;
        // Get organizer and attendees from series VEVENT.
        for (Invite inv : invites) {
            if (!inv.hasRecurId()) {
                hasSeries = true;
                seriesOrganizer = inv.getOrganizer();
                seriesIsOrganizer = inv.isOrganizer();
                seriesAttendees = inv.getAttendees();
                seriesDtStart = inv.getStartTime();
                break;
            }
        }
        if (hasSeries) {
            for (Invite inv : invites) {
                RecurId rid = inv.getRecurId();
                if (rid != null) {
                    if (seriesOrganizer != null && !inv.hasOrganizer()) {
                        inv.setOrganizer(seriesOrganizer);
                        inv.setIsOrganizer(seriesIsOrganizer);
                        // exception instance to have no attendee.
                        if (!inv.hasOtherAttendees() && seriesAttendees != null) {
                            for (ZAttendee at : seriesAttendees) {
                                inv.addAttendee(at);
                            }
                        }
                    }
                    if (!inv.isAllDayEvent() && seriesDtStart != null) {
                        // Exchange can send invalid RECURRENCE-ID with HHMMSS set to 000000.  Detect it and fix it up
                        // by copying the time from series DTSTART.
                        ParsedDateTime ridDt = rid.getDt();
                        if (ridDt != null && ridDt.hasZeroTime() && !seriesDtStart.hasZeroTime() && ridDt.sameTimeZone(seriesDtStart)) {
                            ParsedDateTime fixedDt = seriesDtStart.cloneWithNewDate(ridDt);
                            RecurId fixedRid = new RecurId(fixedDt, rid.getRange());
                            ZimbraLog.calendar.debug("Fixed up invalid RECURRENCE-ID with zero time; before=[%s], after=[%s]", rid, fixedRid);
                            inv.setRecurId(fixedRid);
                        }
                    }
                    // Exception instance invites shouldn't point to the same MIME part in the appointment blob
                    // as the series invite.  If they do, we will lose the series attachment when a new exception
                    // instance update is received.
                    inv.setMailItemId(0);
                }
            }
        }
    }
    // used to check if any invite is non-public
    boolean publicInvites = true;
    status.calItemFolderId = invites.size() > 0 && invites.get(0).isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
    CalendarItem firstCalItem = null;
    Set<String> calUidsSeen = new HashSet<String>();
    for (Invite cur : invites) {
        if (!cur.isPublic()) {
            publicInvites = false;
        }
        // it's the correct organizer.
        if (!cur.hasOrganizer() && cur.hasOtherAttendees()) {
            String fromEmail = pm.getSenderEmail(true);
            if (fromEmail != null) {
                boolean dangerousSender = false;
                // Is sender == recipient?  If so, clear attendees.
                if (status.intendedForAddress != null) {
                    if (status.intendedForAddress.equalsIgnoreCase(fromEmail)) {
                        ZimbraLog.calendar.info("Got malformed invite without organizer.  Clearing attendees to prevent inadvertent cancels.");
                        cur.clearAttendees();
                        dangerousSender = true;
                    }
                } else if (acctMatcher.matches(fromEmail)) {
                    ZimbraLog.calendar.info("Got malformed invite without organizer.  Clearing attendees to prevent inadvertent cancels.");
                    cur.clearAttendees();
                    dangerousSender = true;
                }
                if (!dangerousSender) {
                    if (isOrganizerMethod = Invite.isOrganizerMethod(method)) {
                        // For organizer-originated methods, use email sender as default organizer.
                        ZOrganizer org = new ZOrganizer(fromEmail, null);
                        String senderEmail = pm.getSenderEmail(false);
                        if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
                            org.setSentBy(senderEmail);
                        cur.setOrganizer(org);
                        ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer.  " + "Defaulting organizer to: " + org.toString());
                    } else {
                        // For attendee-originated methods, look up organizer from appointment on calendar.
                        // If appointment is not found, fall back to the intended-for address, then finally to self.
                        ZOrganizer org = null;
                        CalendarItem ci = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
                        if (ci != null) {
                            Invite inv = ci.getInvite(cur.getRecurId());
                            if (inv == null) {
                                inv = ci.getDefaultInviteOrNull();
                            }
                            if (inv != null) {
                                org = inv.getOrganizer();
                            }
                        }
                        if (org == null) {
                            if (status.intendedForAddress != null) {
                                org = new ZOrganizer(status.intendedForAddress, null);
                            } else {
                                org = new ZOrganizer(acct.getName(), null);
                            }
                        }
                        cur.setOrganizer(org);
                        cur.setIsOrganizer(status.intendedForMe);
                        ZimbraLog.calendar.info("Got malformed reply missing organizer.  Defaulting to " + org.toString());
                    }
                }
            }
        }
        cur.setLocalOnly(false);
        status.initAddRevisionSetting(cur.getUid(), calUidsSeen);
        // other than BUSY.  And don't allow transparent meetings.  This will prevent double booking in the future.
        if (cur.isEvent() && (acct instanceof CalendarResource)) {
            cur.setFreeBusy(IcalXmlStrMap.FBTYPE_BUSY);
            cur.setTransparency(IcalXmlStrMap.TRANSP_OPAQUE);
        }
        if (forcePrivateClass) {
            cur.setClassProp(IcalXmlStrMap.CLASS_PRIVATE);
            cur.setClassPropSetByMe(true);
        }
        ICalTok methodTok = Invite.lookupMethod(method);
        // Discard alarms set by organizer.  Add a new one based on attendee's preferences.
        if (!allowOrganizerAlarm) {
            // only for non-cancel/non-declinecounter VEVENTs
            if (cur.isEvent() && isOrganizerMethod && !cur.isCancel() && !ICalTok.DECLINECOUNTER.equals(methodTok))
                Invite.setDefaultAlarm(cur, acct);
        }
        getInfoForAssociatedCalendarItem(acct, cur, method, pm, applyToCalendar, status);
        if (firstCalItem == null) {
            firstCalItem = status.calItem;
        }
    }
    if (status.updatedMetadata) {
        saveMetadata();
    }
    // Don't forward from a system account. (e.g. archiving, galsync, ham/spam)
    if (applyToCalendar && !status.isForwardedInvite && status.intendedForMe && folderId != Mailbox.ID_FOLDER_SENT && !invites.isEmpty() && !acct.isIsSystemResource()) {
        // Don't do the forwarding during redo playback.
        RedoableOp redoPlayer = octxt != null ? octxt.getPlayer() : null;
        RedoLogProvider redoProvider = RedoLogProvider.getInstance();
        boolean needToForward = redoProvider.isMaster() && (redoPlayer == null || redoProvider.getRedoLogManager().getInCrashRecovery());
        if (needToForward) {
            String[] forwardTo = null;
            if (isOrganizerMethod) {
                forwardTo = acct.getPrefCalendarForwardInvitesTo();
            } else {
                // not the users listed in the zimbraPrefCalendarForwardInvitesTo preference.
                if (firstCalItem != null) {
                    Invite invCalItem = firstCalItem.getInvite(invites.get(0).getRecurId());
                    if (invCalItem == null)
                        invCalItem = firstCalItem.getDefaultInviteOrNull();
                    if (invCalItem != null && invCalItem.isOrganizer()) {
                        ZOrganizer org = invCalItem.getOrganizer();
                        if (org.hasSentBy()) {
                            forwardTo = new String[] { org.getSentBy() };
                        }
                    }
                }
            }
            Account senderAcct = null;
            String senderEmail = pm.getSenderEmail(false);
            if (senderEmail != null)
                senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
            if (forwardTo != null && forwardTo.length > 0) {
                // recipients to receive unfiltered message
                List<String> rcptsUnfiltered = new ArrayList<String>();
                // recipients to receive message filtered to remove private data
                List<String> rcptsFiltered = new ArrayList<String>();
                Folder calFolder = null;
                try {
                    calFolder = getMailbox().getFolderById(status.calItemFolderId);
                } catch (NoSuchItemException e) {
                    ZimbraLog.mailbox.warn("No such calendar folder (" + status.calItemFolderId + ") during invite auto-forwarding");
                }
                for (String fwd : forwardTo) {
                    if (fwd != null) {
                        fwd = fwd.trim();
                    }
                    if (StringUtil.isNullOrEmpty(fwd)) {
                        continue;
                    }
                    // Prevent forwarding to self.
                    if (acctMatcher.matches(fwd))
                        continue;
                    // Don't forward back to the sender.  It's redundant and confusing.
                    Account rcptAcct = Provisioning.getInstance().get(AccountBy.name, fwd);
                    boolean rcptIsSender = false;
                    if (rcptAcct != null) {
                        if (senderAcct != null) {
                            rcptIsSender = rcptAcct.getId().equalsIgnoreCase(senderAcct.getId());
                        } else {
                            rcptIsSender = AccountUtil.addressMatchesAccount(rcptAcct, senderEmail);
                        }
                    } else {
                        if (senderAcct != null) {
                            rcptIsSender = AccountUtil.addressMatchesAccount(senderAcct, fwd);
                        } else {
                            rcptIsSender = fwd.equalsIgnoreCase(senderEmail);
                        }
                    }
                    if (rcptIsSender) {
                        ZimbraLog.calendar.info("Not auto-forwarding to " + fwd + " because it is the sender of this message");
                        continue;
                    }
                    if (publicInvites) {
                        rcptsUnfiltered.add(fwd);
                    } else {
                        boolean allowed = false;
                        if (calFolder != null && rcptAcct != null) {
                            allowed = calFolder.canAccess(ACL.RIGHT_PRIVATE, rcptAcct, false);
                        }
                        if (allowed) {
                            rcptsUnfiltered.add(fwd);
                        } else if (acct instanceof CalendarResource) {
                            // Forward filtered invite from calendar resource accounts only.  Don't forward filtered
                            // invite from regular user account because the forwardee won't be able to accept/decline
                            // due to permission error.
                            rcptsFiltered.add(fwd);
                        }
                    }
                }
                if (!rcptsUnfiltered.isEmpty() || !rcptsFiltered.isEmpty()) {
                    MimeMessage mmOrig = pm.getMimeMessage();
                    if (mmOrig != null) {
                        String origSender = pm.getSenderEmail(false);
                        String forwarder = AccountUtil.getCanonicalAddress(acct);
                        if (!rcptsUnfiltered.isEmpty()) {
                            MimeMessage mm = CalendarMailSender.createForwardedInviteMessage(mmOrig, origSender, forwarder, rcptsUnfiltered.toArray(new String[0]));
                            if (mm != null) {
                                ItemId origMsgId = new ItemId(getMailbox(), getId());
                                CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
                            }
                        }
                        if (!rcptsFiltered.isEmpty()) {
                            MimeMessage mm = CalendarMailSender.createForwardedPrivateInviteMessage(acct, acct.getLocale(), method, invites, origSender, forwarder, rcptsFiltered.toArray(new String[0]));
                            if (mm != null) {
                                ItemId origMsgId = new ItemId(getMailbox(), getId());
                                CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
                            }
                        }
                    }
                }
            }
        }
    }
}
Also used : AccessManager(com.zimbra.cs.account.AccessManager) Account(com.zimbra.cs.account.Account) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) ItemId(com.zimbra.cs.service.util.ItemId) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) RedoableOp(com.zimbra.cs.redolog.op.RedoableOp) RedoLogProvider(com.zimbra.cs.redolog.RedoLogProvider) ZMimeMessage(com.zimbra.common.zmime.ZMimeMessage) MimeMessage(javax.mail.internet.MimeMessage) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) CalendarResource(com.zimbra.cs.account.CalendarResource) HashSet(java.util.HashSet) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) PrefCalendarApptVisibility(com.zimbra.common.account.ZAttrProvisioning.PrefCalendarApptVisibility) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Aggregations

PrefCalendarApptVisibility (com.zimbra.common.account.ZAttrProvisioning.PrefCalendarApptVisibility)1 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)1 ICalTok (com.zimbra.common.calendar.ZCalendar.ICalTok)1 ZMimeMessage (com.zimbra.common.zmime.ZMimeMessage)1 AccessManager (com.zimbra.cs.account.AccessManager)1 Account (com.zimbra.cs.account.Account)1 CalendarResource (com.zimbra.cs.account.CalendarResource)1 NoSuchItemException (com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException)1 Invite (com.zimbra.cs.mailbox.calendar.Invite)1 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)1 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)1 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)1 RedoLogProvider (com.zimbra.cs.redolog.RedoLogProvider)1 RedoableOp (com.zimbra.cs.redolog.op.RedoableOp)1 ItemId (com.zimbra.cs.service.util.ItemId)1 AccountAddressMatcher (com.zimbra.cs.util.AccountUtil.AccountAddressMatcher)1 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 MimeMessage (javax.mail.internet.MimeMessage)1