Search in sources :

Example 11 with ZAttendee

use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.

the class TestCalendarMailSender method testAllPrefCalendarAllowedTargetsForInviteDeniedAutoReply.

public void testAllPrefCalendarAllowedTargetsForInviteDeniedAutoReply() throws Exception {
    Account organizerAccount = TestUtil.createAccount(ORGANIZERACCT);
    Account senderAccount = organizerAccount;
    Account attendeeAccount = TestUtil.createAccount(ATTENDEEACCT);
    Mailbox mbox = TestUtil.getMailbox(ATTENDEEACCT);
    String senderEmail = senderAccount.getName();
    Map<String, Object> prefs = Maps.newHashMap();
    prefs.put(Provisioning.A_zimbraPrefCalendarSendInviteDeniedAutoReply, ProvisioningConstants.TRUE);
    prefs.put(Provisioning.A_zimbraPrefCalendarAllowedTargetsForInviteDeniedAutoReply, "all");
    Provisioning.getInstance().modifyAttrs(attendeeAccount, prefs, true, null);
    ZAttendee matchingAttendee = new ZAttendee(attendeeAccount.getName(), attendeeAccount.getCn(), /* cn */
    senderAccount.getName(), /* sentBy */
    null, /* dir */
    null, /* language */
    IcalXmlStrMap.CUTYPE_INDIVIDUAL, /* cutype */
    null, /* role */
    IcalXmlStrMap.PARTSTAT_NEEDS_ACTION, /* ptst */
    true, /* rsvp */
    null, /* member */
    null, /* delegatedTo */
    null, /* delegatedFrom */
    null);
    boolean allowed;
    allowed = CalendarMailSender.allowInviteAutoDeclinedNotification(mbox, attendeeAccount, senderEmail, senderAccount, true, /* applyToCalendar */
    matchingAttendee);
    assertTrue(String.format("Should be allowed to send auto-decline because %s=true & %s=all", Provisioning.A_zimbraPrefCalendarSendInviteDeniedAutoReply, Provisioning.A_zimbraPrefCalendarAllowedTargetsForInviteDeniedAutoReply), allowed);
    Provisioning prov = Provisioning.getInstance();
    prov.createDomain(DIFFDOMAIN, new HashMap<String, Object>());
    senderAccount = prov.createAccount(DIFFDOMAINACCT, "test123", null);
    allowed = CalendarMailSender.allowInviteAutoDeclinedNotification(mbox, attendeeAccount, senderEmail, senderAccount, true, /* applyToCalendar */
    matchingAttendee);
    assertTrue(String.format("Should be allowed to send auto-decline to diff domain because %s=true & %s=all", Provisioning.A_zimbraPrefCalendarSendInviteDeniedAutoReply, Provisioning.A_zimbraPrefCalendarAllowedTargetsForInviteDeniedAutoReply), allowed);
    senderAccount = null;
    allowed = CalendarMailSender.allowInviteAutoDeclinedNotification(mbox, attendeeAccount, senderEmail, senderAccount, true, /* applyToCalendar */
    matchingAttendee);
    assertTrue(String.format("Should be allowed to send auto-decline to non-internal because %s=true & %s=all", Provisioning.A_zimbraPrefCalendarSendInviteDeniedAutoReply, Provisioning.A_zimbraPrefCalendarAllowedTargetsForInviteDeniedAutoReply), allowed);
    senderEmail = "fred@unknown.domain";
    allowed = CalendarMailSender.allowInviteAutoDeclinedNotification(mbox, attendeeAccount, senderEmail, senderAccount, true, /* applyToCalendar */
    matchingAttendee);
    assertTrue(String.format("Should be allowed to send auto-decline to external user because %s=true & %s=all", Provisioning.A_zimbraPrefCalendarSendInviteDeniedAutoReply, Provisioning.A_zimbraPrefCalendarAllowedTargetsForInviteDeniedAutoReply), allowed);
}
Also used : Account(com.zimbra.cs.account.Account) Mailbox(com.zimbra.cs.mailbox.Mailbox) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Provisioning(com.zimbra.cs.account.Provisioning)

Example 12 with ZAttendee

use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.

the class FullInstanceData method encodeMetadata.

