Search in sources :

Example 1 with AccountAddressMatcher

use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher 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 2 with AccountAddressMatcher

use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.

the class Message method getRemoteCalendarItem.

/**
 * If this invite doesn't appear in the mailbox for this user, this will retrieve details for it if it is in
 * a shared calendar that this user manages.
 */
public com.zimbra.soap.mail.type.CalendarItemInfo getRemoteCalendarItem(Invite invite) {
    com.zimbra.soap.mail.type.CalendarItemInfo remoteCalendarItem = null;
    String headerVal;
    try {
        headerVal = getCalendarIntendedFor(getMimeMessage());
        if (headerVal != null && headerVal.length() > 0) {
            AccountAddressMatcher acctMatcher = new AccountAddressMatcher(mMailbox.getAccount());
            if (!acctMatcher.matches(headerVal) && manageCalendar(headerVal)) {
                Provisioning prov = Provisioning.getInstance();
                Account ownerAcct = prov.get(AccountBy.name, headerVal);
                remoteCalendarItem = mMailbox.getRemoteCalItemByUID(ownerAcct, invite.getUid(), true, false);
            }
        }
    } catch (ServiceException e) {
        return null;
    }
    return remoteCalendarItem;
}
Also used : Account(com.zimbra.cs.account.Account) ServiceException(com.zimbra.common.service.ServiceException) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) Provisioning(com.zimbra.cs.account.Provisioning)

Example 3 with AccountAddressMatcher

use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.

the class ScheduleOutbox method validateRequest.

/**
 * Check for illegal requests like trying to CANCEL a meeting when not the organizer or a delegate for
 * the organizer
 * e.g. Bug 85875 Mac OS X/10.8.5 Calendar sometimes sending CANCEL when ATTENDEE deletes an instance,
 *      resulting in other attendees getting invalid CANCELs
 */
private void validateRequest(boolean isOrganizerMethod, DelegationInfo delegationInfo, String organizer, DavContext ctxt, ZComponent req) throws ServiceException, DavException {
    if ((!isOrganizerMethod) || (organizer != null && organizer.equals(delegationInfo.getOriginator()))) {
        return;
    }
    // If here, only the ORGANIZER or a delegate acting as the ORGANIZER should be able to do this
    AccountAddressMatcher acctMatcher = new AccountAddressMatcher(ctxt.getAuthAccount());
    if (!acctMatcher.matches(delegationInfo.getOriginatorEmail())) {
        throw new DavException(String.format("invalid POST to scheduling outbox '%s'. originator '%s' is not authorized account or ORGANIZER", ctxt.getRequest().getRequestURI(), delegationInfo.getOriginatorEmail()), HttpServletResponse.SC_BAD_REQUEST);
    }
    String organizerEmail = CalDavUtils.stripMailto(organizer);
    Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(ctxt.getAuthAccount());
    List<com.zimbra.cs.mailbox.Mountpoint> sharedCalendars = mbox.getCalendarMountpoints(ctxt.getOperationContext(), SortBy.NONE);
    if (sharedCalendars != null) {
        for (com.zimbra.cs.mailbox.Mountpoint sharedCalendar : sharedCalendars) {
            Account acct = Provisioning.getInstance().get(AccountBy.id, sharedCalendar.getOwnerId());
            if (acct != null) {
                acctMatcher = new AccountAddressMatcher(acct);
                if (acctMatcher.matches(organizerEmail)) {
                    return;
                }
            }
        }
    }
    // We haven't found a shared calendar for the ORGANIZER that we are a delegate for.
    throw new DavException(String.format("invalid POST to scheduling outbox '%s'. '%s' cannot act as ORGANIZER '%s'", ctxt.getRequest().getRequestURI(), delegationInfo.getOriginatorEmail(), organizerEmail), HttpServletResponse.SC_BAD_REQUEST);
}
Also used : Account(com.zimbra.cs.account.Account) Mailbox(com.zimbra.cs.mailbox.Mailbox) DavException(com.zimbra.cs.dav.DavException) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher)

Example 4 with AccountAddressMatcher

use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.

the class ScheduleOutbox method adjustOrganizer.

