public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Account account = getRequestedAccount(zsc); if (!canModifyOptions(zsc, account)) throw ServiceException.PERM_DENIED("can not modify options"); HashMap<String, Object> prefs = new HashMap<String, Object>(); Map<String, Set<String>> name2uniqueAttrValues = new HashMap<String, Set<String>>(); for (KeyValuePair kvp : request.listKeyValuePairs(AccountConstants.E_PREF, AccountConstants.A_NAME)) { String name = kvp.getKey(), value = kvp.getValue(); char ch = name.length() > 0 ? name.charAt(0) : 0; int offset = ch == '+' || ch == '-' ? 1 : 0; if (!name.startsWith(PREF_PREFIX, offset)) throw ServiceException.INVALID_REQUEST("pref name must start with " + PREF_PREFIX, null); AttributeInfo attrInfo = AttributeManager.getInstance().getAttributeInfo(name.substring(offset)); if (attrInfo == null) { throw ServiceException.INVALID_REQUEST("no such attribute: " + name, null); } if (attrInfo.isCaseInsensitive()) { String valueLowerCase = Strings.nullToEmpty(value).toLowerCase(); if (name2uniqueAttrValues.get(name) == null) { Set<String> set = new HashSet<String>(); set.add(valueLowerCase); name2uniqueAttrValues.put(name, set); StringUtil.addToMultiMap(prefs, name, value); } else { Set<String> set = name2uniqueAttrValues.get(name); if (set.add(valueLowerCase)) { StringUtil.addToMultiMap(prefs, name, value); } } } else { StringUtil.addToMultiMap(prefs, name, value); } } if (prefs.containsKey(Provisioning.A_zimbraPrefMailForwardingAddress)) { if (!account.getBooleanAttr(Provisioning.A_zimbraFeatureMailForwardingEnabled, false)) throw ServiceException.PERM_DENIED("forwarding not enabled"); } // call modifyAttrs and pass true to checkImmutable Provisioning.getInstance().modifyAttrs(account, prefs, true, zsc.getAuthToken()); Element response = zsc.createElement(AccountConstants.MODIFY_PREFS_RESPONSE); return response; }
public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Provisioning prov = Provisioning.getInstance(); String id = request.getAttribute(AdminConstants.E_ID); Domain domain = prov.get(DomainBy.id, id); if (domain == null) throw AccountServiceException.NO_SUCH_DOMAIN(id); if (domain.isShutdown()) throw ServiceException.PERM_DENIED( "can not access domain, domain is in " + domain.getDomainStatusAsString() + " status"); checkRight(zsc, context, domain, Admin.R_deleteDomain); String name = domain.getName(); prov.deleteDomain(id); ZimbraLog.security.info( ZimbraLog.encodeAttrs(new String[] {"cmd", "DeleteDomain", "name", name, "id", id})); Element response = zsc.createElement(AdminConstants.DELETE_DOMAIN_RESPONSE); return response; }
private static Account validateAuthTokenInternal( Provisioning prov, AuthToken at, boolean addToLoggingContext) throws ServiceException { if (prov == null) { prov = Provisioning.getInstance(); } if (at.isExpired()) { throw ServiceException.AUTH_EXPIRED(); } // make sure that the authenticated account is still active and has not been deleted since the // last request String acctId = at.getAccountId(); Account acct = prov.get(AccountBy.id, acctId, at); if (acct == null) { throw ServiceException.AUTH_EXPIRED("account " + acctId + " not found"); } if (addToLoggingContext) { ZimbraLog.addAccountNameToContext(acct.getName()); } if (!acct.checkAuthTokenValidityValue(at)) { throw ServiceException.AUTH_EXPIRED("invalid validity value"); } boolean delegatedAuth = at.isDelegatedAuth(); String acctStatus = acct.getAccountStatus(prov); if (!delegatedAuth && !Provisioning.ACCOUNT_STATUS_ACTIVE.equals(acctStatus)) { throw ServiceException.AUTH_EXPIRED("account not active"); } // if using delegated auth, make sure the "admin" is really an active admin account if (delegatedAuth) { // note that delegated auth allows access unless the account's in maintenance mode if (Provisioning.ACCOUNT_STATUS_MAINTENANCE.equals(acctStatus)) { throw ServiceException.AUTH_EXPIRED("delegated account in MAINTENANCE mode"); } Account admin = prov.get(AccountBy.id, at.getAdminAccountId()); if (admin == null) { throw ServiceException.AUTH_EXPIRED( "delegating account " + at.getAdminAccountId() + " not found"); } boolean isAdmin = AdminAccessControl.isAdequateAdminAccount(admin); if (!isAdmin) { throw ServiceException.PERM_DENIED("not an admin for delegated auth"); } if (!Provisioning.ACCOUNT_STATUS_ACTIVE.equals(admin.getAccountStatus(prov))) { throw ServiceException.AUTH_EXPIRED("delegating account is not active"); } } return acct; }
/** * Determines the set of {@link Message}s to be deleted from this <code>Conversation</code>. * Assembles a new {@link PendingDelete} object encapsulating the data on the items to be deleted. * * <p>A message will be deleted unless: * * <ul> * <li>The caller lacks {@link ACL#RIGHT_DELETE} permission on the <code>Message</code>. * <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the * <code>Message</code>. * <li>The caller has specified the maximum change number they know about, and the * (modification/content) change number on the <code>Message</code> is greater. * </ul> * * As a result of all these constraints, no messages may actually be deleted. * * @throws ServiceException The following error codes are possible: * <ul> * <li><code>mail.MODIFY_CONFLICT</code> - if the caller specified a max change number and a * modification check, and the modified change number of the <code>Message</code> is * greater * <li><code>service.FAILURE</code> - if there's a database failure fetching the message * list * </ul> */ @Override PendingDelete getDeletionInfo() throws ServiceException { PendingDelete info = new PendingDelete(); info.rootId = mId; info.itemIds.add(getType(), mId); if (mData.size == 0) return info; List<Message> msgs = getMessages(); TargetConstraint tcon = mMailbox.getOperationTargetConstraint(); boolean excludeModify = false, excludeAccess = false; for (Message child : msgs) { // silently skip explicitly excluded messages, PERMISSION_DENIED messages, and MODIFY_CONFLICT // messages if (!TargetConstraint.checkItem(tcon, child)) continue; else if (!child.canAccess(ACL.RIGHT_DELETE)) excludeAccess = true; else if (!child.checkChangeID()) excludeModify = true; else info.add(child.getDeletionInfo()); } int totalDeleted = info.itemIds.size(); if (totalDeleted == 1) { // if all messages have been excluded, some for "error" reasons, throw an exception if (excludeAccess) throw ServiceException.PERM_DENIED("you do not have sufficient permissions"); if (excludeModify) throw MailServiceException.MODIFY_CONFLICT(); } if (totalDeleted != msgs.size() + 1) info.incomplete = true; return info; }
public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Account account = getRequestedAccount(zsc); if (!canAccessAccount(zsc, account)) throw ServiceException.PERM_DENIED("can not access account"); Element response = zsc.createElement(AccountConstants.GET_AVAILABLE_CSV_FORMATS_RESPONSE); for (String format : ContactCSV.getAllFormatNames()) response.addElement(AccountConstants.E_CSV).addAttribute(AccountConstants.A_NAME, format); return response; }
/** * check the checkRight right * * <p>check if the authed admin has the checkRight right on the user/group it is checking right * for. * * @param zsc * @param granteeType * @param granteeBy * @param grantee * @return whether the checkRight right is checked * @throws ServiceException */ protected boolean checkCheckRightRight( ZimbraSoapContext zsc, GranteeType granteeType, GranteeBy granteeBy, String grantee, boolean granteeCanBeExternalEmailAddr) throws ServiceException { NamedEntry granteeEntry = null; try { granteeEntry = GranteeType.lookupGrantee(Provisioning.getInstance(), granteeType, granteeBy, grantee); } catch (ServiceException e) { // grantee to check could be an external email address ZimbraLog.acl.debug("unable to find grantee", e); } if (granteeEntry != null) { // call checkRight instead of checkAccountRight because there is no // backward compatibility issue for this SOAP. // // Note: granteeEntry is the target for the R_checkRight{Usr}/{Grp} right here if (granteeType == GranteeType.GT_USER) checkRight(zsc, granteeEntry, Admin.R_checkRightUsr); else if (granteeType == GranteeType.GT_GROUP) checkRight(zsc, granteeEntry, Admin.R_checkRightGrp); else throw ServiceException.PERM_DENIED( "invalid grantee type for check right:" + granteeType.getCode()); return true; } else { if (granteeCanBeExternalEmailAddr) return false; else throw ServiceException.PERM_DENIED("unable to check checkRight right for " + grantee); } }
@Override public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Element mreq = request.getElement(AdminConstants.E_MAILBOX); String accountId = mreq.getAttribute(AdminConstants.A_ACCOUNTID); Account account = Provisioning.getInstance().get(AccountBy.id, accountId, zsc.getAuthToken()); if (account == null) { // Note: isDomainAdminOnly *always* returns false for pure ACL based AccessManager if (isDomainAdminOnly(zsc)) { throw ServiceException.PERM_DENIED( "account doesn't exist, unable to determine authorization"); } // still need to check right, since we don't have an account, the // last resort is checking the global grant. Do this for now until // there is complain. checkRight(zsc, context, null, Admin.R_deleteAccount); ZimbraLog.account.warn( "DeleteMailbox: account doesn't exist: " + accountId + " (still deleting mailbox)"); } else { checkAccountRight(zsc, account, Admin.R_deleteAccount); } Mailbox mbox = MailboxManager.getInstance().getMailboxByAccountId(accountId, false); int mailboxId = -1; if (mbox != null) { mailboxId = mbox.getId(); mbox.deleteMailbox(); } String idString = (mbox == null) ? "<no mailbox for account " + accountId + ">" : Integer.toString(mailboxId); ZimbraLog.security.info( ZimbraLog.encodeAttrs(new String[] {"cmd", "DeleteMailbox", "id", idString})); Element response = zsc.createElement(AdminConstants.DELETE_MAILBOX_RESPONSE); if (mbox != null) response .addElement(AdminConstants.E_MAILBOX) .addAttribute(AdminConstants.A_MAILBOXID, mailboxId); return response; }
public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); if (!AccessControlUtil.isGlobalAdmin(getAuthenticatedAccount(zsc))) { throw ServiceException.PERM_DENIED("only global admin is allowed"); } Map<String, Long> domainAggrQuotaUsed = new HashMap<String, Long>(); SearchAccountsOptions searchOpts = new SearchAccountsOptions(); searchOpts.setIncludeType(IncludeType.ACCOUNTS_ONLY); searchOpts.setMakeObjectOpt(MakeObjectOpt.NO_DEFAULTS); Provisioning prov = Provisioning.getInstance(); List<NamedEntry> accounts = prov.searchAccountsOnServer(prov.getLocalServer(), searchOpts); Map<String, Long> acctQuotaUsed = MailboxManager.getInstance().getMailboxSizes(accounts); for (NamedEntry ne : accounts) { if (!(ne instanceof Account)) { continue; } Account acct = (Account) ne; Long acctQuota = acctQuotaUsed.get(acct.getId()); if (acctQuota == null) { acctQuota = 0L; } String domainId = acct.getDomainId(); if (domainId == null) { continue; } Long aggrQuota = domainAggrQuotaUsed.get(domainId); domainAggrQuotaUsed.put(domainId, aggrQuota == null ? acctQuota : aggrQuota + acctQuota); } Element response = zsc.createElement(AdminConstants.GET_AGGR_QUOTA_USAGE_ON_SERVER_RESPONSE); for (String domainId : domainAggrQuotaUsed.keySet()) { Domain domain = prov.getDomainById(domainId); Element domainElt = response.addElement(AdminConstants.E_DOMAIN); domainElt.addAttribute(AdminConstants.A_NAME, domain.getName()); domainElt.addAttribute(AdminConstants.A_ID, domainId); domainElt.addAttribute(AdminConstants.A_QUOTA_USED, domainAggrQuotaUsed.get(domainId)); } return response; }
/** * Updates the unread state of all messages in the conversation. Persists the change to the * database and cache, and also updates the unread counts for the affected items' {@link Folder}s * and {@link Tag}s appropriately. * * <p>Messages in the conversation are omitted from this operation if one or more of the following * applies: * * <ul> * <li>The caller lacks {@link ACL#RIGHT_WRITE} permission on the <code>Message</code>. * <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the * <code>Message</code>. * <li>The caller has specified the maximum change number they know about, and the * (modification/content) change number on the <code>Message</code> is greater. * </ul> * * As a result of all these constraints, no messages may actually be marked read/unread. * * @perms {@link ACL#RIGHT_WRITE} on all the messages */ @Override void alterUnread(boolean unread) throws ServiceException { markItemModified(Change.MODIFIED_UNREAD); boolean excludeAccess = false; // Decrement the in-memory unread count of each message. Each message will // then implicitly decrement the unread count for its conversation, folder // and tags. TargetConstraint tcon = mMailbox.getOperationTargetConstraint(); List<Integer> targets = new ArrayList<Integer>(); for (Message msg : getMessages()) { // skip messages that don't need to be changed, or that the client can't modify, doesn't know // about, or has explicitly excluded if (msg.isUnread() == unread) { continue; } else if (!msg.canAccess(ACL.RIGHT_WRITE)) { excludeAccess = true; continue; } else if (!msg.checkChangeID() || !TargetConstraint.checkItem(tcon, msg)) { continue; } int delta = unread ? 1 : -1; msg.updateUnread(delta, msg.isTagged(Flag.ID_FLAG_DELETED) ? delta : 0); msg.mData.metadataChanged(mMailbox); targets.add(msg.getId()); } // mark the selected messages in this conversation as read in the database if (targets.isEmpty()) { if (excludeAccess) throw ServiceException.PERM_DENIED("you do not have sufficient permissions"); } else { DbMailItem.alterUnread(mMailbox, targets, unread); } }
/** * Moves all the conversation's {@link Message}s to a different {@link Folder}. Persists the * change to the database and the in-memory cache. Updates all relevant unread counts, folder * sizes, etc. * * <p>Messages moved to the Trash folder are automatically marked read. Conversations moved to the * Junk folder will not receive newly-delivered messages. * * <p>Messages in the conversation are omitted from this operation if one or more of the following * applies: * * <ul> * <li>The caller lacks {@link ACL#RIGHT_WRITE} permission on the <code>Message</code>. * <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the * <code>Message</code>. * <li>The caller has specified the maximum change number they know about, and the * (modification/content) change number on the <code>Message</code> is greater. * </ul> * * As a result of all these constraints, no messages may actually be moved. * * @perms {@link ACL#RIGHT_INSERT} on the target folder, {@link ACL#RIGHT_DELETE} on the messages' * source folders */ @Override boolean move(Folder target) throws ServiceException { if (!target.canContain(TYPE_MESSAGE)) throw MailServiceException.CANNOT_CONTAIN(); markItemModified(Change.UNMODIFIED); List<Message> msgs = getMessages(); TargetConstraint tcon = mMailbox.getOperationTargetConstraint(); boolean toTrash = target.inTrash(); int oldUnread = 0; for (Message msg : msgs) if (msg.isUnread()) oldUnread++; // if mData.unread is wrong, what to do? right now, always use the calculated value mData.unreadCount = oldUnread; boolean excludeAccess = false; List<Integer> markedRead = new ArrayList<Integer>(); List<Message> moved = new ArrayList<Message>(); List<Message> indexUpdated = new ArrayList<Message>(); for (Message msg : msgs) { Folder source = msg.getFolder(); // skip messages that don't need to be moved, or that the client can't modify, doesn't know // about, or has explicitly excluded if (source.getId() == target.getId()) { continue; } else if (!source.canAccess(ACL.RIGHT_DELETE)) { excludeAccess = true; continue; } else if (target.getId() != Mailbox.ID_FOLDER_TRASH && target.getId() != Mailbox.ID_FOLDER_SPAM && !target.canAccess(ACL.RIGHT_INSERT)) { excludeAccess = true; continue; } else if (!msg.checkChangeID() || !TargetConstraint.checkItem(tcon, msg)) { continue; } boolean isDeleted = msg.isTagged(Flag.ID_FLAG_DELETED); if (msg.isUnread()) { if (!toTrash || msg.inTrash()) { source.updateUnread(-1, isDeleted ? -1 : 0); target.updateUnread(1, isDeleted ? 1 : 0); } else { // unread messages moved from Mailbox to Trash need to be marked read: // update cached unread counts (message, conversation, folder, tags) msg.updateUnread(-1, isDeleted ? -1 : 0); // note that we need to update this message in the DB markedRead.add(msg.getId()); } } // moved an item out of the spam folder, need to index it if (msg.inSpam() && !target.inSpam()) { if (msg.isIndexed() && msg.getIndexId() != -1) { msg.indexIdChanged(msg.getId()); indexUpdated.add(msg); } } // if a draft is being moved to Trash then remove any "send-later" info from it if (toTrash && msg.isDraft()) msg.setDraftAutoSendTime(0); // handle folder message counts source.updateSize(-1, isDeleted ? -1 : 0, -msg.getTotalSize()); target.updateSize(1, isDeleted ? 1 : 0, msg.getTotalSize()); moved.add(msg); msg.folderChanged(target, 0); } // mark unread messages moved from Mailbox to Trash/Spam as read in the DB if (!markedRead.isEmpty()) DbMailItem.alterUnread(target.getMailbox(), markedRead, false); if (moved.isEmpty()) { if (excludeAccess) throw ServiceException.PERM_DENIED("you do not have sufficient permissions"); } else { // moving a conversation to spam closes it if (target.inSpam()) detach(); if (ZimbraLog.mailop.isInfoEnabled()) { StringBuilder ids = new StringBuilder(); for (int i = 0; i < moved.size(); i++) { if (i > 0) { ids.append(','); } ids.append(moved.get(i).getId()); } ZimbraLog.mailop.info( "Moving %s to %s. Affected message ids: %s.", getMailopContext(this), getMailopContext(target), ids); } DbMailItem.setFolder(moved, target); if (!indexUpdated.isEmpty()) { DbMailItem.setIndexIds(mMailbox, indexUpdated); for (Message msg : indexUpdated) { mMailbox.queueForIndexing(msg, false, null); } } } return !moved.isEmpty(); }
/** * Tags or untags all messages in the conversation. Persists the change to the database and cache. * If the conversation includes at least one unread {@link Message} whose tagged state is * changing, updates the {@link Tag}'s unread count appropriately. * * <p>Messages in the conversation are omitted from this operation if one or more of the following * applies: * * <ul> * <li>The caller lacks {@link ACL#RIGHT_WRITE} permission on the <code>Message</code>. * <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the * <code>Message</code>. * <li>The caller has specified the maximum change number they know about, and the * (modification/content) change number on the <code>Message</code> is greater. * </ul> * * As a result of all these constraints, no messages may actually be tagged/untagged. * * @perms {@link ACL#RIGHT_WRITE} on all the messages */ @Override void alterTag(Tag tag, boolean add) throws ServiceException { if (tag == null) throw ServiceException.FAILURE("missing tag argument", null); if (!add && !isTagged(tag)) return; if (tag.getId() == Flag.ID_FLAG_UNREAD) throw ServiceException.FAILURE("unread state must be set with alterUnread", null); // don't let the user tag things as "has attachments" or "draft" if (tag instanceof Flag && (tag.getBitmask() & Flag.FLAG_SYSTEM) != 0) throw MailServiceException.CANNOT_TAG(tag, this); markItemModified(tag instanceof Flag ? Change.MODIFIED_FLAGS : Change.MODIFIED_TAGS); TargetConstraint tcon = mMailbox.getOperationTargetConstraint(); boolean excludeAccess = false; List<Message> msgs = getMessages(); List<Integer> targets = new ArrayList<Integer>(msgs.size()); for (Message msg : msgs) { // skip messages that don't need to be changed, or that the client can't modify, doesn't know // about, or has explicitly excluded if (msg.isTagged(tag) == add) { continue; } else if (!msg.canAccess(ACL.RIGHT_WRITE)) { excludeAccess = true; continue; } else if (!msg.checkChangeID() || !TargetConstraint.checkItem(tcon, msg)) { continue; } else if (add && !tag.canTag(msg)) { throw MailServiceException.CANNOT_TAG(tag, this); } targets.add(msg.getId()); msg.tagChanged(tag, add); // since we're adding/removing a tag, the tag's unread count may change int delta = add ? 1 : -1; if (tag.trackUnread() && msg.isUnread()) tag.updateUnread(delta, isTagged(Flag.ID_FLAG_DELETED) ? delta : 0); // if we're adding/removing the \Deleted flag, update the folder and tag "deleted" and // "deleted unread" counts if (tag.getId() == Flag.ID_FLAG_DELETED) { getFolder().updateSize(0, delta, 0); // note that Message.updateUnread() calls updateTagUnread() if (msg.isUnread()) msg.updateUnread(0, delta); } } if (targets.isEmpty()) { if (excludeAccess) throw ServiceException.PERM_DENIED("you do not have sufficient permissions"); } else { if (ZimbraLog.mailop.isDebugEnabled()) { String operation = add ? "Setting" : "Unsetting"; ZimbraLog.mailop.debug( "%s %s for %s. Affected ids: %s", operation, getMailopContext(tag), getMailopContext(this), StringUtil.join(",", targets)); } recalculateCounts(msgs); DbMailItem.alterTag(tag, targets, add); } }