Metadata encodeMetadata() {
    Metadata meta = super.encodeMetadata();
    meta.put(FN_IS_FULL_INSTANCE, true);
    meta.put(FN_INVID, mInvId);
    meta.put(FN_COMPNUM, mCompNum);
    meta.put(FN_RECURRENCE_ID, mRecurrenceId);
    meta.put(FN_SEQUENCE, mSequence);
    meta.put(FN_DTSTAMP, mDtStamp);
    if (mOrganizer != null)
        meta.put(FN_ORGANIZER, mOrganizer.encodeMetadata());
    if (mIsOrganizer != null)
        meta.put(FN_IS_ORGANIZER, mIsOrganizer.booleanValue());
    if (mAttendees != null) {
        meta.put(FN_NUM_ATTENDEES, mAttendees.size());
        int i = 0;
        for (ZAttendee at : mAttendees) {
            meta.put(FN_ATTENDEE + i, at.encodeAsMetadata());
            i++;
        }
    }
    if (mHasAlarm != null)
        meta.put(FN_HAS_ALARM, mHasAlarm.booleanValue());
    if (mHasAttachment != null)
        meta.put(FN_HAS_ATTACHMENT, mHasAttachment.booleanValue());
    if (mDraft != null)
        meta.put(FN_DRAFT, mDraft.booleanValue());
    if (mNeverSent != null)
        meta.put(FN_NEVER_SENT, mNeverSent.booleanValue());
    meta.put(FN_SUMMARY, mSummary);
    meta.put(FN_LOCATION, mLocation);
    meta.put(FN_FRAGMENT, mFragment);
    if (mDescInMeta != null)
        meta.put(FN_DESC_IN_META, mDescInMeta.booleanValue());
    meta.put(FN_DESC, mDesc);
    meta.put(FN_DESC_HTML, mDescHtml);
    if (mIsAllDay != null)
        meta.put(FN_IS_ALLDAY, mIsAllDay.booleanValue());
    meta.put(FN_STATUS, mStatus);
    meta.put(FN_PRIORITY, mPriority);
    meta.put(FN_CLASS, mClassProp);
    meta.put(FN_FREEBUSY, mFreeBusyIntended);
    meta.put(FN_TRANSPARENCY, mTransparency);
    if (mCategories != null) {
        int numCat = mCategories.size();
        if (numCat > 0) {
            meta.put(FN_NUM_CATEGORIES, numCat);
            int i = 0;
            for (String cat : mCategories) {
                meta.put(FN_CATEGORY + i, cat);
                i++;
            }
        }
    }
    if (mGeo != null) {
        meta.put(FN_GEO, com.zimbra.cs.mailbox.calendar.Util.encodeMetadata(mGeo));
    }
    return meta;
}
Also used : ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Metadata(com.zimbra.cs.mailbox.Metadata)

Example 13 with ZAttendee

use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.

the class ModifyCalendarItem method modifyCalendarItem.

