Search in sources :

Example 11 with Invite

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

the class CalendarItem method organizerChangeCheck.

/**
     * Check to make sure the new invite doesn't change the organizer in a disallowed way.
     * @param newInvite
     * @return true if organizer change was detected, false if no change
     * @throws ServiceException
     */
private boolean organizerChangeCheck(Invite newInvite, boolean denyChange) throws ServiceException {
    Invite originalInvite = null;
    if (!newInvite.hasRecurId()) {
        // New invite is not for an exception.
        originalInvite = getDefaultInviteOrNull();
    } else {
        // New invite is for an exception.
        boolean found = false;
        RecurId newRid = newInvite.getRecurId();
        for (Invite inv : mInvites) {
            if (inv.hasRecurId() && newRid.equals(inv.getRecurId())) {
                originalInvite = inv;
                found = true;
                break;
            }
        }
        if (!found) {
            // If no invite with matching RECURRENCE-ID was found, use the default invite.
            originalInvite = getDefaultInviteOrNull();
        }
    }
    if (originalInvite == null) {
        // If no "default" invite was found, use the first one.
        if (mInvites.size() > 0)
            originalInvite = mInvites.get(0);
        if (originalInvite == null) {
            // checks in this method.
            return false;
        }
    }
    boolean updatingSameComponent = true;
    if (newInvite.hasRecurId()) {
        if (originalInvite.hasRecurId()) {
            updatingSameComponent = newInvite.getRecurId().equals(originalInvite.getRecurId());
        } else {
            updatingSameComponent = false;
        }
    }
    boolean changed = false;
    ZOrganizer originalOrganizer = originalInvite.getOrganizer();
    if (!originalInvite.isOrganizer()) {
        // This account WAS NOT the organizer.  Prevent organizer change.
        if (newInvite.hasOrganizer()) {
            String newOrgAddr = newInvite.getOrganizer().getAddress();
            if (originalOrganizer == null) {
                if (denyChange) {
                    newInvite.isTodo();
                    if (updatingSameComponent) {
                        throw BadOrganizerException.ADD_ORGANIZER_NOT_ALLOWED(newOrgAddr, calDesc(newInvite));
                    } else {
                        throw BadOrganizerException.ORGANIZER_INTRODUCED_FOR_EXCEPTION(newOrgAddr, calDesc(newInvite));
                    }
                } else {
                    changed = true;
                }
            } else {
                // Both old and new organizers are set.  They must be the same address.
                String origOrgAddr = originalOrganizer.getAddress();
                if (newOrgAddr == null || !CalendarUtils.belongToSameAccount(origOrgAddr, newOrgAddr)) {
                    if (denyChange) {
                        if (updatingSameComponent) {
                            throw BadOrganizerException.CHANGE_ORGANIZER_NOT_ALLOWED(origOrgAddr, newOrgAddr, calDesc(newInvite));
                        } else {
                            throw BadOrganizerException.DIFF_ORGANIZER_IN_COMPONENTS(origOrgAddr, newOrgAddr, calDesc(newInvite));
                        }
                    } else {
                        changed = true;
                    }
                }
            }
        } else if (originalOrganizer != null) {
            // No organizer for new newInvite but there is one in the original
            String origOrgAddr = originalOrganizer.getAddress();
            if (denyChange) {
                if (updatingSameComponent) {
                    throw BadOrganizerException.DEL_ORGANIZER_NOT_ALLOWED(origOrgAddr, calDesc(newInvite));
                } else {
                    throw BadOrganizerException.MISSING_ORGANIZER_IN_SINGLE_INSTANCE(origOrgAddr, calDesc(newInvite));
                }
            } else {
                changed = true;
            }
        }
    } else {
        // Still don't allow changing the organizer field to an arbitrary address.
        if (newInvite.hasOrganizer()) {
            if (!newInvite.isOrganizer()) {
                String newOrgAddr = newInvite.getOrganizer().getAddress();
                String origOrgAddr = (originalOrganizer != null) ? originalOrganizer.getAddress() : null;
                if (newOrgAddr.equalsIgnoreCase(origOrgAddr)) {
                    /* Speculative fix for Bug 83261.  Had gotten to this point with the same address but
                         * thought that wasn't the organizer for the new invite even though that organizer
                         * passed the test for originalInvite.  Ideally, should track down why the value was wrong
                         * but don't have a full repro scenario.
                         */
                    newInvite.setIsOrganizer(true);
                }
                if (!newInvite.isOrganizer()) {
                    if (denyChange) {
                        if (originalOrganizer != null) {
                            if (updatingSameComponent) {
                                throw BadOrganizerException.CHANGE_ORGANIZER_NOT_ALLOWED(origOrgAddr, newOrgAddr, calDesc(newInvite));
                            } else {
                                throw BadOrganizerException.DIFF_ORGANIZER_IN_COMPONENTS(origOrgAddr, newOrgAddr, calDesc(newInvite));
                            }
                        } else {
                            if (updatingSameComponent) {
                                throw BadOrganizerException.ADD_ORGANIZER_NOT_ALLOWED(newOrgAddr, calDesc(newInvite));
                            } else {
                                throw BadOrganizerException.ORGANIZER_INTRODUCED_FOR_EXCEPTION(newOrgAddr, calDesc(newInvite));
                            }
                        }
                    } else {
                        changed = true;
                    }
                }
            }
        }
    }
    if (changed) {
        String origOrg = originalOrganizer != null ? originalOrganizer.getAddress() : null;
        ZOrganizer newOrganizer = newInvite.getOrganizer();
        String newOrg = newOrganizer != null ? newOrganizer.getAddress() : null;
        boolean wasOrganizer = originalInvite.isOrganizer();
        boolean isOrganizer = newInvite.isOrganizer();
        ZimbraLog.calendar.info("Changed organizer: old=" + origOrg + ", new=" + newOrg + ", wasOrg=" + wasOrganizer + ", isOrg=" + isOrganizer + ", UID=\"" + newInvite.getUid() + "\", invId=" + newInvite.getMailItemId());
    }
    return changed;
}
Also used : ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 12 with Invite

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

