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