private Element modifyCalendarItem(ZimbraSoapContext zsc, OperationContext octxt, Element request, Account acct, Mailbox mbox, int folderId, CalendarItem calItem, Invite inv, Invite seriesInv, Element response, boolean isInterMboxMove, MailSendQueue sendQueue) throws ServiceException {
    // <M>
    Element msgElem = request.getElement(MailConstants.E_MSG);
    ModifyCalendarItemParser parser = new ModifyCalendarItemParser(inv, seriesInv);
    CalSendData dat = handleMsgElement(zsc, octxt, msgElem, acct, mbox, parser);
    dat.mDontNotifyAttendees = isInterMboxMove;
    if (!dat.mInvite.hasRecurId())
        ZimbraLog.calendar.info("<ModifyCalendarItem> id=%d, folderId=%d, subject=\"%s\", UID=%s", calItem.getId(), folderId, dat.mInvite.isPublic() ? dat.mInvite.getName() : "(private)", dat.mInvite.getUid());
    else
        ZimbraLog.calendar.info("<ModifyCalendarItem> id=%d, folderId=%d, subject=\"%s\", UID=%s, recurId=%s", calItem.getId(), folderId, dat.mInvite.isPublic() ? dat.mInvite.getName() : "(private)", dat.mInvite.getUid(), dat.mInvite.getRecurId().getDtZ());
    boolean hasRecipients;
    try {
        Address[] rcpts = dat.mMm.getAllRecipients();
        hasRecipients = rcpts != null && rcpts.length > 0;
    } catch (MessagingException e) {
        throw ServiceException.FAILURE("Checking recipients of outgoing msg ", e);
    }
    // If we are sending this to other people, then we MUST be the organizer!
    if (!dat.mInvite.isOrganizer() && hasRecipients)
        throw MailServiceException.MUST_BE_ORGANIZER("ModifyCalendarItem");
    if (!dat.mInvite.isOrganizer()) {
        // neverSent is always false for attendee users.
        dat.mInvite.setNeverSent(false);
    } else if (!dat.mInvite.hasOtherAttendees()) {
        // neverSent is always false for appointments without attendees.
        dat.mInvite.setNeverSent(false);
    } else if (hasRecipients) {
        // neverSent is set to false when attendees are notified.
        dat.mInvite.setNeverSent(false);
    } else {
        // This is the case of organizer saving an invite with attendees, but without sending the notification.
        assert (dat.mInvite.hasOtherAttendees() && !hasRecipients);
        if (!inv.hasOtherAttendees()) {
            // Special case of going from a personal appointment (no attendees) to a draft appointment with
            // attendees.  neverSent was false for being a personal appointment, so we need to explicitly set it to true.
            // This case is essentially identical to creating a new appointment with attendees without notification.
            dat.mInvite.setNeverSent(true);
        } else {
            // Set neverSent to false, but only if it wasn't already set to true before.
            // !inv.isNeverSent() ? false : true ==> inv.isNeverSent()
            dat.mInvite.setNeverSent(inv.isNeverSent());
        }
    }
    boolean echo = request.getAttributeBool(MailConstants.A_CAL_ECHO, false);
    ItemIdFormatter ifmt = new ItemIdFormatter(zsc);
    int maxSize = (int) request.getAttributeLong(MailConstants.A_MAX_INLINED_LENGTH, 0);
    boolean wantHTML = request.getAttributeBool(MailConstants.A_WANT_HTML, false);
    boolean neuter = request.getAttributeBool(MailConstants.A_NEUTER, true);
    boolean forceSend = request.getAttributeBool(MailConstants.A_CAL_FORCESEND, true);
    if (inv.isOrganizer()) {
        // Notify removed attendees before making any changes to the appointment.
        List<ZAttendee> atsCanceled = parser.getAttendeesCanceled();
        if (!inv.isNeverSent()) {
            // No need to notify for a draft appointment.
            if (!atsCanceled.isEmpty()) {
                notifyRemovedAttendees(zsc, octxt, acct, mbox, inv.getCalendarItem(), inv, atsCanceled, sendQueue);
            }
        }
        List<ZAttendee> atsAdded = parser.getAttendeesAdded();
        // Figure out if we're notifying all attendees.  Must do this before clearing recipients from dat.mMm.
        boolean notifyAllAttendees = isNotifyingAll(dat.mMm, atsAdded);
        // If notifying all the attendees update the last sequence number otherwise retain the existing value.
        if (notifyAllAttendees) {
            dat.mInvite.setLastFullSeqNo(dat.mInvite.getSeqNo());
        } else {
            dat.mInvite.setLastFullSeqNo(inv.getLastFullSeqNo());
        }
        if (inv.isRecurrence()) {
            // Clear to/cc/bcc from the MimeMessage, so that the sendCalendarMessage call only updates the organizer's
            // own appointment without notifying any attendees.  Notifications will be sent later,
            removeAllRecipients(dat.mMm);
            // (unless they are added as new series attendees)
            if (!acct.isCalendarKeepExceptionsOnSeriesTimeChange()) {
                InviteChanges ic = new InviteChanges(seriesInv, dat.mInvite);
                if (ic.isExceptionRemovingChange()) {
                    long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
                    Invite[] invites = calItem.getInvites();
                    for (Invite except : invites) {
                        if (!except.isNeverSent() && except.hasRecurId() && !except.isCancel() && inviteIsAfterTime(except, now)) {
                            List<ZAttendee> toNotify = CalendarUtils.getRemovedAttendees(except.getAttendees(), seriesInv.getAttendees(), false, acct);
                            if (!toNotify.isEmpty()) {
                                notifyRemovedAttendees(zsc, octxt, acct, mbox, calItem, except, toNotify, sendQueue);
                            }
                        }
                    }
                }
            }
            // Save the change to the series as specified by the client.
            sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
            // Echo the updated inv in the response.
            if (echo && dat.mAddInvData != null) {
                echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
            }
            boolean ignorePastExceptions = true;
            // Reflect added/removed attendees in the exception instances.
            if (!atsAdded.isEmpty() || !atsCanceled.isEmpty()) {
                addRemoveAttendeesInExceptions(octxt, mbox, inv.getCalendarItem(), atsAdded, atsCanceled, ignorePastExceptions);
            }
            // Send notifications.
            if (hasRecipients) {
                notifyCalendarItem(zsc, octxt, acct, mbox, inv.getCalendarItem(), notifyAllAttendees, atsAdded, ignorePastExceptions, sendQueue);
            }
        } else {
            // Modifying a one-off appointment or an exception instance.  There are no
            // complications like in the series update case.  Just update the invite with the
            // data supplied by the client, and let the built-in notification take place.
            sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
            // Echo the updated inv in the response.
            if (echo && dat.mAddInvData != null) {
                echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
            }
        }
    } else {
        // not organizer
        // Apply the change.
        sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
        // Echo the updated inv in the response.
        if (echo && dat.mAddInvData != null) {
            echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
        }
    }
    return response;
}
Also used : InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) Address(javax.mail.Address) InternetAddress(javax.mail.internet.InternetAddress) MessagingException(javax.mail.MessagingException) ItemIdFormatter(com.zimbra.cs.service.util.ItemIdFormatter) Element(com.zimbra.common.soap.Element) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 14 with ZAttendee