the class CalendarItem method create.

static CalendarItem create(int id, Folder folder, int flags, Tag.NormalizedTags ntags, String uid, ParsedMessage pm, Invite firstInvite, long nextAlarm, CustomMetadata custom) throws ServiceException {
    firstInvite.sanitize(false);
    if (!folder.canAccess(ACL.RIGHT_INSERT)) {
        throw ServiceException.PERM_DENIED("you do not have the required rights on the folder");
    }
    if (!firstInvite.isPublic() && !folder.canAccess(ACL.RIGHT_PRIVATE)) {
        throw ServiceException.PERM_DENIED("you do not have permission to create private calendar item in this folder");
    }
    Mailbox mbox = folder.getMailbox();
    if (pm != null && pm.hasAttachments()) {
        firstInvite.setHasAttachment(true);
        flags |= Flag.BITMASK_ATTACHED;
    } else {
        firstInvite.setHasAttachment(false);
        flags &= ~Flag.BITMASK_ATTACHED;
    }
    if (firstInvite.isDraft()) {
        flags |= Flag.BITMASK_DRAFT;
    } else {
        flags &= ~Flag.BITMASK_DRAFT;
    }
    if (firstInvite.isHighPriority()) {
        flags |= Flag.BITMASK_HIGH_PRIORITY;
    } else {
        flags &= ~Flag.BITMASK_HIGH_PRIORITY;
    }
    if (firstInvite.isLowPriority()) {
        flags |= Flag.BITMASK_LOW_PRIORITY;
    } else {
        flags &= ~Flag.BITMASK_LOW_PRIORITY;
    }
    MailItem.Type type = firstInvite.isEvent() ? Type.APPOINTMENT : Type.TASK;
    String sender = null;
    ZOrganizer org = firstInvite.getOrganizer();
    if (org != null) {
        sender = org.getIndexString();
    }
    sender = Strings.nullToEmpty(sender);
    String subject = Strings.nullToEmpty(firstInvite.getName());
    List<Invite> invites = new ArrayList<Invite>();
    invites.add(firstInvite);
    Recurrence.IRecurrence recur = firstInvite.getRecurrence();
    long startTime, endTime;
    if (recur != null) {
        ParsedDateTime dtStart = recur.getStartTime();
        startTime = dtStart != null ? dtStart.getUtcTime() : 0;
        ParsedDateTime dtEnd = recur.getEndTime();
        endTime = dtEnd != null ? dtEnd.getUtcTime() : 0;
    } else {
        ParsedDateTime dtStart = firstInvite.getStartTime();
        startTime = dtStart != null ? dtStart.getUtcTime() : 0;
        ParsedDateTime dtEnd = firstInvite.getEffectiveEndTime();
        endTime = dtEnd != null ? dtEnd.getUtcTime() : startTime;
    }
    Account account = mbox.getAccount();
    firstInvite.updateMyPartStat(account, firstInvite.getPartStat());
    UnderlyingData data = new UnderlyingData();
    data.id = id;
    data.type = type.toByte();
    data.folderId = folder.getId();
    if (!folder.inSpam() || mbox.getAccount().getBooleanAttr(Provisioning.A_zimbraJunkMessagesIndexingEnabled, false)) {
        data.indexId = IndexStatus.DEFERRED.id();
    }
    data.imapId = id;
    data.date = mbox.getOperationTimestamp();
    data.setFlags(flags & (Flag.FLAGS_CALITEM | Flag.FLAGS_GENERIC));
    data.setTags(ntags);
    data.setSubject(subject);
    data.metadata = encodeMetadata(DEFAULT_COLOR_RGB, 1, 1, custom, uid, startTime, endTime, recur, invites, firstInvite.getTimeZoneMap(), new ReplyList(), null);
    data.contentChanged(mbox, false);
    if (!firstInvite.hasRecurId()) {
        ZimbraLog.calendar.info("Adding CalendarItem: id=%d, Message-ID=\"%s\", folderId=%d, subject=\"%s\", UID=%s", data.id, pm != null ? pm.getMessageID() : "(none)", folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid());
    } else {
        ZimbraLog.calendar.info("Adding CalendarItem: id=%d, Message-ID=\"%s\", folderId=%d, subject=\"%s\", UID=%s, recurId=%s", data.id, pm != null ? pm.getMessageID() : "(none)", folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid(), firstInvite.getRecurId().getDtZ());
    }
    new DbMailItem(mbox).setSender(sender).create(data);
    CalendarItem item = type == Type.APPOINTMENT ? new Appointment(mbox, data) : new Task(mbox, data);
    Invite defInvite = item.getDefaultInviteOrNull();
    if (defInvite != null) {
        Collection<Instance> instances = item.expandInstances(CalendarUtils.MICROSOFT_EPOC_START_MS_SINCE_EPOC, Long.MAX_VALUE, false);
        if (instances.isEmpty()) {
            ZimbraLog.calendar.info("CalendarItem has effectively zero instances: id=%d, folderId=%d, subject=\"%s\", UID=%s ", data.id, folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid());
            item.delete();
            throw ServiceException.FORBIDDEN("Recurring series has effectively zero instances");
        }
    }
    // If we're creating an invite during email delivery, always default to NEEDS_ACTION state.
    // If not email delivery, we assume the requesting client knows what it's doing and has set the
    // correct partstat in the invite.
    String defaultPartStat;
    if (mbox.getOperationContext() == null) {
        // octxt == null implies we're in email delivery.  (There needs to be better way to determine this...)
        defaultPartStat = IcalXmlStrMap.PARTSTAT_NEEDS_ACTION;
    } else {
        defaultPartStat = firstInvite.getPartStat();
    }
    item.processPartStat(firstInvite, pm != null ? pm.getMimeMessage() : null, true, defaultPartStat);
    item.finishCreation(null);
    folder.updateHighestMODSEQ();
    if (pm != null) {
        item.createBlob(pm, firstInvite);
    }
    item.mEndTime = item.recomputeRecurrenceEndTime(item.mEndTime);
    if (firstInvite.hasAlarm()) {
        item.recomputeNextAlarm(nextAlarm, false, false);
        item.saveMetadata();
        AlarmData alarmData = item.getAlarmData();
        if (alarmData != null) {
            long newNextAlarm = alarmData.getNextAtBase();
            if (newNextAlarm > 0 && newNextAlarm < item.mStartTime) {
                item.mStartTime = newNextAlarm;
            }
        }
    }
    DbMailItem.addToCalendarItemTable(item);
    Callback cb = getCallback();
    if (cb != null) {
        cb.created(item);
    }
    return item;
}
Also used : IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Recurrence(com.zimbra.cs.mailbox.calendar.Recurrence) Account(com.zimbra.cs.account.Account) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) DbMailItem(com.zimbra.cs.db.DbMailItem) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) ArrayList(java.util.ArrayList) DbMailItem(com.zimbra.cs.db.DbMailItem) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 13 with Invite

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