/**
 * For Vanilla CalDAV access where Apple style delegation has not been enabled, attempts by the delegate
 * to use a shared calendar acting as themselves are translated to appear as if acting as a delegate,
 * otherwise the experience can be very poor.
 * @throws ServiceException
 */
private void adjustOrganizer(DavContext ctxt, ZCalendar.ZVCalendar cal, ZComponent req, DelegationInfo delegationInfo) throws ServiceException {
    // BusyCal 2.5.3 seems to post with the wrong organizer even if ical delegation is switched on - even though
    // it uses the right ORGANIZER in the calendar entry.
    // if (ctxt.useIcalDelegation()) { return; }
    Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(ctxt.getAuthAccount());
    String uid = req.getPropVal(ICalTok.UID, null);
    CalendarItem matchingCalendarEntry = mbox.getCalendarItemByUid(ctxt.getOperationContext(), uid);
    if (matchingCalendarEntry == null) {
        List<com.zimbra.cs.mailbox.Mountpoint> sharedCalendars = mbox.getCalendarMountpoints(ctxt.getOperationContext(), SortBy.NONE);
        if (sharedCalendars == null) {
            // Can't work out anything useful
            return;
        }
        Set<Account> accts = Sets.newHashSet();
        for (com.zimbra.cs.mailbox.Mountpoint sharedCalendar : sharedCalendars) {
            accts.add(Provisioning.getInstance().get(AccountBy.id, sharedCalendar.getOwnerId()));
        }
        for (Account acct : accts) {
            Mailbox sbox = MailboxManager.getInstance().getMailboxByAccount(acct);
            matchingCalendarEntry = sbox.getCalendarItemByUid(ctxt.getOperationContext(), uid);
            if (matchingCalendarEntry != null) {
                break;
            }
        }
    }
    if (matchingCalendarEntry == null) {
        return;
    }
    Invite[] invites = matchingCalendarEntry.getInvites();
    if (invites == null) {
        return;
    }
    for (Invite inv : invites) {
        ZOrganizer org = inv.getOrganizer();
        if (org != null) {
            delegationInfo.setOwner(org.getAddress());
            if (Strings.isNullOrEmpty(org.getCn())) {
                Account ownerAcct = Provisioning.getInstance().get(AccountBy.name, org.getAddress());
                if (!Strings.isNullOrEmpty(ownerAcct.getDisplayName())) {
                    delegationInfo.setOwnerCn(ownerAcct.getDisplayName());
                }
            } else {
                delegationInfo.setOwnerCn(org.getCn());
            }
            break;
        }
    }
    if (delegationInfo.getOwner() == null) {
        return;
    }
    AccountAddressMatcher acctMatcher = new AccountAddressMatcher(ctxt.getAuthAccount());
    boolean originatorIsCalEntryOrganizer = acctMatcher.matches(delegationInfo.getOwnerEmail());
    if (originatorIsCalEntryOrganizer) {
        return;
    }
    for (ZComponent component : cal.getComponents()) {
        ZProperty organizerProp = component.getProperty(ICalTok.ORGANIZER);
        if (organizerProp != null) {
            organizerProp.setValue(delegationInfo.getOwner());
            ZParameter cn = organizerProp.getParameter(ICalTok.CN);
            if (cn == null) {
                organizerProp.addParameter(new ZParameter(ICalTok.CN, delegationInfo.getOwnerCn()));
            } else {
                cn.setValue(delegationInfo.getOwnerCn());
            }
            ZParameter sentBy = organizerProp.getParameter(ICalTok.SENT_BY);
            if (sentBy == null) {
                organizerProp.addParameter(new ZParameter(ICalTok.SENT_BY, delegationInfo.getOriginator()));
            } else {
                sentBy.setValue(delegationInfo.getOriginator());
            }
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) ZParameter(com.zimbra.common.calendar.ZCalendar.ZParameter) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) ZComponent(com.zimbra.common.calendar.ZCalendar.ZComponent) Mailbox(com.zimbra.cs.mailbox.Mailbox) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 5 with AccountAddressMatcher

use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.

the class ScheduleOutbox method handlePost.

@Override
public void handlePost(DavContext ctxt) throws DavException, IOException, ServiceException {
    DelegationInfo delegationInfo = new DelegationInfo(ctxt.getRequest().getHeader(DavProtocol.HEADER_ORIGINATOR));
    Enumeration<String> recipients = ctxt.getRequest().getHeaders(DavProtocol.HEADER_RECIPIENT);
    InputStream in = ctxt.getUpload().getInputStream();
    ZCalendar.ZVCalendar vcalendar = ZCalendar.ZCalendarBuilder.build(in, MimeConstants.P_CHARSET_UTF8);
    Closeables.closeQuietly(in);
    Iterator<ZComponent> iter = vcalendar.getComponentIterator();
    ZComponent req = null;
    while (iter.hasNext()) {
        req = iter.next();
        if (req.getTok() != ICalTok.VTIMEZONE)
            break;
        req = null;
    }
    if (req == null) {
        throw new DavException("empty request", HttpServletResponse.SC_BAD_REQUEST);
    }
    ZimbraLog.dav.debug("originator: %s", delegationInfo.getOriginator());
    boolean isVEventOrVTodo = ICalTok.VEVENT.equals(req.getTok()) || ICalTok.VTODO.equals(req.getTok());
    boolean isOrganizerMethod = false, isCancel = false;
    if (isVEventOrVTodo) {
        String method = vcalendar.getPropVal(ICalTok.METHOD, null);
        if (method != null) {
            isOrganizerMethod = Invite.isOrganizerMethod(method);
            isCancel = ICalTok.CANCEL.toString().equalsIgnoreCase(method);
            ;
        }
        // Apple iCal fixup
        CalDavUtils.removeAttendeeForOrganizer(req);
    }
    // Get organizer and list of attendees. (mailto:email values)
    ArrayList<String> attendees = new ArrayList<String>();
    String organizer = null;
    for (Iterator<ZProperty> propsIter = req.getPropertyIterator(); propsIter.hasNext(); ) {
        ZProperty prop = propsIter.next();
        ICalTok token = prop.getToken();
        if (ICalTok.ATTENDEE.equals(token)) {
            String val = prop.getValue();
            if (val != null) {
                attendees.add(val.trim());
            }
        } else if (ICalTok.ORGANIZER.equals(token)) {
            String val = prop.getValue();
            if (val != null) {
                organizer = val.trim();
                String addr = CalDavUtils.stripMailto(organizer);
                // Rewrite the alias to primary address
                Account acct = Provisioning.getInstance().get(AccountBy.name, addr);
                if (acct != null) {
                    String newAddr = acct.getName();
                    if (!addr.equals(newAddr)) {
                        organizer = "mailto:" + newAddr;
                        prop.setValue(organizer);
                    }
                }
            }
        }
    }
    // Apple iCal is very inconsistent about the user's identity when the account has aliases.
    if (isVEventOrVTodo && delegationInfo.getOriginator() != null && ctxt.getAuthAccount() != null) {
        AccountAddressMatcher acctMatcher = new AccountAddressMatcher(ctxt.getAuthAccount());
        if (acctMatcher.matches(delegationInfo.getOriginatorEmail())) {
            if (isOrganizerMethod) {
                if (organizer != null) {
                    String organizerEmail = CalDavUtils.stripMailto(organizer);
                    if (!organizerEmail.equalsIgnoreCase(delegationInfo.getOriginatorEmail()) && acctMatcher.matches(organizerEmail)) {
                        delegationInfo.setOriginator(organizer);
                        ZimbraLog.dav.debug("changing originator to %s to match address/alias used in ORGANIZER", delegationInfo.getOriginator());
                    }
                }
            } else {
                for (String at : attendees) {
                    String atEmail = CalDavUtils.stripMailto(at);
                    if (delegationInfo.getOriginatorEmail().equalsIgnoreCase(atEmail)) {
                        break;
                    } else if (acctMatcher.matches(atEmail)) {
                        delegationInfo.setOriginator(at);
                        ZimbraLog.dav.debug("changing originator to %s to match address/alias used in ATTENDEE", delegationInfo.getOriginator());
                        break;
                    }
                }
            }
        }
    }
    // Get the recipients.
    ArrayList<String> rcptArray = new ArrayList<String>();
    while (recipients.hasMoreElements()) {
        String rcptHdr = recipients.nextElement();
        String[] rcpts = null;
        if (rcptHdr.indexOf(',') > 0) {
            rcpts = rcptHdr.split(",");
        } else {
            rcpts = new String[] { rcptHdr };
        }
        for (String rcpt : rcpts) {
            if (rcpt != null) {
                rcpt = rcpt.trim();
                if (rcpt.length() != 0) {
                    // Workaround for Apple iCal: Ignore attendees with address "invalid:nomail".
                    if (rcpt.equalsIgnoreCase("invalid:nomail")) {
                        continue;
                    }
                    if (isVEventOrVTodo) {
                        // iCal can sometimes do that when organizer account has aliases.
                        if (isOrganizerMethod && rcpt.equalsIgnoreCase(organizer)) {
                            continue;
                        }
                        // as ATTENDEE in the CANCEL component being sent.  (iCal does that part correctly, at least.)
                        if (isCancel) {
                            boolean isAttendee = false;
                            // Rcpt must be an attendee of the cancel component.
                            for (String at : attendees) {
                                if (rcpt.equalsIgnoreCase(at)) {
                                    isAttendee = true;
                                    break;
                                }
                            }
                            if (!isAttendee) {
                                ZimbraLog.dav.info("Ignoring non-attendee recipient '%s' of CANCEL request; likely a client bug", rcpt);
                                continue;
                            }
                        }
                    }
                    // All checks passed.
                    rcptArray.add(rcpt);
                }
            }
        }
    }
    Element scheduleResponse = ctxt.getDavResponse().getTop(DavElements.E_SCHEDULE_RESPONSE);
    for (String rcpt : rcptArray) {
        ZimbraLog.dav.debug("recipient email: " + rcpt);
        Element resp = scheduleResponse.addElement(DavElements.E_CALDAV_RESPONSE);
        switch(req.getTok()) {
            case VFREEBUSY:
                handleFreebusyRequest(ctxt, req, delegationInfo.getOriginator(), rcpt, resp);
                break;
            case VEVENT:
                // records.
                if (isOrganizerMethod) {
                    adjustOrganizer(ctxt, vcalendar, req, delegationInfo);
                }
                validateRequest(isOrganizerMethod, delegationInfo, organizer, ctxt, req);
                handleEventRequest(ctxt, vcalendar, req, delegationInfo, rcpt, resp);
                break;
            default:
                throw new DavException("unrecognized request: " + req.getTok(), HttpServletResponse.SC_BAD_REQUEST);
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) DavException(com.zimbra.cs.dav.DavException) InputStream(java.io.InputStream) Element(org.dom4j.Element) ArrayList(java.util.ArrayList) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) ZCalendar(com.zimbra.common.calendar.ZCalendar) ZComponent(com.zimbra.common.calendar.ZCalendar.ZComponent) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty)

Aggregations

AccountAddressMatcher (com.zimbra.cs.util.AccountUtil.AccountAddressMatcher)20 Account (com.zimbra.cs.account.Account)16 Invite (com.zimbra.cs.mailbox.calendar.Invite)7 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)6 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)5 ServiceException (com.zimbra.common.service.ServiceException)5 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)5 ArrayList (java.util.ArrayList)4 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)3 ICalTok (com.zimbra.common.calendar.ZCalendar.ICalTok)3 JavaMailInternetAddress (com.zimbra.common.mime.shim.JavaMailInternetAddress)3 Provisioning (com.zimbra.cs.account.Provisioning)3 DavException (com.zimbra.cs.dav.DavException)3 Mailbox (com.zimbra.cs.mailbox.Mailbox)3 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)3 ZComponent (com.zimbra.common.calendar.ZCalendar.ZComponent)2 ZParameter (com.zimbra.common.calendar.ZCalendar.ZParameter)2 AccessManager (com.zimbra.cs.account.AccessManager)2 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)2 MailItem (com.zimbra.cs.mailbox.MailItem)2