use of com.zimbra.cs.mailbox.calendar.ZAttendee 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)

Example 15 with ZAttendee

use of com.zimbra.cs.mailbox.calendar.ZAttendee in project zm-mailbox by Zimbra.

the class CalendarCollection method createItemFromInvites.

/**
     * @param name Preferred DAV basename for the new item - including ".ics" if appropriate.
     * @param allowUpdate - PUTs are allowed to update a pre-existing item.  POSTs to the containing collection are not.
     */
public DavResource createItemFromInvites(DavContext ctxt, Account account, String name, List<Invite> invites, boolean allowUpdate) throws DavException, IOException {
    boolean useEtag = allowUpdate;
    try {
        String user = account.getName();
        /*
             * Some of the CalDAV clients do not behave very well when it comes to etags.
             *     chandler doesn't set User-Agent header, doesn't understand If-None-Match or If-Match headers.
             *     evolution 2.8 always sets If-None-Match although we return etag in REPORT.
             *     ical correctly understands etag and sets If-Match for existing etags, but does not use If-None-Match
             *     for new resource creation.
             */
        HttpServletRequest req = ctxt.getRequest();
        String etag = null;
        if (useEtag) {
            etag = req.getHeader(DavProtocol.HEADER_IF_MATCH);
            useEtag = (etag != null);
        }
        String baseName = HttpUtil.urlUnescape(name);
        boolean acceptableClientChosenBasename = DebugConfig.enableDAVclientCanChooseResourceBaseName && baseName.equals(name);
        if (name.endsWith(CalendarObject.CAL_EXTENSION)) {
            name = name.substring(0, name.length() - CalendarObject.CAL_EXTENSION.length());
            // Unescape the name (It was encoded in DavContext intentionally)
            name = HttpUtil.urlUnescape(name);
        }
        String uid = findEventUid(invites);
        Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(account);
        CalendarItem origCalItem = null;
        // Is the basename of the path client assigned rather than following the standard pattern?
        Integer itemId = null;
        if (acceptableClientChosenBasename) {
            itemId = DavNames.get(this.mMailboxId, this.mId, baseName);
        }
        if (itemId != null) {
            try {
                MailItem mailItem = mbox.getItemById(ctxt.getOperationContext(), itemId, MailItem.Type.UNKNOWN);
                if (mailItem != null && mailItem instanceof CalendarItem) {
                    origCalItem = (CalendarItem) mailItem;
                }
            } catch (ServiceException se) {
            }
        }
        if (origCalItem == null) {
            if (uid.equals(name)) {
                origCalItem = mbox.getCalendarItemByUid(ctxt.getOperationContext(), name);
            } else {
                /* the basename of the path doesn't fit our preferred naming convention. */
                origCalItem = mbox.getCalendarItemByUid(ctxt.getOperationContext(), uid);
                String redirectUrl = null;
                if (origCalItem != null) {
                    if (this.mId != origCalItem.getFolderId()) {
                        // In another folder, ignore
                        origCalItem = null;
                    } else {
                        // The item exists, but doesn't have this name - UID conflict.
                        if (acceptableClientChosenBasename) {
                            redirectUrl = hrefForCalendarItem(origCalItem, user, uid);
                        } else {
                            redirectUrl = defaultUrlForCalendarItem(user, uid);
                        }
                        throw new DavException.UidConflict("An item with the same UID already exists in the calendar", redirectUrl);
                    }
                }
                if ((origCalItem == null) && (!DebugConfig.enableDAVclientCanChooseResourceBaseName)) {
                    redirectUrl = defaultUrlForCalendarItem(user, uid);
                }
                if (allowUpdate && (redirectUrl != null)) {
                    /* SC_FOUND - Status code (302) indicating that the resource reside temporarily under a
                         * different URI. Since the redirection might be altered on occasion, the client should
                         * continue to use the Request-URI for future requests.(HTTP/1.1) To represent the status code
                         * (302), it is recommended to use this variable.  Used to be called SC_MOVED_TEMPORARILY
                         */
                    // sets status to SC_FOUND
                    ctxt.getResponse().sendRedirect(redirectUrl);
                    StringBuilder wrongUrlMsg = new StringBuilder();
                    wrongUrlMsg.append("wrong url - redirecting to:\n").append(redirectUrl);
                    throw new DavException(wrongUrlMsg.toString(), HttpServletResponse.SC_FOUND, null);
                }
            }
        }
        if (origCalItem == null && useEtag) {
            throw new DavException("event not found", HttpServletResponse.SC_NOT_FOUND, null);
        }
        if (origCalItem != null && !allowUpdate) {
            throw new DavException.UidConflict("An item with the same UID already exists in the calendar", hrefForCalendarItem(origCalItem, user, uid));
        }
        boolean isNewItem = true;
        if (useEtag) {
            String itemEtag = MailItemResource.getEtag(origCalItem);
            if (!itemEtag.equals(etag)) {
                throw new DavException(String.format("CalDAV client has stale event: event has different etag (%s) vs %s", itemEtag, etag), HttpServletResponse.SC_PRECONDITION_FAILED);
            }
            isNewItem = false;
        }
        // prepare to call Mailbox.setCalendarItem()
        int flags = 0;
        String[] tags = null;
        List<ReplyInfo> replies = null;
        Invite[] origInvites = null;
        if (origCalItem != null) {
            flags = origCalItem.getFlagBitmask();
            tags = origCalItem.getTags();
            replies = origCalItem.getAllReplies();
            origInvites = origCalItem.getInvites();
        }
        SetCalendarItemData scidDefault = new SetCalendarItemData();
        SetCalendarItemData[] scidExceptions = null;
        int idxExceptions = 0;
        boolean first = true;
        for (Invite i : invites) {
            // check for valid uid.
            if (i.getUid() == null)
                i.setUid(uid);
            adjustOrganizer(ctxt, i);
            // Carry over the MimeMessage/ParsedMessage to preserve any attachments.
            // CalDAV clients don't support attachments, and on edit we have to either
            // retain existing attachments or drop them.  Retaining is better.
            ParsedMessage oldPm = null;
            if (origCalItem != null) {
                Invite oldInv = origCalItem.getInvite(i.getRecurId());
                if (oldInv == null && i.hasRecurId()) {
                    // It's a new exception instance.  Inherit from series.
                    oldInv = origCalItem.getInvite((RecurId) null);
                }
                if (oldInv != null) {
                    MimeMessage mmInv = origCalItem.getSubpartMessage(oldInv.getMailItemId());
                    oldPm = mmInv != null ? new ParsedMessage(mmInv, false) : null;
                }
            }
            if (first) {
                scidDefault.invite = i;
                scidDefault.message = oldPm;
                first = false;
            } else {
                SetCalendarItemData scid = new SetCalendarItemData();
                scid.invite = i;
                scid.message = oldPm;
                if (scidExceptions == null) {
                    scidExceptions = new SetCalendarItemData[invites.size() - 1];
                }
                scidExceptions[idxExceptions++] = scid;
            }
            // For attendee case, update replies list with matching ATTENDEE from the invite.
            if (!i.isOrganizer() && replies != null) {
                ZAttendee at = i.getMatchingAttendee(account);
                if (at != null) {
                    AccountAddressMatcher acctMatcher = new AccountAddressMatcher(account);
                    ReplyInfo newReply = null;
                    for (Iterator<ReplyInfo> replyIter = replies.iterator(); replyIter.hasNext(); ) {
                        ReplyInfo reply = replyIter.next();
                        if (acctMatcher.matches(reply.getAttendee().getAddress())) {
                            RecurId ridR = reply.getRecurId(), ridI = i.getRecurId();
                            if ((ridR == null && ridI == null) || (ridR != null && ridR.equals(ridI))) {
                                // matching RECURRENCE-ID
                                // No need to compare SEQUENCE and DTSTAMP of existing reply and new invite.
                                // We're just going to take what the caldav client sent, even if it's older
                                // than the existing reply.
                                replyIter.remove();
                                if (!IcalXmlStrMap.PARTSTAT_NEEDS_ACTION.equalsIgnoreCase(at.getPartStat())) {
                                    newReply = new ReplyInfo(at, i.getSeqNo(), i.getDTStamp(), ridI);
                                }
                                break;
                            }
                        }
                    }
                    if (newReply != null) {
                        replies.add(newReply);
                    }
                }
            }
        }
        CalendarItem newCalItem = null;
        AutoScheduler autoScheduler = AutoScheduler.getAutoScheduler(mbox, this.getCalendarMailbox(ctxt), origInvites, mId, flags, tags, scidDefault, scidExceptions, replies, ctxt);
        if (autoScheduler == null) {
            newCalItem = mbox.setCalendarItem(ctxt.getOperationContext(), mId, flags, tags, scidDefault, scidExceptions, replies, CalendarItem.NEXT_ALARM_KEEP_CURRENT);
        } else {
            // Note: This also sets the calendar item
            newCalItem = autoScheduler.doSchedulingActions();
        }
        if (newCalItem == null) {
            throw new DavException("cannot create icalendar item - corrupt ICAL?", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        if (!uid.equals(name)) {
            if (acceptableClientChosenBasename) {
                DavNames.put(DavNames.DavName.create(this.mMailboxId, newCalItem.getFolderId(), baseName), newCalItem.getId());
            }
        }
        return new CalendarObject.LocalCalendarObject(ctxt, newCalItem, isNewItem);
    } catch (BadOrganizerException.DiffOrganizerInComponentsException e) {
        throw new DavException.NeedSameOrganizerInAllComponents(e.getMessage());
    } catch (BadOrganizerException e) {
        // FORBIDDEN if we aren't going to be able to cope with the data
        throw new DavException(e.getMessage(), HttpServletResponse.SC_FORBIDDEN, e);
    } catch (ServiceException e) {
        if (e.getCode().equals(ServiceException.FORBIDDEN)) {
            throw new DavException(e.getMessage(), HttpServletResponse.SC_FORBIDDEN, e);
        } else {
            throw new DavException("cannot create icalendar item", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
        }
    }
}
Also used : RecurId(com.zimbra.cs.mailbox.calendar.RecurId) ReplyInfo(com.zimbra.cs.mailbox.CalendarItem.ReplyInfo) SetCalendarItemData(com.zimbra.cs.mailbox.Mailbox.SetCalendarItemData) HttpServletRequest(javax.servlet.http.HttpServletRequest) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) Mailbox(com.zimbra.cs.mailbox.Mailbox) MimeMessage(javax.mail.internet.MimeMessage) BadOrganizerException(com.zimbra.cs.mailbox.BadOrganizerException) AutoScheduler(com.zimbra.cs.dav.caldav.AutoScheduler) DavException(com.zimbra.cs.dav.DavException) ParsedMessage(com.zimbra.cs.mime.ParsedMessage) MailItem(com.zimbra.cs.mailbox.MailItem) ServiceException(com.zimbra.common.service.ServiceException) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Aggregations

ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)29 Account (com.zimbra.cs.account.Account)16 Invite (com.zimbra.cs.mailbox.calendar.Invite)15 Mailbox (com.zimbra.cs.mailbox.Mailbox)10 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)9 ArrayList (java.util.ArrayList)9 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)8 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)7 Element (com.zimbra.common.soap.Element)6 MimeMessage (javax.mail.internet.MimeMessage)6 ParsedDuration (com.zimbra.common.calendar.ParsedDuration)5 Provisioning (com.zimbra.cs.account.Provisioning)5 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)5 AccountAddressMatcher (com.zimbra.cs.util.AccountUtil.AccountAddressMatcher)5 ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)4 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)4 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)4 ServiceException (com.zimbra.common.service.ServiceException)4 CalendarResource (com.zimbra.cs.account.CalendarResource)4 Alarm (com.zimbra.cs.mailbox.calendar.Alarm)4