use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class Message method manageCalendar.
/**
* @return true if have right "ACTION" for a calender owned by {@code ownerEmail}
* @throws ServiceException
*/
private boolean manageCalendar(String ownerEmail) throws ServiceException {
if (Strings.isNullOrEmpty(ownerEmail)) {
return false;
}
Account ownerAcct = Provisioning.getInstance().get(AccountBy.name, ownerEmail);
if (ownerAcct == null) {
ZimbraLog.account.info("Account = %s not present.", ownerEmail);
return false;
}
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(ownerAcct);
for (Mountpoint sharedCal : getMailbox().getCalendarMountpoints(getMailbox().getOperationContext(), SortBy.NONE)) {
if (sharedCal.canAccess(ACL.RIGHT_ACTION)) {
String calOwner = Provisioning.getInstance().get(AccountBy.id, sharedCal.getOwnerId()).getName();
if (acctMatcher.matches(calOwner)) {
return true;
}
}
}
return false;
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class Message method getInfoForAssociatedCalendarItem.
/**
* Update {@code status.calItem} and {@code calendarItemInfos}
*/
private void getInfoForAssociatedCalendarItem(Account acct, Invite cur, String method, ParsedMessage pm, boolean applyToCalendar, ProcessInvitesStatus status) throws ServiceException {
boolean calItemIsNew = false;
boolean modifiedCalItem = false;
boolean success = false;
try {
InviteChanges invChanges = null;
// Look for organizer-provided change list.
ZProperty changesProp = cur.getXProperty(ICalTok.X_ZIMBRA_CHANGES.toString());
if (changesProp != null) {
invChanges = new InviteChanges(changesProp.getValue());
// Don't let the x-prop propagate further. This x-prop is used during transport only. Presence
// of this x-prop in the appointment object can confuse clients.
cur.removeXProp(ICalTok.X_ZIMBRA_CHANGES.toString());
}
if (!(status.intendedForMe || status.intendedForCalendarIManage)) {
// Not intended for me. Just save the invite detail in metadata.
CalendarItemInfo info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
status.updatedMetadata = true;
success = true;
return;
}
OperationContext octxt = getMailbox().getOperationContext();
ICalTok methodTok = Invite.lookupMethod(method);
AccountAddressMatcher acctMatcher = status.getAcctMatcher();
cur.sanitize(true);
if (status.intendedForCalendarIManage) {
Provisioning prov = Provisioning.getInstance();
Account ownerAcct = prov.get(AccountBy.name, calendarIntendedFor);
com.zimbra.soap.mail.type.CalendarItemInfo cii = getMailbox().getRemoteCalItemByUID(ownerAcct, cur.getUid(), false, false);
CalendarItemInfo info;
if (cii == null) {
info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
} else {
int calItemId;
String owner;
try {
ItemId iid = new ItemId(cii.getId(), (String) null);
calItemId = iid.getId();
owner = iid.getAccountId();
} catch (Exception e) {
calItemId = CalendarItemInfo.CALITEM_ID_NONE;
owner = null;
}
info = new CalendarItemInfo(calItemId, owner, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
}
status.updatedMetadata = true;
success = true;
return;
}
status.calItem = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
if (applyToCalendar && // replies are handled elsewhere (in Mailbox.addMessage())
!ICalTok.REPLY.equals(methodTok) && !ICalTok.COUNTER.equals(methodTok) && !ICalTok.DECLINECOUNTER.equals(methodTok)) {
if (status.calItem == null) {
// Allow PUBLISH method as well depending on the preference.
if (ICalTok.REQUEST.equals(methodTok) || (ICalTok.PUBLISH.equals(methodTok) && getAccount().getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowPublishMethodInvite, false))) {
if (status.autoAddNew) {
if (mMailbox.getAccount().sameAccount(cur.getOrganizerAccount())) {
// Bug 100456 ZCO sends out invites before adding a calendar entry. If server adds
// Calendar entry and ZCO sees that before creating its entry, ZCO gets confused.
LOG.info("Mailbox %d Msg %d Don't create ORGANIZER calendar item for Invite %s", getMailboxId(), getId(), method);
} else {
int flags = 0;
// int flags = Flag.BITMASK_INDEXING_DEFERRED;
// mMailbox.incrementIndexDeferredCount(1);
int defaultFolder = cur.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
status.calItem = mMailbox.createCalendarItem(defaultFolder, flags, null, cur.getUid(), pm, cur, null);
calItemIsNew = true;
status.calItemFolderId = status.calItem.getFolderId();
}
}
} else {
LOG.info("Mailbox %d Message %d SKIPPING Invite %s b/c no CalendarItem could be found", getMailboxId(), getId(), method);
}
} else {
// bug 27887: Ignore when calendar request email somehow made a loop back to the
// organizer address. Necessary conditions are:
//
// 1) This mailbox is currently organizer.
// 2) ORGANIZER in the request is this mailbox.
// 3) User/COS preference doesn't explicitly allow it. (bug 29777)
//
// If ORGANIZER in the request is a different user this is an organizer change request,
// which should be allowed to happen.
boolean ignore = false;
Invite defInv = status.calItem.getDefaultInviteOrNull();
if (defInv != null && defInv.isOrganizer()) {
ZOrganizer org = cur.getOrganizer();
String orgAddress = org != null ? org.getAddress() : null;
if (acctMatcher.matches(orgAddress))
ignore = !acct.getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowCancelEmailToSelf, false);
}
if (ignore) {
ZimbraLog.calendar.info("Ignoring calendar request emailed from organizer to self, " + "possibly in a mail loop involving mailing lists and/or forwards; " + "calItemId=" + status.calItem.getId() + ", msgId=" + getId());
} else {
// the calendar item in the folder it's currently in.
if (status.addRevision)
status.calItem.snapshotRevision(false);
// If updating (but not canceling) an appointment in trash folder,
// use default calendar folder and discard all existing invites.
boolean discardExistingInvites = false;
int calFolderId = status.calItem.getFolderId();
if (!cur.isCancel() && status.calItem.inTrash()) {
discardExistingInvites = true;
if (status.calItem.getType() == MailItem.Type.TASK) {
calFolderId = Mailbox.ID_FOLDER_TASKS;
} else {
calFolderId = Mailbox.ID_FOLDER_CALENDAR;
}
}
// against the current appointment data.
if (invChanges == null && !cur.isCancel()) {
Invite prev = status.calItem.getInvite(cur.getRecurId());
if (prev == null) {
// If incoming invite is a brand-new exception instance, we have to compare
// against the series data, but adjusted to the RECURRENCE-ID of the instance.
Invite series = status.calItem.getInvite(null);
if (series != null)
prev = series.makeInstanceInvite(cur.getRecurId().getDt());
}
if (prev != null)
invChanges = new InviteChanges(prev, cur);
}
modifiedCalItem = status.calItem.processNewInvite(pm, cur, calFolderId, CalendarItem.NEXT_ALARM_KEEP_CURRENT, true, /* preserveAlarms */
discardExistingInvites, false);
status.calItemFolderId = calFolderId;
status.calItem.getFolder().updateHighestMODSEQ();
}
}
}
int calItemId = status.calItem != null ? status.calItem.getId() : CalendarItemInfo.CALITEM_ID_NONE;
CalendarItemInfo info = new CalendarItemInfo(calItemId, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
status.updatedMetadata = true;
if (status.calItem != null && (calItemIsNew || modifiedCalItem)) {
mMailbox.index.add(status.calItem);
}
success = true;
} finally {
if (!success && status.calItem != null) {
// Error occurred and the calItem in memory may be out of sync with the database.
// Uncache it here, because the error will be ignored by this method's caller.
getMailbox().uncache(status.calItem);
}
}
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class CalendarItem method processNewInviteReply.
/**
* @param updatePrevFolders - If set, update the record of previous folders
* @param itemIdGetter - Used in newly created pseudo exceptions
* @return false if the invite being updated is out of date
* @throws ServiceException
*/
protected boolean processNewInviteReply(Invite reply, String sender, boolean updatePrevFolders, Mailbox.ItemIdGetter itemIdGetter) 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, itemIdGetter);
}
if (updatePrevFolders) {
performSetPrevFoldersOperation(octxt);
}
saveMetadata();
return true;
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class Invite method getMatchingAttendee.
/**
* Find the (first) Attendee in our list that matches the passed-in account. If multiple attendees
* match this account (because an account can have multiple addresses), the address that matches
* the given identity (persona) id is returned. Account's default identity is used if identityId
* is null or invalid.
*
* @param acct
* @param identityId
* @return The first matching attendee
* @throws ServiceException
*/
public ZAttendee getMatchingAttendee(Account acct, String identityId) throws ServiceException {
Identity identity;
if (identityId != null) {
identity = acct.getIdentityById(identityId);
if (identity == null) {
ZimbraLog.calendar.warn("No such identity " + identityId + " for account " + acct.getName());
identity = acct.getDefaultIdentity();
}
} else {
identity = acct.getDefaultIdentity();
}
String identityEmail = identity.getAttr(Provisioning.A_zimbraPrefFromAddress);
ZAttendee acctMatch = null;
List<ZAttendee> attendees = getAttendees();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
for (ZAttendee at : attendees) {
String thisAtEmail = at.getAddress();
// Does this attendee match our identity?
if (identityEmail != null && identityEmail.equalsIgnoreCase(thisAtEmail))
return at;
if (acctMatch == null && acctMatcher.matches(thisAtEmail)) {
acctMatch = at;
// If we didn't have identity email for some reason, we have our best match.
if (identityEmail == null)
return at;
}
}
return acctMatch;
}
use of com.zimbra.cs.util.AccountUtil.AccountAddressMatcher in project zm-mailbox by Zimbra.
the class Invite method decodeMetadata.
/**
* This API is public for RedoLogging to call into it -- you probably don't want to call it from
* anywhere else!
*
* @param mailboxId
* @param meta
* @param calItem
* @param accountTZ
* @return
* @throws ServiceException
*/
public static Invite decodeMetadata(int mailboxId, Metadata meta, CalendarItem calItem, ICalTimeZone accountTZ) throws ServiceException {
byte btype = (byte) meta.getLong(FN_ITEMTYPE, -1);
MailItem.Type type = btype >= 0 ? MailItem.Type.of(btype) : MailItem.Type.APPOINTMENT;
String uid = meta.get(FN_UID, null);
int mailItemId = (int) meta.getLong(FN_INVMSGID);
int componentNum = (int) meta.getLong(FN_COMPNUM);
String classProp = meta.get(FN_CLASS, IcalXmlStrMap.CLASS_PUBLIC);
boolean classPropSetByMe = meta.getBool(FN_CLASS_SETBYME, false);
String status = meta.get(FN_STATUS, IcalXmlStrMap.STATUS_CONFIRMED);
String freebusy = meta.get(FN_APPT_FREEBUSY, null);
String transp = meta.get(FN_TRANSP, IcalXmlStrMap.TRANSP_OPAQUE);
boolean sentByMe = meta.getBool(FN_SENTBYME);
String fragment = meta.get(FN_FRAGMENT, "");
// default to false for backward compat
boolean descInMeta = meta.getBool(FN_DESC_IN_META, false);
String desc = descInMeta ? meta.get(FN_DESC, null) : null;
String descHtml = descInMeta ? meta.get(FN_DESC_HTML, null) : null;
long completed = meta.getLong(FN_COMPLETED, 0);
ParsedDateTime dtStart = null;
ParsedDateTime dtEnd = null;
ParsedDuration duration = null;
RecurId recurrenceId = null;
TimeZoneMap tzMap = Util.decodeFromMetadata(meta.getMap(FN_TZMAP), accountTZ);
Metadata metaRecur = meta.getMap(FN_RECURRENCE, true);
Recurrence.IRecurrence recurrence = null;
if (metaRecur != null) {
recurrence = Recurrence.decodeMetadata(metaRecur, tzMap);
}
String methodStr = meta.get(FN_METHOD, ICalTok.PUBLISH.toString());
if (ICalTok.CANCEL.toString().equals(methodStr))
status = IcalXmlStrMap.STATUS_CANCELLED;
int flags = (int) meta.getLong(FN_APPT_FLAGS, 0);
try {
// DtStart
dtStart = ParsedDateTime.parse(meta.get(FN_START, null), tzMap);
// DtEnd
dtEnd = ParsedDateTime.parse(meta.get(FN_END, null), tzMap);
if ((flags & APPT_FLAG_ALLDAY) != 0) {
// Fixup historic data with incorrect all-day start/end format.
if (dtStart != null)
dtStart.forceDateOnly();
if (dtEnd != null)
dtEnd.forceDateOnly();
}
// Duration
duration = ParsedDuration.parse(meta.get(FN_DURATION, null));
if (meta.containsKey(FN_RECUR_ID)) {
Metadata rdata = meta.getMap(FN_RECUR_ID);
recurrenceId = RecurId.decodeMetadata(rdata, tzMap);
}
} catch (ParseException e) {
throw ServiceException.FAILURE(String.format("Error parsing metadata for invite %s-%s in calItem %s", mailItemId, componentNum, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)"), e);
}
String name = meta.get(FN_NAME, "");
String loc = meta.get(FN_LOCATION, null);
// For existing invites with no partstat, default to ACCEPTED status.
String partStat = meta.get(FN_PARTSTAT, IcalXmlStrMap.PARTSTAT_ACCEPTED);
// For existing invites with no RSVP, default to true.
boolean rsvp = meta.getBool(FN_RSVP, true);
long dtstamp = meta.getLong(FN_DTSTAMP, 0);
long lastModified = meta.getLong(FN_LAST_MODIFIED, 0);
int seqno = (int) meta.getLong(FN_SEQ_NO, 0);
int lastFullSeqno = (int) meta.getLong(FN_LAST_FULL_SEQ_NO, seqno);
ZOrganizer org = null;
try {
Metadata metaOrg = meta.getMap(FN_ORGANIZER, true);
org = metaOrg != null ? new ZOrganizer(metaOrg) : null;
} catch (ServiceException e) {
sLog.warn("Problem decoding organizer for calItem %s invite %s-%s", (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum);
}
long numAts = meta.getLong(FN_NUM_ATTENDEES, 0);
ArrayList<ZAttendee> attendees = new ArrayList<ZAttendee>((int) numAts);
for (int i = 0; i < numAts; i++) {
try {
Metadata metaAttendee = meta.getMap(FN_ATTENDEE + i, true);
if (metaAttendee != null)
attendees.add(new ZAttendee(metaAttendee));
} catch (ServiceException e) {
ZimbraLog.calendar.warn("Problem decoding attendee %s for calendar item %s invite %s-%s", i, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum);
}
}
boolean isOrganizer = false;
if (meta.containsKey(FN_IS_ORGANIZER)) {
isOrganizer = meta.getBool(FN_IS_ORGANIZER);
} else {
// backward compat for invites created before FN_IS_ORGANIZER was introduced
if (org != null) {
String orgAddr = org.getAddress();
Account account = MailboxManager.getInstance().getMailboxById(mailboxId).getAccount();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(account);
isOrganizer = acctMatcher.matches(orgAddr);
} else {
// If there are other attendees, it's an Outlook POP/IMAP bug. If not,
// it's a properly formatted single-user event. See isOrganizer()
// method for more info.
isOrganizer = numAts < 1;
}
}
String priority = meta.get(FN_PRIORITY, null);
String pctComplete = meta.get(FN_PCT_COMPLETE, null);
List<String> comments = null;
int numComm = (int) meta.getLong(FN_NUM_COMMENTS, 0);
if (numComm > 0) {
comments = new ArrayList<String>(numComm);
for (int i = 0; i < numComm; i++) {
String comm = meta.get(FN_COMMENT + i, null);
if (comm != null)
comments.add(comm);
}
}
List<String> contacts = null;
int numContacts = (int) meta.getLong(FN_NUM_CONTACTS, 0);
if (numContacts > 0) {
contacts = new ArrayList<String>(numContacts);
for (int i = 0; i < numContacts; i++) {
String contact = meta.get(FN_CONTACT + i, null);
if (contact != null)
contacts.add(contact);
}
}
List<String> categories = null;
int numCat = (int) meta.getLong(FN_NUM_CATEGORIES, 0);
if (numCat > 0) {
categories = new ArrayList<String>(numCat);
for (int i = 0; i < numCat; i++) {
String cat = meta.get(FN_CATEGORY + i, null);
if (cat != null)
categories.add(cat);
}
}
Geo geo = null;
Metadata metaGeo = meta.getMap(FN_GEO, true);
if (metaGeo != null)
geo = Util.decodeGeoFromMetadata(metaGeo);
String url = meta.get(FN_URL, null);
Invite invite = new Invite(type, methodStr, tzMap, calItem, uid, status, priority, pctComplete, completed, freebusy, transp, classProp, dtStart, dtEnd, duration, recurrence, isOrganizer, org, attendees, name, loc, flags, partStat, rsvp, recurrenceId, dtstamp, lastModified, seqno, lastFullSeqno, mailboxId, mailItemId, componentNum, sentByMe, desc, descHtml, fragment, comments, categories, contacts, geo, url);
// a little hacky, but necessary
invite.mDescInMeta = descInMeta;
invite.setClassPropSetByMe(classPropSetByMe);
long numAlarms = meta.getLong(FN_NUM_ALARMS, 0);
for (int i = 0; i < numAlarms; i++) {
try {
Metadata metaAlarm = meta.getMap(FN_ALARM + i, true);
if (metaAlarm != null) {
Alarm alarm = Alarm.decodeMetadata(metaAlarm);
if (alarm != null)
invite.addAlarm(alarm);
}
} catch (ServiceException e) {
ZimbraLog.calendar.warn("Problem decoding alarm %s for calendar item %s invite %s-%s", i, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum, e);
}
}
List<ZProperty> xprops = Util.decodeXPropsFromMetadata(meta);
if (xprops != null) {
for (ZProperty xprop : xprops) {
boolean isHtmlDesc = false;
if (ICalTok.X_ALT_DESC.equals(xprop.getToken())) {
// Backward compat. We used to save X-ALT-DESC property as an x-prop. Now we use it
// for HTML description, when FMTTYPE=text/html.
ZParameter fmttype = xprop.getParameter(ICalTok.FMTTYPE);
if (fmttype != null && MimeConstants.CT_TEXT_HTML.equalsIgnoreCase(fmttype.getValue())) {
isHtmlDesc = true;
invite.mDescHtml = xprop.getValue();
}
}
if (!isHtmlDesc)
invite.addXProp(xprop);
}
}
invite.setDontIndexMimeMessage(meta.getBool(FN_DONT_INDEX_MM, false));
boolean localOnly = meta.getBool(FN_LOCAL_ONLY, false);
invite.setLocalOnly(localOnly);
invite.sanitize(false);
return invite;
}
Aggregations