use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class CalendarUtils method getRemovedAttendees.
// Compare the old and new attendee lists to figure out which attendees are being removed.
// Distribution lists are taken into consideration if requested.
public static List<ZAttendee> getRemovedAttendees(List<ZAttendee> oldAttendees, List<ZAttendee> newAttendees, boolean checkListMembership, Account account) throws ServiceException {
List<ZAttendee> list = new ArrayList<ZAttendee>();
Provisioning prov = Provisioning.getInstance();
// if attendees have been removed, then we need to send them individual cancellation messages
for (ZAttendee old : oldAttendees) {
boolean matches = false;
String oldAddr = old.getAddress();
if (oldAddr != null) {
Account oldAcct = prov.get(AccountBy.name, oldAddr);
if (oldAcct != null) {
// local user - consider aliases
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(oldAcct);
for (ZAttendee newAt : newAttendees) {
if (acctMatcher.matches(newAt.getAddress())) {
matches = true;
break;
}
}
} else {
// external email - simple string comparison of email addresses
for (ZAttendee newAt : newAttendees) {
if (oldAddr.equalsIgnoreCase(newAt.getAddress())) {
matches = true;
break;
}
}
}
}
if (!matches)
list.add(old);
}
if (list.isEmpty())
return list;
// bug 68728, skip checking in ZD
checkListMembership = checkListMembership && LC.check_dl_membership_enabled.booleanValue();
// Find out which of the new attendees are local distribution lists or GAL groups.
if (checkListMembership) {
List<DistributionList> newAtsDL = new ArrayList<DistributionList>();
List<String> /* GAL group email */
newAtsGALGroup = new ArrayList<String>();
for (ZAttendee at : newAttendees) {
String addr = at.getAddress();
if (addr != null) {
DistributionList dl = prov.get(Key.DistributionListBy.name, addr);
if (dl != null)
newAtsDL.add(dl);
else if (GalGroup.isGroup(addr, account))
newAtsGALGroup.add(addr);
}
}
// GAL groups: Iterate over GAL groups first because fetching member list is expensive.
for (String galAddr : newAtsGALGroup) {
if (list.isEmpty())
break;
Set<String> galMembers = GalGroupMembers.getGroupMembers(galAddr, account);
for (Iterator<ZAttendee> removedIter = list.iterator(); removedIter.hasNext(); ) {
ZAttendee removedAt = removedIter.next();
String addr = removedAt.getAddress();
if (addr != null && galMembers.contains(addr))
removedIter.remove();
}
}
Set<String> remoteAddrs = new HashSet<String>();
// via alias address.
for (Iterator<ZAttendee> removedIter = list.iterator(); removedIter.hasNext(); ) {
ZAttendee removedAt = removedIter.next();
String addr = removedAt.getAddress();
if (addr != null) {
Account removedAcct = prov.get(AccountBy.name, addr);
if (removedAcct != null) {
Set<String> acctDLs = prov.getDistributionLists(removedAcct);
for (DistributionList dl : newAtsDL) {
if (acctDLs.contains(dl.getId())) {
removedIter.remove();
break;
}
}
} else {
// Removed address is not a local account.
remoteAddrs.add(addr);
}
}
}
// Check non-local attendee membership in local DLs. Only direct membership is checked.
if (!remoteAddrs.isEmpty()) {
for (DistributionList dl : newAtsDL) {
// Get list members. We won't do recursive expansion; let's keep it sane.
String[] members = dl.getAllMembers();
if (members != null && members.length > 0) {
Set<String> membersLower = new HashSet<String>();
for (String member : members) {
membersLower.add(member.toLowerCase());
}
for (Iterator<ZAttendee> removedIter = list.iterator(); removedIter.hasNext(); ) {
ZAttendee removedAt = removedIter.next();
String addr = removedAt.getAddress();
if (addr != null && remoteAddrs.contains(addr) && membersLower.contains(addr.toLowerCase())) {
removedIter.remove();
}
}
}
}
}
}
return list;
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class CalendarUtils method belongToSameAccount.
/**
* Checks whether two given addresses belong to same account
* @param address1
* @param address2
* @return true if addresses belong to same account, false otherwise.
* @throws ServiceException
*/
public static boolean belongToSameAccount(String address1, String address2) throws ServiceException {
boolean isSameAccount = false;
if (address1 != null && address2 != null) {
// check if the addresses are equal
isSameAccount = address1.equalsIgnoreCase(address2);
if (!isSameAccount) {
// check if the addresses belong to same account
Account acct = Provisioning.getInstance().get(AccountBy.name, address1);
if (acct != null) {
// local user - consider aliases
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
isSameAccount = acctMatcher.matches(address2);
}
}
}
return isSameAccount;
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class CalendarCollection method adjustOrganizer.
/**
* Check that we have a valid organizer field and if not, make adjustments if appropriate.
* 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.
*/
private void adjustOrganizer(DavContext ctxt, Invite invite) throws ServiceException {
if (!invite.hasOrganizer() && !invite.hasOtherAttendees()) {
return;
}
ZOrganizer org = invite.getOrganizer();
String origOrganizerAddr = null;
// if ORGANIZER field is unset, set the field value with authUser's email addr.
if (org == null) {
org = new ZOrganizer(ctxt.getAuthAccount().getName(), null);
invite.setOrganizer(org);
invite.setIsOrganizer(true);
} else {
// iCal may use alias for organizer address. Rewrite that to primary address
origOrganizerAddr = org.getAddress();
Account acct = Provisioning.getInstance().get(AccountBy.name, origOrganizerAddr);
if (acct != null) {
String newAddr = acct.getName();
if (!origOrganizerAddr.equals(newAddr)) {
org.setAddress(newAddr);
invite.setIsOrganizer(acct);
}
}
}
if (ctxt.useIcalDelegation()) {
return;
}
String owner = getOwner();
if (owner == null) {
return;
}
Account ownerAcct = Provisioning.getInstance().get(AccountBy.name, owner);
if (ownerAcct == null) {
return;
}
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(ctxt.getAuthAccount());
origOrganizerAddr = org.getAddress();
if (acctMatcher.matches(origOrganizerAddr) && !owner.equalsIgnoreCase(origOrganizerAddr)) {
/**
* Client probably doesn't realize user should be acting as a delegate for this calendar.
* Adjust ATTENDEE and ORGANIZER if applicable.
*/
org.setAddress(ownerAcct.getName());
org.setCn(ownerAcct.getDisplayName());
org.setSentBy(origOrganizerAddr);
invite.setIsOrganizer(ownerAcct);
boolean hasOwnerAttendee = false;
for (ZAttendee attendee : invite.getAttendees()) {
if (attendee.addressMatches(owner)) {
hasOwnerAttendee = true;
}
}
if (!hasOwnerAttendee) {
// PARTSTAT:ACCEPTED - probably meant the real ORGANIZER
for (ZAttendee attendee : invite.getAttendees()) {
if (attendee.addressMatches(origOrganizerAddr) && (IcalXmlStrMap.PARTSTAT_ACCEPTED.equalsIgnoreCase(attendee.getPartStat()))) {
attendee.setAddress(ownerAcct.getName());
attendee.setCn(ownerAcct.getDisplayName());
attendee.setSentBy(origOrganizerAddr);
}
}
}
}
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher 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);
}
}
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher 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);
}
}
}
}
}
}
}
}
Aggregations