Java tutorial
/* * ***** BEGIN LICENSE BLOCK ***** * Zimbra Collaboration Suite Server * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, * version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with this program. * If not, see <https://www.gnu.org/licenses/>. * ***** END LICENSE BLOCK ***** */ package com.zimbra.cs.mailbox; import java.security.SecureRandom; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.codec.binary.Hex; import com.zimbra.common.service.ServiceException; import com.zimbra.common.util.StringUtil; import com.zimbra.common.util.ZimbraLog; import com.zimbra.cs.account.Account; import com.zimbra.cs.account.GuestAccount; import com.zimbra.cs.account.NamedEntry; import com.zimbra.cs.account.Provisioning; /** * @since Jul 5, 2005 * @author dkarp */ public final class ACL { /** The right to read a message, list a folder's contents, etc. */ public static final short RIGHT_READ = 0x0001; /** The right to edit an item, change its flags, etc.. */ public static final short RIGHT_WRITE = 0x0002; /** The right to add or move an item to a folder */ public static final short RIGHT_INSERT = 0x0004; /** The right to hard-delete an item. */ public static final short RIGHT_DELETE = 0x0008; /** The right to take a workflow action on an item (e.g. accept a meeting). */ public static final short RIGHT_ACTION = 0x0010; /** The right to grant permissions on the item. */ public static final short RIGHT_ADMIN = 0x0100; /** The calculated right to create subfolders in a folder. */ public static final short RIGHT_SUBFOLDER = 0x0200; /** The right to view a private item. */ public static final short RIGHT_PRIVATE = 0x0400; /** The right to view free/busy on a calendar folder. */ public static final short RIGHT_FREEBUSY = 0x0800; /** The combination of rights that equates to {@link #RIGHT_SUBFOLDER}. */ private static final short SUBFOLDER_RIGHTS = RIGHT_READ | RIGHT_INSERT; /** Bitmask of all rights that can be explicitly granted. <i>Note: * CAN_CREATE_FOLDER is calculated and hence cannot be granted. */ private static final short GRANTABLE_RIGHTS = RIGHT_READ | RIGHT_WRITE | RIGHT_INSERT | RIGHT_DELETE | RIGHT_ACTION | RIGHT_ADMIN | RIGHT_PRIVATE | RIGHT_FREEBUSY; public static final short ROLE_VIEW = ACL.RIGHT_READ; public static final short ROLE_MANAGER = ACL.RIGHT_READ | ACL.RIGHT_WRITE | ACL.RIGHT_INSERT | ACL.RIGHT_DELETE | ACL.RIGHT_ACTION; public static final short ROLE_ADMIN = ACL.ROLE_MANAGER | ACL.RIGHT_ADMIN; /** The grantee of these rights is the zimbraId for a user. */ public static final byte GRANTEE_USER = 1; /** The grantee of these rights is the zimbraId for a distribution list. */ public static final byte GRANTEE_GROUP = 2; /** The grantee of these rights is all authenticated users. */ public static final byte GRANTEE_AUTHUSER = 3; /** The grantee of these rights is the zimbraId for a domain. */ public static final byte GRANTEE_DOMAIN = 4; /** The grantee of these rights is the zimbraId for a COS. */ public static final byte GRANTEE_COS = 5; /** The grantee of these rights is all authenticated and unauthenticated users. */ public static final byte GRANTEE_PUBLIC = 6; /** The grantee of these rights is a named non Zimbra user identified by the email address */ public static final byte GRANTEE_GUEST = 7; /** The grantee of these rights is a named non Zimbra user identified by the access key */ public static final byte GRANTEE_KEY = 8; private static final int ACCESSKEY_SIZE_BYTES = 16; public static class Grant { /** The zimbraId of the entry being granted rights. */ private String mGrantee; /** The display name of the grantee, which is often the email address of grantee. */ private String mName; /** The type of object the grantee's ID refers to. * For instance, {@link ACL#GRANTEE_USER}. */ private final byte mType; /** A bitmask of the rights being granted. For instance, * <tt>{@link ACL#RIGHT_INSERT} | {@link ACL#RIGHT_READ}</tt>. */ private short mRights; /** The password for guest accounts, or hex ascii string version of the accesskey for "key" grantees. */ private String mSecret; /** Time when this grant expires. * Value of 0 indicates that expiry is derived from ACL, except when {@link #mType} is * {@link ACL.GRANTEE_PUBLIC}. */ private long mExpiry = 0; /** Creates a new Grant object granting access to a user or class * of users. <tt>zimbraId</tt> may be <tt>null</tt> * if the <tt>type</tt> is {@link ACL#GRANTEE_PUBLIC}. * * @param zimbraId The zimbraId of the entry being granted rights. * @param type The type of object the grantee's ID refers to. * @param rights A bitmask of the rights being granted. * @see ACL */ Grant(String zimbraId, byte type, short rights) { mGrantee = zimbraId; mType = type; mRights = (short) (rights & GRANTABLE_RIGHTS); } Grant(String zimbraId, byte type, short rights, String secret, long expiry) { this(zimbraId, type, rights); if (mType == GRANTEE_GUEST || mType == GRANTEE_KEY) mSecret = secret; mExpiry = expiry; } /** Creates a new Grant object from a decoded {@link Metadata} hash. * * @param meta The Metadata object containing ACL data. * @throws ServiceException if any required fields are missing. */ public Grant(Metadata meta) throws ServiceException { mType = (byte) meta.getLong(FN_TYPE); mRights = (short) (meta.getLong(FN_RIGHTS) & GRANTABLE_RIGHTS); mName = meta.get(FN_NAME, null); if (hasGrantee()) mGrantee = meta.get(FN_GRANTEE); if (mType == ACL.GRANTEE_GUEST) mSecret = meta.get(FN_PASSWORD, null); else if (mType == ACL.GRANTEE_KEY) mSecret = meta.get(FN_ACCESSKEY); mExpiry = meta.getLong(FN_EXPIRY, 0); } /** Returns true if there is an explicit grantee. */ public boolean hasGrantee() { return mType != ACL.GRANTEE_AUTHUSER && mType != ACL.GRANTEE_PUBLIC; } /** Returns the zimbraId of the entry granted rights. */ public String getGranteeId() { return hasGrantee() ? mGrantee : null; } /** Returns type of object the grantee's ID refers to. */ public byte getGranteeType() { return mType; } /** Returns the bitmask of the rights granted. */ public short getGrantedRights() { return mRights; } /** Returns the rights granted to the given {@link Account} by this * <tt>Grant</tt>. If the grant does not apply to the Account, * returns <tt>0</tt>. */ public short getGrantedRights(Account acct, ACL acl) throws ServiceException { if (isExpired(acl)) { if (ZimbraLog.acl.isTraceEnabled()) { ZimbraLog.acl.trace("ACL.GrantedRights 0 for acl=%s (expired)", acl); } return 0; } if (matches(acct)) { if (ZimbraLog.acl.isTraceEnabled()) { ZimbraLog.acl.trace("ACL.GrantedRights %s for acl=%s", mRights, acl); } return mRights; } if (ZimbraLog.acl.isTraceEnabled()) { ZimbraLog.acl.trace("ACL.GrantedRights 0 for acl=%s (does not match %s)", acl, acct == null ? "'null acct'" : acct.getName()); } return 0; } private boolean isExpired(ACL acl) { long expiry = getEffectiveExpiry(acl); return expiry != 0 && System.currentTimeMillis() > expiry; } public long getEffectiveExpiry(ACL acl) { long expiry = mExpiry; if (expiry == 0) { if (mType == ACL.GRANTEE_GUEST || mType == ACL.GRANTEE_KEY) { expiry = acl.getGuestGrantExpiry(); } else if (mType != ACL.GRANTEE_PUBLIC) { expiry = acl.getInternalGrantExpiry(); } } return expiry; } /** Returns the display name of grantee. */ public String getGranteeName() { return mName; } /** Sets the display name of grantee. */ public void setGranteeName(String name) { mName = name; } /** Returns whether this grant applies to the given {@link Account}. * If <tt>acct</tt> is <tt>null</tt>, only return * <tt>true</tt> if the grantee is {@link ACL#GRANTEE_PUBLIC}. */ public boolean matches(Account acct) throws ServiceException { Provisioning prov = Provisioning.getInstance(); if (acct == null) return mType == ACL.GRANTEE_PUBLIC; switch (mType) { case ACL.GRANTEE_PUBLIC: return true; case ACL.GRANTEE_AUTHUSER: return isInternalAccount(acct); case ACL.GRANTEE_COS: return mGrantee.equals(getId(prov.getCOS(acct))); case ACL.GRANTEE_DOMAIN: return matchesDomainGrantee(acct, prov); case ACL.GRANTEE_GROUP: return prov.inACLGroup(acct, mGrantee); case ACL.GRANTEE_USER: return mGrantee.equals(acct.getId()); case ACL.GRANTEE_GUEST: return matchesGuestAccount(acct); case ACL.GRANTEE_KEY: return matchesAccessKey(acct); default: throw ServiceException.FAILURE("unknown ACL grantee type: " + mType, null); } } private boolean matchesDomainGrantee(Account acct, Provisioning prov) throws ServiceException { return !acct.isIsExternalVirtualAccount() && mGrantee.equals(getId(prov.getDomain(acct))); } private boolean isInternalAccount(Account acct) { return !acct.getId().equals(GuestAccount.GUID_PUBLIC) && !acct.isIsExternalVirtualAccount(); } private boolean matchesGuestAccount(Account acct) { if (acct instanceof GuestAccount) { // Now that we can have virtual accounts, the secret is null/empty in virtual account // sharing, and so we need to block access to GuestAccount(s) if (!StringUtil.isNullOrEmpty(mSecret)) { return ((GuestAccount) acct).matches(mGrantee, mSecret); } } else if (acct.isIsExternalVirtualAccount()) { return mGrantee.equalsIgnoreCase(acct.getExternalUserMailAddress()); } return false; } private boolean matchesAccessKey(Account acct) { if (!(acct instanceof GuestAccount)) return false; return ((GuestAccount) acct).matchesAccessKey(mGrantee, mSecret); } /** Utility function: Returns the zimbraId for a null-checked LDAP * entry. */ private static final String getId(NamedEntry entry) { return (entry == null ? null : entry.getId()); } /** Returns whether the principal id exactly matches the grantee. * <tt>zimbraId</tt> must be {@link GuestAccount#GUID_PUBLIC} (<tt>null</tt> * is also OK) if the actual grantee is {@link ACL#GRANTEE_PUBLIC}. * <tt>zimbraId</tt> must be {@link GuestAccount#GUID_AUTHUSER} if the actual * grantee is {@link ACL#GRANTEE_AUTHUSER}. * * @param zimbraId The zimbraId of the principal. */ public boolean isGrantee(String zimbraId) { if (zimbraId == null || zimbraId.equals(GuestAccount.GUID_PUBLIC)) return (mType == GRANTEE_PUBLIC); else if (zimbraId.equals(GuestAccount.GUID_AUTHUSER)) return (mType == GRANTEE_AUTHUSER); return mType == GRANTEE_GUEST || mType == GRANTEE_KEY ? zimbraId.equalsIgnoreCase(mGrantee) : zimbraId.equals(mGrantee); } /** Updates the granted rights in the <tt>Grant</tt>. The old * set of rights is discarded. * * @param rights A bitmask of the rights being granted. * @param inherit Whether subfolders inherit these same rights. * @see ACL */ void setRights(short rights) { mRights = rights; } /** For grants to external users, sets the password/accesskey required to * access the resource. */ void setPassword(String password) { if ((mType == GRANTEE_GUEST || mType == GRANTEE_KEY) && password != null) mSecret = password; } /** * Only for grants to external users */ public String getPassword() { return mSecret; } /** Updates the expiry time for the grant. * * @param expiry */ public void setExpiry(long expiry) { mExpiry = expiry; } public long getExpiry() { return mExpiry; } private static final String FN_GRANTEE = "g"; private static final String FN_NAME = "n"; private static final String FN_TYPE = "t"; private static final String FN_RIGHTS = "r"; private static final String FN_PASSWORD = "a"; private static final String FN_ACCESSKEY = "k"; private static final String FN_EXPIRY = "e"; /** Encapsulates this <tt>Grant</tt> as a {@link Metadata} object * for serialization. */ public Metadata encode() { Metadata meta = new Metadata(); meta.put(FN_GRANTEE, hasGrantee() ? mGrantee : null); meta.put(FN_NAME, mName); meta.put(FN_TYPE, mType); // FIXME: use "rwidxsca" instead of numeric value meta.put(FN_RIGHTS, mRights); if (mType == GRANTEE_KEY) meta.put(FN_ACCESSKEY, mSecret); else meta.put(FN_PASSWORD, mSecret); meta.put(FN_EXPIRY, mExpiry); return meta; } } /** The <tt>List</tt> of all {@link ACL.Grant}s set on an item. */ private final List<Grant> mGrants = new CopyOnWriteArrayList<Grant>(); /** Time when all grants to internal users or groups expire. Value of 0 indicates that they never expire. */ private long mInternalGrantExpiry = 0; /** Time when all grants to guest/external users expire. Value of 0 indicates that they never expire. */ private long mGuestGrantExpiry = 0; public ACL() { } public ACL(long internalGrantExpiry, long guestGrantExpiry) { mInternalGrantExpiry = internalGrantExpiry; mGuestGrantExpiry = guestGrantExpiry; } public ACL(MetadataList mlist) { decodeGrants(mlist); } public ACL(Metadata meta) { MetadataList mlist = null; try { mlist = meta.getList(FN_GRANTS, true); mInternalGrantExpiry = meta.getLong(FN_INT_GRANT_EXPIRY, 0); mGuestGrantExpiry = meta.getLong(FN_GST_GRANT_EXPIRY, 0); } catch (ServiceException e) { ZimbraLog.mailbox.warn("malformed ACL: " + meta, e); } if (mlist != null) { decodeGrants(mlist); } } private void decodeGrants(MetadataList mlist) { for (int i = 0; i < mlist.size(); i++) { try { mGrants.add(new Grant(mlist.getMap(i))); } catch (ServiceException e) { ZimbraLog.mailbox.warn("malformed permission grant: " + mlist, e); } } } public long getInternalGrantExpiry() { return mInternalGrantExpiry; } public long getGuestGrantExpiry() { return mGuestGrantExpiry; } /** Returns the bitmask of rights granted to the user by the ACL, or * <tt>null</tt> if there are no rights granted to anyone. (Note that * if rights are granted to <i>other</i> accounts but not to the * specified user, returns <tt>0</tt>.) * * @param authuser The user to gather rights for. * @return A <tt>Short</tt> containing the OR'ed-together rights * granted to the user, or <tt>null</tt>. */ public Short getGrantedRights(Account authuser) throws ServiceException { if (mGrants.isEmpty()) { if (ZimbraLog.acl.isTraceEnabled()) { ZimbraLog.acl.trace("ACL.GrantedRights NULL (no grants)"); } return null; } short rightsGranted = 0; for (Grant grant : mGrants) { rightsGranted |= grant.getGrantedRights(authuser, this); } if ((rightsGranted & SUBFOLDER_RIGHTS) == SUBFOLDER_RIGHTS) rightsGranted |= RIGHT_SUBFOLDER; if (ZimbraLog.acl.isTraceEnabled()) { ZimbraLog.acl.trace("ACL.GrantedRights %s from %s grants", rightsGranted, mGrants.size()); } return Short.valueOf(rightsGranted); } /** Returns whether there are any grants encapsulated by this ACL. */ public boolean isEmpty() { return mGrants.isEmpty(); } public ACL.Grant grantAccess(String zimbraId, byte type, short rights, String secret) throws ServiceException { return grantAccess(zimbraId, type, rights, secret, 0); } /** Grants the specified set of rights to the target. If another set * of rights has already been granted to the exact given (id, type) * pair, the previous set is revoked and the new set is granted. * * @param zimbraId The zimbraId of the entry being granted rights. * @param type The type of object the grantee's ID refers to. * @param rights A bitmask of the rights being granted. * @param secret password or accesskey * @param expiry time when grant expires * @return the grant object */ public ACL.Grant grantAccess(String zimbraId, byte type, short rights, String secret, long expiry) throws ServiceException { if (expiry != 0) { if (type == ACL.GRANTEE_GUEST || type == ACL.GRANTEE_KEY) { if (mGuestGrantExpiry != 0 && expiry > mGuestGrantExpiry) { throw ServiceException.PERM_DENIED("share expiration policy conflict"); } } else if (type != ACL.GRANTEE_PUBLIC) { // internal grantee if (mInternalGrantExpiry != 0 && expiry > mInternalGrantExpiry) { throw ServiceException.PERM_DENIED("share expiration policy conflict"); } } } if (type == GRANTEE_AUTHUSER) zimbraId = GuestAccount.GUID_AUTHUSER; else if (type == GRANTEE_PUBLIC) zimbraId = GuestAccount.GUID_PUBLIC; else if (zimbraId == null) throw ServiceException.INVALID_REQUEST("missing grantee id", null); // always generate a new key (if not provided) for updating or new key grants if (type == GRANTEE_KEY && secret == null) secret = generateAccessKey(); if (!mGrants.isEmpty()) { for (Grant grant : mGrants) { if (grant.isGrantee(zimbraId)) { if (grant.getGrantedRights() == rights && ((type != GRANTEE_GUEST && type != GRANTEE_KEY) || StringUtil.equal(grant.getPassword(), secret)) && (grant.getExpiry() == expiry)) { // same grant is already in the ACL throw MailServiceException.GRANTEE_EXISTS(zimbraId, null); } grant.setRights(rights); if (type == GRANTEE_GUEST || type == GRANTEE_KEY) grant.setPassword(secret); grant.setExpiry(expiry); return grant; } } } Grant grant = new Grant(zimbraId, type, rights, secret, expiry); mGrants.add(grant); return grant; } /** Removes the set of rights granted to the specified id. If no rights * were previously granted to the target, no error is thrown and * <tt>false</tt> is returned. * * @param zimbraId The zimbraId of the entry being revoked rights. * @return whether an {@link Grant} was actually removed from the set. */ public boolean revokeAccess(String zimbraId) { if (mGrants == null || mGrants.isEmpty()) return false; int count = mGrants.size(); for (Grant grant : mGrants) { if (grant.isGrantee(zimbraId)) { mGrants.remove(grant); } } return (mGrants.size() != count); } public void setGuestGrantExpiry(long expiry) { mGuestGrantExpiry = expiry; } public void setInternalGrantExpiry(long expiry) { mInternalGrantExpiry = expiry; } private static final String FN_GRANTS = "g"; private static final String FN_INT_GRANT_EXPIRY = "ie"; private static final String FN_GST_GRANT_EXPIRY = "ge"; public Metadata encode() { Metadata meta = new Metadata(); MetadataList mlist = new MetadataList(); for (Grant grant : mGrants) mlist.add(grant.encode()); meta.put(FN_GRANTS, mlist); meta.put(FN_INT_GRANT_EXPIRY, mInternalGrantExpiry); meta.put(FN_GST_GRANT_EXPIRY, mGuestGrantExpiry); return meta; } @Override public String toString() { return encode().toString(); } /** Returns a different <tt>ACL</tt> with the same contents. */ public ACL duplicate() { return new ACL(encode()); } /** Returns the list of this <tt>ACL</tt>'s set of encapsulated * {@link ACL.Grant} objects. */ public List<Grant> getGrants() { return Collections.unmodifiableList(mGrants); } public int getNumberOfGrantsByType(byte granteeType) { int i = 0; if (mGrants != null) { for (Grant grant : mGrants) { if (grant.getGranteeType() == granteeType) { i++; } } } return i; } public static final char ABBR_READ = 'r'; public static final char ABBR_WRITE = 'w'; public static final char ABBR_INSERT = 'i'; public static final char ABBR_DELETE = 'd'; public static final char ABBR_ACTION = 'x'; public static final char ABBR_ADMIN = 'a'; public static final char ABBR_PRIVATE = 'p'; public static final char ABBR_FREEBUSY = 'f'; public static final char ABBR_CREATE_FOLDER = 'c'; public static short stringToRights(String encoded) throws ServiceException { short rights = 0; if (encoded != null && encoded.length() != 0) { for (int i = 0; i < encoded.length(); i++) { switch (encoded.charAt(i)) { case ABBR_READ: rights |= RIGHT_READ; break; case ABBR_WRITE: rights |= RIGHT_WRITE; break; case ABBR_INSERT: rights |= RIGHT_INSERT; break; case ABBR_DELETE: rights |= RIGHT_DELETE; break; case ABBR_ACTION: rights |= RIGHT_ACTION; break; case ABBR_ADMIN: rights |= RIGHT_ADMIN; break; case ABBR_PRIVATE: rights |= RIGHT_PRIVATE; break; case ABBR_FREEBUSY: rights |= RIGHT_FREEBUSY; break; case ABBR_CREATE_FOLDER: break; default: throw ServiceException.INVALID_REQUEST("unknown right: " + encoded.charAt(i), null); } } } return rights; } public static String rightsToString(short rights) { if (rights == 0) return ""; StringBuffer sb = new StringBuffer(); if ((rights & RIGHT_READ) != 0) sb.append(ABBR_READ); if ((rights & RIGHT_WRITE) != 0) sb.append(ABBR_WRITE); if ((rights & RIGHT_INSERT) != 0) sb.append(ABBR_INSERT); if ((rights & RIGHT_DELETE) != 0) sb.append(ABBR_DELETE); if ((rights & RIGHT_ACTION) != 0) sb.append(ABBR_ACTION); if ((rights & RIGHT_ADMIN) != 0) sb.append(ABBR_ADMIN); if ((rights & RIGHT_PRIVATE) != 0) sb.append(ABBR_PRIVATE); if ((rights & RIGHT_FREEBUSY) != 0) sb.append(ABBR_FREEBUSY); if ((rights & RIGHT_SUBFOLDER) != 0) sb.append(ABBR_CREATE_FOLDER); return sb.toString(); } public static byte stringToType(String typeStr) throws ServiceException { if (typeStr.equalsIgnoreCase("usr")) return ACL.GRANTEE_USER; if (typeStr.equalsIgnoreCase("grp")) return ACL.GRANTEE_GROUP; if (typeStr.equalsIgnoreCase("cos")) return ACL.GRANTEE_COS; if (typeStr.equalsIgnoreCase("dom")) return ACL.GRANTEE_DOMAIN; if (typeStr.equalsIgnoreCase("all")) return ACL.GRANTEE_AUTHUSER; if (typeStr.equalsIgnoreCase("pub")) return ACL.GRANTEE_PUBLIC; if (typeStr.equalsIgnoreCase("guest")) return ACL.GRANTEE_GUEST; if (typeStr.equalsIgnoreCase("key")) return ACL.GRANTEE_KEY; throw ServiceException.INVALID_REQUEST("unknown grantee type: " + typeStr, null); } public static String typeToString(byte type) { if (type == ACL.GRANTEE_USER) return "usr"; if (type == ACL.GRANTEE_GROUP) return "grp"; if (type == ACL.GRANTEE_PUBLIC) return "pub"; if (type == ACL.GRANTEE_AUTHUSER) return "all"; if (type == ACL.GRANTEE_COS) return "cos"; if (type == ACL.GRANTEE_DOMAIN) return "dom"; if (type == ACL.GRANTEE_GUEST) return "guest"; if (type == ACL.GRANTEE_KEY) return "key"; return null; } public static String generateAccessKey() { SecureRandom random = new SecureRandom(); byte[] key = new byte[ACCESSKEY_SIZE_BYTES]; random.nextBytes(key); // in the form of e.g. 8d159aed5fb9431d8ac52db5e20baafb return new String(Hex.encodeHex(key)); } }