the class CalendarItem method updateLocalExceptionsWhichMatchSeriesReply.

/**
     * Exceptions can be created which aren't communicated to ATTENDEEs, either because the ORGANIZER wants to
     * have local changes like a different alarm time or because a response is received which only affects one
     * instance of a series.
     * Caller is responsible for ensuring changed MetaData is written through to SQL sending notification of change.
     */
private void updateLocalExceptionsWhichMatchSeriesReply(Invite reply) throws ServiceException {
    if ((reply == null) || reply.getRecurId() != null) {
        // Only interested in series replies
        return;
    }
    IRecurrence replyRecurrence = reply.getRecurrence();
    if (replyRecurrence == null) {
        sLog.debug("Giving up on trying to match series reply to local exceptions - no recurrence in reply");
        return;
    }
    for (int i = 0; i < numInvites(); i++) {
        Invite cur = getInvite(i);
        if (!cur.classPropSetByMe() || (cur.getRecurId() == null)) {
            continue;
        }
        ParsedDateTime recurIdDT = cur.getRecurId().getDt();
        ParsedDateTime startDT = cur.getStartTime();
        // If the start time has moved then the series response can't be applicable.
        if ((recurIdDT == null) || (startDT == null) || !recurIdDT.sameTime(startDT)) {
            continue;
        }
        long utcTime = recurIdDT.getUtcTime();
        // Find instances within 2 seconds either side of start - assuming it will be a direct hit if found
        List<Instance> instances = Recurrence.expandInstances(replyRecurrence, getId(), utcTime - 2000L, utcTime + 2000L);
        if (instances == null || (instances.size() != 1)) {
            continue;
        }
        cur.updateMatchingAttendeesFromReply(reply);
    }
}
Also used : IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 14 with Invite

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

