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;
}
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;
}
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);
}
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());
}
}
}
}
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);
}
}
}
Aggregations