use of com.zimbra.cs.account.CalendarResource in project zm-mailbox by Zimbra.
the class TestSetPassword method globalAdmin.
@Test
public void globalAdmin() throws Exception {
Account acct = createAcctAndSetPasswordpolicy();
CalendarResource cr = createCRAndSetPasswordpolicy();
Account adminAcct = provUtil.createGlobalAdmin(genAcctNameLocalPart("admin"), domain);
verifyOK(adminAcct, acct, true);
verifyOK(adminAcct, acct, false);
verifyOK(adminAcct, cr, true);
verifyOK(adminAcct, cr, false);
provUtil.deleteAccount(acct);
provUtil.deleteAccount(cr);
provUtil.deleteAccount(adminAcct);
}
use of com.zimbra.cs.account.CalendarResource in project zm-mailbox by Zimbra.
the class GetCalendarResource method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Provisioning prov = Provisioning.getInstance();
GetCalendarResourceRequest req = zsc.elementToJaxb(request);
boolean applyCos = !Boolean.FALSE.equals(req.getApplyCos());
CalendarResourceBy calresBy = req.getCalResource().getBy().toKeyCalendarResourceBy();
String value = req.getCalResource().getKey();
CalendarResource resource = prov.get(calresBy, value);
defendAgainstCalResourceHarvesting(resource, calresBy, value, zsc, Admin.R_getCalendarResourceInfo);
AdminAccessControl aac = checkCalendarResourceRight(zsc, resource, AdminRight.PR_ALWAYS_ALLOW);
Element response = zsc.createElement(AdminConstants.GET_CALENDAR_RESOURCE_RESPONSE);
Set<String> reqAttrs = getReqAttrs(req.getAttrs(), AttributeClass.calendarResource);
ToXML.encodeCalendarResource(response, resource, applyCos, reqAttrs, aac.getAttrRightChecker(resource));
return response;
}
use of com.zimbra.cs.account.CalendarResource in project zm-mailbox by Zimbra.
the class RenameCalendarResource method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Provisioning prov = Provisioning.getInstance();
RenameCalendarResourceRequest req = zsc.elementToJaxb(request);
String id = req.getId();
String newName = req.getNewName();
CalendarResource resource = prov.get(CalendarResourceBy.id, id);
defendAgainstCalResourceHarvesting(resource, CalendarResourceBy.id, id, zsc, Admin.R_renameCalendarResource);
String oldName = resource.getName();
// check if the admin can rename the calendar resource
checkAccountRight(zsc, resource, Admin.R_renameCalendarResource);
// check if the admin can "create calendar resource" in the domain (can be same or diff)
checkDomainRightByEmail(zsc, newName, Admin.R_createCalendarResource);
prov.renameCalendarResource(id, newName);
ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] { "cmd", "RenameCalendarResource", "name", oldName, "newName", newName }));
// get again with new name...
resource = prov.get(CalendarResourceBy.id, id);
if (resource == null) {
throw ServiceException.FAILURE("unable to get calendar resource after rename: " + id, null);
}
Element response = zsc.createElement(AdminConstants.RENAME_CALENDAR_RESOURCE_RESPONSE);
ToXML.encodeCalendarResource(response, resource, true);
return response;
}
use of com.zimbra.cs.account.CalendarResource 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);
}
}
}
}
}
}
}
}
use of com.zimbra.cs.account.CalendarResource in project zm-mailbox by Zimbra.
the class SendInviteReply method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Mailbox mbox = getRequestedMailbox(zsc);
Account acct = getRequestedAccount(zsc);
Account authAcct = getAuthenticatedAccount(zsc);
boolean isAdmin = zsc.isUsingAdminPrivileges();
OperationContext octxt = getOperationContext(zsc, context);
boolean onBehalfOf = isOnBehalfOfRequest(zsc);
ItemId iid = new ItemId(request.getAttribute(MailConstants.A_ID), zsc);
int compNum = (int) request.getAttributeLong(MailConstants.A_CAL_COMPONENT_NUM);
String verbStr = request.getAttribute(MailConstants.A_VERB);
Verb verb = CalendarMailSender.parseVerb(verbStr);
boolean isDecline = CalendarMailSender.VERB_DECLINE.equals(verb);
boolean updateOrg = request.getAttributeBool(MailConstants.A_CAL_UPDATE_ORGANIZER, true);
// Get the identity/persona being used in the reply. It is set at the request level, but
// let's also look for it in the <m> child element too, because that is the precedent in
// the SendMsg request. For SendInviteReply we have to insist it at request level because
// <m> is an optional element.
String identityId = request.getAttribute(MailConstants.A_IDENTITY_ID, null);
if (identityId == null) {
Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
if (msgElem != null)
identityId = msgElem.getAttribute(MailConstants.A_IDENTITY_ID, null);
}
Element response = getResponseElement(zsc);
boolean intendedForMe = true;
Invite oldInv = null;
int calItemId;
int inviteMsgId;
CalendarItem calItem = null;
boolean wasInTrash = false;
String stat = null;
// calendar item (id="aaaa-nnnn") --- work in both cases
if (iid.hasSubpart()) {
// directly accepting the calendar item
calItemId = iid.getId();
inviteMsgId = iid.getSubpartId();
calItem = safeGetCalendarItemById(mbox, octxt, iid.getId());
if (calItem == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
oldInv = calItem.getInvite(inviteMsgId, compNum);
} else {
// accepting the message: go find the calendar item and then the invite
inviteMsgId = iid.getId();
Message msg = mbox.getMessageById(octxt, inviteMsgId);
Message.CalendarItemInfo info = msg.getCalendarItemInfo(compNum);
if (info == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
String intendedFor = msg.getCalendarIntendedFor();
Account intendedAcct = null;
if (intendedFor != null) {
try {
InternetAddress intendedForAddr = new JavaMailInternetAddress(intendedFor);
intendedAcct = Provisioning.getInstance().get(AccountBy.name, intendedForAddr.getAddress());
} catch (AddressException e) {
throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " is invalid", e);
}
if (intendedAcct == null) {
throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " was not found", null);
}
// Special case: intended account = me.
if (intendedAcct.equals(mbox.getAccount()))
intendedAcct = null;
else
intendedForMe = false;
}
if (intendedAcct != null) {
// trace logging: let's just indicate we're replying to a remote appointment
ZimbraLog.calendar.info("<SendInviteReply> (remote mbox) id=%s, verb=%s, notifyOrg=%s", new ItemIdFormatter(zsc).formatItemId(iid), verb.toString(), Boolean.toString(updateOrg));
// Replying to a remote appointment
calItem = null;
calItemId = 0;
ZMailbox zmbx = getRemoteZMailbox(octxt, authAcct, intendedAcct);
// Try to add the appointment to remote mailbox.
AddInviteResult addInviteResult = sendAddInvite(zmbx, octxt, msg);
if (addInviteResult == null)
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
// Forward the reply request.
remoteSendInviteReply(zmbx, request, addInviteResult);
} else {
// Replying to a local appointment
if (info.getInvite() != null) {
calItem = mbox.getCalendarItemByUid(octxt, info.getInvite().getUid());
wasInTrash = calItem != null && calItem.inTrash();
if (calItem != null && !wasInTrash) {
Invite newInv = info.getInvite();
// If appointment exists, check if our invite has been outdated.
Invite curr = calItem.getInvite(newInv.getRecurId());
if (curr != null && !newInv.isSameOrNewerVersion(curr))
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
}
Invite inv = info.getInvite().newCopy();
Invite.setDefaultAlarm(inv, acct);
inv.setMailItemId(inviteMsgId);
// (TODO: Is it better to delete the existing appointment/instance when declining?)
if (calItem != null || !isDecline) {
// Add the invite. This will either create or update the appointment.
int folder;
boolean untrashing = wasInTrash && !isDecline;
if (calItem == null || untrashing) {
// If appointment/task doesn't exist, create in default folder.
// If it exists but is in Trash and is not a decline, move it out of Trash.
// If it's in trash and we're declining, leave it in trash.
folder = inv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
} else {
folder = calItem.getFolderId();
}
ParsedMessage pm = new ParsedMessage(msg.getMimeMessage(false), false);
AddInviteData aid = mbox.addInvite(octxt, inv, folder, pm, false, untrashing, true);
if (aid == null)
throw ServiceException.FAILURE("Could not create/update calendar item", null);
calItemId = aid.calItemId;
// Refetch updated item.
calItem = safeGetCalendarItemById(mbox, octxt, aid.calItemId);
if (calItem == null)
throw ServiceException.FAILURE("Could not refetch created/updated calendar item", null);
} else {
calItemId = 0;
}
oldInv = inv;
} else if (info.calItemCreated()) {
// legacy case (before we added Invite info to Message metadata)
calItem = safeGetCalendarItemById(mbox, octxt, info.getCalendarItemId());
if (calItem == null)
throw ServiceException.FAILURE("Missing invite data", null);
// Must be for this mailbox
calItemId = info.getCalendarItemId().getId();
wasInTrash = calItem.inTrash();
oldInv = calItem.getInvite(inviteMsgId, compNum);
} else {
throw ServiceException.FAILURE("Missing invite data", null);
}
}
}
if (intendedForMe) {
if (oldInv == null)
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
if (calItem != null && (mbox.getEffectivePermissions(octxt, calItemId, MailItem.Type.UNKNOWN) & ACL.RIGHT_ACTION) == 0) {
throw ServiceException.PERM_DENIED("You do not have ACTION rights for CalendarItem " + calItemId);
}
// check if invite organizer requested rsvp or not
if (oldInv.hasRsvp()) {
updateOrg = updateOrg && oldInv.getRsvp();
}
// Don't allow creating/editing a private appointment on behalf of another user,
// unless that other user is a calendar resource.
boolean allowPrivateAccess = calItem != null ? calItem.allowPrivateAccess(authAcct, isAdmin) : true;
boolean isCalendarResource = acct instanceof CalendarResource;
if (!allowPrivateAccess && !oldInv.isPublic() && !isCalendarResource)
throw ServiceException.PERM_DENIED("Cannot reply to a private appointment/task on behalf of another user");
// see if there is a specific Exception being referenced by this reply...
Element exc = request.getOptionalElement(MailConstants.E_CAL_EXCEPTION_ID);
ParsedDateTime exceptDt = null;
if (exc != null) {
TimeZoneMap tzmap = oldInv.getTimeZoneMap();
Element tzElem = request.getOptionalElement(MailConstants.E_CAL_TZ);
ICalTimeZone tz = null;
if (tzElem != null) {
tz = CalendarUtils.parseTzElement(tzElem);
tzmap.add(tz);
}
exceptDt = CalendarUtils.parseDateTime(exc, tzmap);
} else if (oldInv.hasRecurId()) {
exceptDt = oldInv.getRecurId().getDt();
}
// trace logging
String calItemIdStr = calItem != null ? Integer.toString(calItem.getId()) : "none";
String folderIdStr = calItem != null ? Integer.toString(calItem.getFolderId()) : "none";
if (exceptDt == null)
ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid());
else
ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s, recurId=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid(), exceptDt.getUtcString());
// exception instance first. Then reply to it.
if (calItem != null && oldInv.isRecurrence() && exceptDt != null) {
Invite localException = oldInv.makeInstanceInvite(exceptDt);
long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
localException.setDtStamp(now);
String partStat = verb.getXmlPartStat();
localException.setPartStat(partStat);
ZAttendee at = localException.getMatchingAttendee(acct, identityId);
if (at != null)
at.setPartStat(partStat);
// Carry over the MimeMessage/ParsedMessage to preserve any attachments.
MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
ParsedMessage pm = mmInv != null ? new ParsedMessage(mmInv, false) : null;
int folder;
boolean untrashing = wasInTrash && !isDecline;
if (untrashing) {
// If it exists but is in Trash and is not a decline, move it out of Trash.
// If it's in trash and we're declining, leave it in trash.
folder = localException.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
} else {
folder = calItem.getFolderId();
}
mbox.addInvite(octxt, localException, folder, pm, true, untrashing, true);
// Refetch the updated calendar item and set oldInv to refetched local exception instance.
calItem = safeGetCalendarItemById(mbox, octxt, calItemId);
if (calItem == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
oldInv = calItem.getInvite(new RecurId(exceptDt, RecurId.RANGE_NONE));
}
ZAttendee att = oldInv.getMatchingAttendee(mbox.getAccount());
if (att != null) {
stat = att.getPartStat();
}
if (updateOrg && oldInv.hasOrganizer()) {
Locale locale;
Account organizer = oldInv.getOrganizerAccount();
if (organizer != null)
locale = organizer.getLocale();
else
locale = !onBehalfOf ? acct.getLocale() : authAcct.getLocale();
String subject;
if (!allowPrivateAccess && !oldInv.isPublic())
subject = L10nUtil.getMessage(MsgKey.calendarSubjectWithheld, locale);
else
subject = oldInv.getName();
String replySubject = CalendarMailSender.getReplySubject(verb, subject, locale);
CalSendData csd = new CalSendData();
csd.mOrigId = new ItemId(mbox, oldInv.getMailItemId());
csd.mReplyType = MailSender.MSGTYPE_REPLY;
csd.mInvite = CalendarMailSender.replyToInvite(acct, identityId, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, exceptDt);
ZVCalendar iCal = csd.mInvite.newToICalendar(true);
ParseMimeMessage.MimeMessageData parsedMessageData = new ParseMimeMessage.MimeMessageData();
// did they specify a custom <m> message? If so, then we don't have to build one...
Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
if (msgElem != null) {
String text = ParseMimeMessage.getTextPlainContent(msgElem);
String html = ParseMimeMessage.getTextHtmlContent(msgElem);
iCal.addDescription(text, html);
MimeBodyPart[] mbps = new MimeBodyPart[1];
mbps[0] = CalendarMailSender.makeICalIntoMimePart(iCal);
// the <inv> element is *NOT* allowed -- we always build it manually
// based on the params to the <SendInviteReply> and stick it in the
// mbps (additionalParts) parameter...
csd.mMm = ParseMimeMessage.parseMimeMsgSoap(zsc, octxt, mbox, msgElem, mbps, ParseMimeMessage.NO_INV_ALLOWED_PARSER, parsedMessageData);
} else {
// build a default "Accepted" response
if (!(acct instanceof CalendarResource)) {
csd.mMm = CalendarMailSender.createDefaultReply(acct, identityId, authAcct, identityId, isAdmin, onBehalfOf, calItem, oldInv, null, replySubject, verb, null, iCal);
} else {
// different template for calendar resources
RecurId rid = oldInv.getRecurId();
ParsedDateTime ridDt = rid != null ? rid.getDt() : null;
Invite replyInv = CalendarMailSender.replyToInvite(acct, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, ridDt);
MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
csd.mMm = CalendarMailSender.createResourceAutoReply(octxt, identityId, identityId, mbox, verb, false, null, calItem, oldInv, new Invite[] { replyInv }, mmInv, true);
}
}
int apptFolderId;
if (calItem != null)
apptFolderId = calItem.getFolderId();
else
apptFolderId = oldInv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
MailSendQueue sendQueue = new MailSendQueue();
try {
if (stat != null && IcalXmlStrMap.PARTSTAT_DECLINED.equals(stat) && !CalendarMailSender.VERB_DECLINE.equals(verb)) {
sendCalendarMessage(zsc, octxt, apptFolderId, acct, mbox, csd, response, sendQueue, true);
} else {
sendCalendarMessage(zsc, octxt, apptFolderId, acct, mbox, csd, response, sendQueue);
}
} finally {
sendQueue.send();
}
}
RecurId recurId = null;
if (exceptDt != null) {
recurId = new RecurId(exceptDt, RecurId.RANGE_NONE);
}
ZAttendee me = oldInv.getMatchingAttendee(acct);
String cnStr = null;
String addressStr = acct.getName();
String role = IcalXmlStrMap.ROLE_OPT_PARTICIPANT;
int seqNo = oldInv.getSeqNo();
long dtStamp = oldInv.getDTStamp();
if (me != null) {
if (me.hasCn()) {
cnStr = me.getCn();
}
addressStr = me.getAddress();
if (me.hasRole()) {
role = me.getRole();
}
}
if (calItem != null)
mbox.modifyPartStat(octxt, calItemId, recurId, cnStr, addressStr, null, role, verb.getXmlPartStat(), Boolean.FALSE, seqNo, dtStamp);
}
// move the invite to the Trash if the user wants it
if (deleteInviteOnReply(acct)) {
try {
if (onBehalfOf) {
// HACK: Run the move in the context of the organizer
// mailbox because the authenticated account doesn't
// have rights on Inbox and Trash folders.
octxt = new OperationContext(mbox);
}
mbox.move(octxt, inviteMsgId, MailItem.Type.MESSAGE, Mailbox.ID_FOLDER_TRASH);
} catch (MailServiceException.NoSuchItemException nsie) {
ZimbraLog.calendar.debug("can't move nonexistent invite to Trash: " + inviteMsgId);
}
}
return response;
}
Aggregations