the class CalendarItem method processNewInviteReply.

boolean processNewInviteReply(Invite reply, String sender) throws ServiceException {
    List<ZAttendee> attendees = reply.getAttendees();
    String senderAddress = null;
    if (sender != null && !sender.isEmpty()) {
        try {
            JavaMailInternetAddress address = new JavaMailInternetAddress(sender);
            senderAddress = address.getAddress();
        } catch (AddressException e) {
        // ignore invalid sender address.
        }
    }
    if (senderAddress != null && !attendees.isEmpty()) {
        AccountAddressMatcher acctMatcher = null;
        Account acct = Provisioning.getInstance().get(AccountBy.name, senderAddress);
        if (acct != null) {
            acctMatcher = new AccountAddressMatcher(acct);
        }
        Iterator<ZAttendee> iter = attendees.iterator();
        while (iter.hasNext()) {
            ZAttendee att = iter.next();
            // Remove the attendee if not same as the sender.
            if (!(att.addressMatches(senderAddress) || (acctMatcher != null && acctMatcher.matches(att.getAddress())))) {
                iter.remove();
            }
        }
    }
    // trace logging
    ZAttendee att1 = !attendees.isEmpty() ? attendees.get(0) : null;
    if (att1 != null) {
        String ptst = IcalXmlStrMap.sPartStatMap.toIcal(att1.getPartStat());
        if (!reply.hasRecurId())
            ZimbraLog.calendar.info("Processing CalendarItem reply: attendee=%s, partstat=%s, id=%d, folderId=%d, subject=\"%s\", UID=%s", att1.getAddress(), ptst, mId, getFolderId(), reply.isPublic() ? reply.getName() : "(private)", mUid);
        else
            ZimbraLog.calendar.info("Processing CalendarItem reply: attendee=%s, partstat=%s, id=%d, folderId=%d, subject=\"%s\", UID=%s, recurId=%s", att1.getAddress(), ptst, mId, getFolderId(), reply.isPublic() ? reply.getName() : "(private)", mUid, reply.getRecurId().getDtZ());
    }
    // Require private access permission only when we're replying to a private series/instance.
    boolean requirePrivateCheck = requirePrivateCheck(reply);
    OperationContext octxt = getMailbox().getOperationContext();
    Account authAccount = octxt != null ? octxt.getAuthenticatedUser() : null;
    boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
    if (!canAccess(ACL.RIGHT_ACTION, authAccount, asAdmin, requirePrivateCheck))
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions to change this appointment/task's state");
    boolean dirty = false;
    // unique ID: UID+RECURRENCE_ID
    // See RFC2446: 2.1.5 Message Sequencing
    // UID already matches...next check if RecurId matches
    // if so, then seqNo is next
    // finally use DTStamp
    Invite matchingInvite = matchingInvite(reply.getRecurId());
    if (matchingInvite != null) {
        //             up to date with the organizer's event, provided there were no major changes.
        if ((matchingInvite.isOrganizer() && (matchingInvite.getLastFullSeqNo() > reply.getSeqNo())) || (!matchingInvite.isOrganizer() && (matchingInvite.getSeqNo() > reply.getSeqNo()))) {
            sLog.info("Invite-Reply %s is outdated (Calendar entry has higher SEQUENCE), ignoring!", reply);
            return false;
        }
    // maybeStoreNewReply does some further checks which might invalidate this reply
    // so, postpone updating attendee information until after that.
    }
    // they must be replying to a arbitrary instance)
    for (ZAttendee at : attendees) {
        if (mReplyList.maybeStoreNewReply(reply, at, this))
            dirty = true;
    }
    if (!dirty) {
        sLog.info("Invite-Reply %s is outdated ignoring!", reply);
        return false;
    }
    if (matchingInvite != null) {
        matchingInvite.updateMatchingAttendeesFromReply(reply);
        updateLocalExceptionsWhichMatchSeriesReply(reply);
    } else {
        createPseudoExceptionForSingleInstanceReplyIfNecessary(reply);
    }
    saveMetadata();
    return true;
}
Also used : Account(com.zimbra.cs.account.Account) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) AddressException(javax.mail.internet.AddressException) JavaMailInternetAddress(com.zimbra.common.mime.shim.JavaMailInternetAddress) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 15 with Invite

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

the class CalendarItem method move.

@Override
boolean move(Folder target) throws ServiceException {
    Invite defInv = getDefaultInviteOrNull();
    String sbj;
    if (defInv != null)
        sbj = defInv.isPublic() ? defInv.getName() : "(private)";
    else
        sbj = "(none)";
    ZimbraLog.calendar.info("Moving CalendarItem: id=%d, src=%s, dest=%s, subject=\"%s\", UID=%s", mId, getMailopContext(getFolder()), getMailopContext(target), sbj, mUid);
    if (!isPublic()) {
        if (!canAccess(ACL.RIGHT_PRIVATE))
            throw ServiceException.PERM_DENIED("you do not have permission to move private calendar item from the current folder");
        if (target.getId() != Mailbox.ID_FOLDER_TRASH && !target.canAccess(ACL.RIGHT_PRIVATE))
            throw ServiceException.PERM_DENIED("you do not have permission to move private calendar item to the target folder");
    }
    addRevision(true);
    return super.move(target);
}
Also used : Invite(com.zimbra.cs.mailbox.calendar.Invite)

Aggregations

Invite (com.zimbra.cs.mailbox.calendar.Invite)103 Account (com.zimbra.cs.account.Account)30 Element (com.zimbra.common.soap.Element)26 ServiceException (com.zimbra.common.service.ServiceException)23 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)23 Mailbox (com.zimbra.cs.mailbox.Mailbox)23 MimeMessage (javax.mail.internet.MimeMessage)23 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)22 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)20 ItemId (com.zimbra.cs.service.util.ItemId)20 ArrayList (java.util.ArrayList)19 IOException (java.io.IOException)18 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)16 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)15 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)15 OperationContext (com.zimbra.cs.mailbox.OperationContext)14 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)14 MessagingException (javax.mail.MessagingException)12 MailServiceException (com.zimbra.cs.mailbox.MailServiceException)11 ParsedMessage (com.zimbra.cs.mime.ParsedMessage)11