MAILBOX-307 Removing unneeded interface-sception for ACLs
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4e10f10b Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4e10f10b Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4e10f10b Branch: refs/heads/master Commit: 4e10f10b6e5b67972bd5b9cbdfaeb1b3eb7dcef9 Parents: 91d303b Author: benwa <btell...@linagora.com> Authored: Wed Sep 27 09:41:23 2017 +0700 Committer: Matthieu Baechler <matth...@apache.org> Committed: Fri Sep 29 09:20:40 2017 +0200 ---------------------------------------------------------------------- .../apache/james/mailbox/MailboxManager.java | 11 +- .../james/mailbox/acl/MailboxACLResolver.java | 11 +- .../mailbox/acl/UnionMailboxACLResolver.java | 94 ++- .../exception/UnsupportedRightException.java | 4 +- .../apache/james/mailbox/model/MailboxACL.java | 823 +++++++++++++++---- .../james/mailbox/model/SimpleMailboxACL.java | 679 --------------- .../acl/UnionMailboxACLResolverTest.java | 611 +++++++------- .../mailbox/model/MailboxACLEntryKeyTest.java | 157 ++++ .../james/mailbox/model/MailboxACLTest.java | 224 +++++ .../james/mailbox/model/Rfc4314RightsTest.java | 75 +- .../model/SimpleMailboxACLEntryKeyTest.java | 157 ---- .../mailbox/model/SimpleMailboxACLTest.java | 226 ----- .../mailbox/caching/CachingMailboxMapper.java | 3 +- .../cassandra/CassandraMailboxManager.java | 4 +- .../cassandra/mail/CassandraACLMapper.java | 13 +- .../cassandra/mail/CassandraMailboxMapper.java | 2 +- .../cassandra/mail/CassandraACLMapperTest.java | 91 +- .../mailbox/hbase/mail/HBaseMailboxMapper.java | 2 +- .../mailbox/hbase/mail/model/HBaseMailbox.java | 3 +- .../mailbox/jcr/mail/JCRMailboxMapper.java | 2 +- .../mailbox/jcr/mail/model/JCRMailbox.java | 3 +- .../mailbox/jpa/mail/JPAMailboxMapper.java | 2 +- .../mailbox/jpa/mail/model/JPAMailbox.java | 3 +- .../jpa/mail/TransactionalMailboxMapper.java | 3 +- .../LuceneMailboxMessageSearchIndexTest.java | 3 +- .../james/mailbox/maildir/MaildirFolder.java | 17 +- .../maildir/mail/MaildirMailboxMapper.java | 2 +- .../inmemory/mail/InMemoryMailboxMapper.java | 2 +- .../mailbox/store/StoreMailboxManager.java | 25 +- .../mailbox/store/StoreMessageManager.java | 7 +- .../json/SimpleMailboxACLJsonConverter.java | 11 +- .../james/mailbox/store/mail/MailboxMapper.java | 2 +- .../store/mail/model/impl/SimpleMailbox.java | 5 +- .../store/TestMailboxSessionMapperFactory.java | 2 +- .../store/json/MailboxACLJsonConverterTest.java | 140 ++++ .../json/SimpleMailboxACLJsonConverterTest.java | 139 ---- .../store/mail/model/ListMailboxAssertTest.java | 2 +- .../store/mail/model/MailboxMapperACLTest.java | 103 ++- .../mpt/imapmailbox/GrantRightsOnHost.java | 2 +- .../mpt/imapmailbox/suite/ACLCommands.java | 5 +- .../mpt/imapmailbox/suite/ACLIntegration.java | 68 +- .../suite/ACLScriptedTestProtocol.java | 7 +- .../cyrus/host/GrantRightsOnCyrusHost.java | 5 +- .../james/imap/encode/ACLResponseEncoder.java | 8 +- .../imap/encode/ListRightsResponseEncoder.java | 6 +- .../imap/encode/MyRightsResponseEncoder.java | 4 +- .../imap/message/response/ACLResponse.java | 16 +- .../message/response/ListRightsResponse.java | 10 +- .../imap/message/response/MyRightsResponse.java | 8 +- .../imap/processor/DeleteACLProcessor.java | 15 +- .../james/imap/processor/GetACLProcessor.java | 8 +- .../james/imap/processor/GetQuotaProcessor.java | 6 +- .../imap/processor/GetQuotaRootProcessor.java | 6 +- .../imap/processor/ListRightsProcessor.java | 17 +- .../james/imap/processor/MyRightsProcessor.java | 18 +- .../james/imap/processor/SetACLProcessor.java | 25 +- .../imap/processor/DeleteACLProcessorTest.java | 22 +- .../imap/processor/GetACLProcessorTest.java | 13 +- .../imap/processor/GetQuotaProcessorTest.java | 8 +- .../processor/GetQuotaRootProcessorTest.java | 7 +- .../imap/processor/ListRightsProcessorTest.java | 27 +- .../imap/processor/SetACLProcessorTest.java | 29 +- .../base/MailboxEventAnalyserTest.java | 16 +- ...ltMailboxesProvisioningFilterThreadTest.java | 17 +- 64 files changed, 1908 insertions(+), 2128 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java index 2963ad6..3ef58b7 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java @@ -40,7 +40,6 @@ import org.apache.james.mailbox.model.MailboxQuery; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.MultimailboxesSearchQuery; -import org.apache.james.mailbox.model.SimpleMailboxACL; /** * <p> @@ -348,7 +347,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport { * mailbox; false otherwise. * @throws MailboxException */ - boolean hasRight(MailboxPath mailboxPath, MailboxACL.MailboxACLRight right, MailboxSession session) throws MailboxException; + boolean hasRight(MailboxPath mailboxPath, MailboxACL.Right right, MailboxSession session) throws MailboxException; /** * Returns the rights applicable to the user who has sent the current @@ -357,11 +356,11 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport { * @param mailboxPath Path of the mailbox you want to get your rights on. * @param session The session used to determine the user we should retrieve the rights of. * @return the rights applicable to the user who has sent the request, - * returns {@link SimpleMailboxACL#NO_RIGHTS} if + * returns {@link MailboxACL#NO_RIGHTS} if * {@code session.getUser()} is null. * @throws UnsupportedRightException */ - MailboxACL.MailboxACLRights myRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; + MailboxACL.Rfc4314Rights myRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException; /** * Computes a result suitable for the LISTRIGHTS IMAP command. The result is @@ -385,7 +384,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport { * @return result suitable for the LISTRIGHTS IMAP command * @throws UnsupportedRightException */ - MailboxACL.MailboxACLRights[] listRigths(MailboxPath mailboxPath, MailboxACL.MailboxACLEntryKey identifier, MailboxSession session) throws MailboxException; + MailboxACL.Rfc4314Rights[] listRigths(MailboxPath mailboxPath, MailboxACL.EntryKey identifier, MailboxSession session) throws MailboxException; /** * Update the Mailbox ACL of the designated mailbox. We can either ADD REPLACE or REMOVE entries. @@ -393,7 +392,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport { * @param mailboxACLCommand Update to perform. * @throws UnsupportedRightException */ - void setRights(MailboxPath mailboxPath, MailboxACL.MailboxACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException; + void setRights(MailboxPath mailboxPath, MailboxACL.ACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException; /** http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java index 55fc15d..f858756 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java @@ -24,9 +24,6 @@ import javax.mail.Flags; import org.apache.james.mailbox.exception.UnsupportedRightException; import org.apache.james.mailbox.model.MailboxACL; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; /** * Implements the interpretation of ACLs. @@ -86,7 +83,7 @@ public interface MailboxACLResolver { * resource; false otherwise. * @throws UnsupportedRightException */ - boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL.Right right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; /** * Maps the given {@code mailboxACLRights} to READ-WRITE and READ-ONLY @@ -125,7 +122,7 @@ public interface MailboxACLResolver { * @return * @throws UnsupportedRightException */ - boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException; + boolean isReadWrite(MailboxACL.Rfc4314Rights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException; /** * Computes a result suitable for the LISTRIGHTS IMAP command. The result is @@ -148,7 +145,7 @@ public interface MailboxACLResolver { * of rights which can be set for the given identifier and resource. * @throws UnsupportedRightException */ - MailboxACLRights[] listRights(MailboxACLEntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + MailboxACL.Rfc4314Rights[] listRights(MailboxACL.EntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; /** * Computes the rights which apply to the given user and resource. Global @@ -173,6 +170,6 @@ public interface MailboxACLResolver { * @return the rights applicable for the given user and resource. * @throws UnsupportedRightException */ - MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; + MailboxACL.Rfc4314Rights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException; } http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java index 545d5df..8f51b1a 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java @@ -30,13 +30,11 @@ import javax.mail.Flags.Flag; import org.apache.james.mailbox.exception.UnsupportedRightException; import org.apache.james.mailbox.model.MailboxACL; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; +import org.apache.james.mailbox.model.MailboxACL.EntryKey; import org.apache.james.mailbox.model.MailboxACL.NameType; -import org.apache.james.mailbox.model.SimpleMailboxACL; -import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights; -import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; +import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.MailboxACL.Right; +import org.apache.james.mailbox.model.MailboxACL.SpecialName; /** @@ -56,12 +54,12 @@ import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey; * */ public class UnionMailboxACLResolver implements MailboxACLResolver { - public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = SimpleMailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = MailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; /** * Nothing else than full rights for the owner. */ - public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = SimpleMailboxACL.OWNER_FULL_ACL; + public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = MailboxACL.OWNER_FULL_ACL; private static final int POSITIVE_INDEX = 0; private static final int NEGATIVE_INDEX = 1; @@ -72,7 +70,7 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * computing * {@link #rightsOf(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, Mailbox)} * and - * {@link #hasRight(String, Mailbox, MailboxACLRight, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)} + * {@link #hasRight(String, Mailbox, Right, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)} * . */ private final MailboxACL userGlobalACL; @@ -112,25 +110,25 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { } /** - * Tells whether the given {@code aclKey} {@link MailboxACLEntryKey} is + * Tells whether the given {@code aclKey} {@link EntryKey} is * applicable for the given {@code queryKey}. * * There are two use cases for which this method was designed and tested: * * (1) Calls from - * {@link #hasRight(String, GroupMembershipResolver, MailboxACLRight, MailboxACL, String, boolean)} + * {@link #hasRight(String, GroupMembershipResolver, Right, MailboxACL, String, boolean)} * and * {@link #resolveRights(String, GroupMembershipResolver, MailboxACL, String, boolean)} * in which the {@code queryKey} is a {@link NameType#user}. * * (2) Calls from - * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * {@link #listRights(EntryKey, GroupMembershipResolver, String, boolean)} * where {@code queryKey} can be anything including {@link NameType#user}, * {@link NameType#group} and all {@link NameType#special} identifiers. * * Clearly the set of cases which this method has to handle in (1) is a * proper subset of the cases handled in (2). See the javadoc on - * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)} + * {@link #listRights(EntryKey, GroupMembershipResolver, String, boolean)} * for more details. * * @param aclKey @@ -140,10 +138,10 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * @param resourceOwnerIsGroup * @return */ - protected static boolean applies(MailboxACLEntryKey aclKey, MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) { + protected static boolean applies(EntryKey aclKey, EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) { final String aclKeyName = aclKey.getName(); final NameType aclKeyNameType = aclKey.getNameType(); - if (MailboxACL.SpecialName.anybody.name().equals(aclKeyName)) { + if (SpecialName.anybody.name().equals(aclKeyName)) { /* this works also for unauthenticated users */ return true; } else if (queryKey != null) { @@ -153,14 +151,14 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { /* Authenticated users */ switch (aclKeyNameType) { case special: - if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + if (SpecialName.authenticated.name().equals(aclKeyName)) { /* non null query user is viewed as authenticated */ return true; - } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + } else if (SpecialName.owner.name().equals(aclKeyName)) { return (!resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner)) || (resourceOwnerIsGroup && groupMembershipResolver.isMember(queryUserOrGroupName, resourceOwner)); } else { /* should not happen unless the parent if is changed */ - throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + throw new IllegalStateException("Unexpected " + SpecialName.class.getName() + "." + aclKeyName); } case user: return aclKeyName.equals(queryUserOrGroupName); @@ -173,16 +171,16 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { /* query is a group */ switch (aclKeyNameType) { case special: - if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + if (SpecialName.authenticated.name().equals(aclKeyName)) { /* * see the javadoc comment on listRights() */ return true; - } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) { + } else if (SpecialName.owner.name().equals(aclKeyName)) { return resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner); } else { /* should not happen unless the parent if is changed */ - throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName); + throw new IllegalStateException("Unexpected " + SpecialName.class.getName() + "." + aclKeyName); } case user: /* query groups cannot match ACL users */ @@ -202,7 +200,7 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * owner */ return true; - } else if (MailboxACL.SpecialName.owner.name().equals(queryUserOrGroupName) && MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) { + } else if (SpecialName.owner.name().equals(queryUserOrGroupName) && SpecialName.authenticated.name().equals(aclKeyName)) { /* * query owner matches authenticated because owner will * be resolved only if the user is authenticated @@ -240,17 +238,17 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#hasRight(java. * lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver. * GroupMembershipResolver, - * org.apache.james.mailbox.MailboxACL.MailboxACLRight, + * org.apache.james.mailbox.MailboxACL.Right, * org.apache.james.mailbox.MailboxACL, java.lang.String) */ @Override - public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { - final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, Right right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + final EntryKey queryKey = requestUser == null ? null : new EntryKey(requestUser, NameType.user, false); boolean result = false; - Map<MailboxACLEntryKey, MailboxACLRights> entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries(); + Map<EntryKey, Rfc4314Rights> entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries(); if (entries != null) { - for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) { - final MailboxACLEntryKey key = entry.getKey(); + for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) { + final EntryKey key = entry.getKey(); if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { if (key.isNegative()) { return false; @@ -264,8 +262,8 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { if (resourceACL != null) { entries = resourceACL.getEntries(); if (entries != null) { - for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) { - final MailboxACLEntryKey key = entry.getKey(); + for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) { + final EntryKey key = entry.getKey(); if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) { if (key.isNegative()) { return false; @@ -281,13 +279,13 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { } /** - * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.MailboxACL.MailboxACLRights, + * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.Rfc4314Rights, * javax.mail.Flags) */ @Override - public boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException { + public boolean isReadWrite(Rfc4314Rights Rfc4314Rights, Flags sharedFlags) throws UnsupportedRightException { /* the two fast cases first */ - if (mailboxACLRights.contains(SimpleMailboxACL.Right.Insert) || mailboxACLRights.contains(SimpleMailboxACL.Right.PerformExpunge)) { + if (Rfc4314Rights.contains(MailboxACL.Right.Insert) || Rfc4314Rights.contains(MailboxACL.Right.PerformExpunge)) { return true; } /* @@ -303,12 +301,12 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * flags. */ else if (sharedFlags != null) { - if (sharedFlags.contains(Flag.DELETED) && mailboxACLRights.contains(SimpleMailboxACL.Right.DeleteMessages)) { + if (sharedFlags.contains(Flag.DELETED) && Rfc4314Rights.contains(MailboxACL.Right.DeleteMessages)) { return true; - } else if (sharedFlags.contains(Flag.SEEN) && mailboxACLRights.contains(SimpleMailboxACL.Right.WriteSeenFlag)) { + } else if (sharedFlags.contains(Flag.SEEN) && Rfc4314Rights.contains(MailboxACL.Right.WriteSeenFlag)) { return true; } else { - boolean hasWriteRight = mailboxACLRights.contains(SimpleMailboxACL.Right.Write); + boolean hasWriteRight = Rfc4314Rights.contains(MailboxACL.Right.Write); return hasWriteRight && (sharedFlags.contains(Flag.ANSWERED) || sharedFlags.contains(Flag.DRAFT) || sharedFlags.contains(Flag.FLAGGED) || sharedFlags.contains(Flag.RECENT) || sharedFlags.contains(Flag.USER)); } } @@ -363,8 +361,8 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * @see org.apache.james.mailbox.acl.MailboxACLResolver#listRightsDefault(boolean) */ @Override - public MailboxACLRights[] listRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { - MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; + public Rfc4314Rights[] listRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + Rfc4314Rights[] positiveNegativePair = { MailboxACL.NO_RIGHTS, MailboxACL.NO_RIGHTS }; MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); @@ -376,15 +374,15 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { } } - private static MailboxACLRights[] toListRightsArray(MailboxACLRights implicitRights) throws UnsupportedRightException { - List<MailboxACLRights> result = new ArrayList<>(); + private static Rfc4314Rights[] toListRightsArray(Rfc4314Rights implicitRights) throws UnsupportedRightException { + List<Rfc4314Rights> result = new ArrayList<>(); result.add(implicitRights); - for (MailboxACLRight right : SimpleMailboxACL.FULL_RIGHTS) { + for (Right right : MailboxACL.FULL_RIGHTS.list()) { if (!implicitRights.contains(right)) { result.add(new Rfc4314Rights(right)); } } - return result.toArray(new MailboxACLRights[result.size()]); + return result.toArray(new Rfc4314Rights[result.size()]); } /** @@ -394,9 +392,9 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * java.lang.String) */ @Override - public MailboxACL.MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { - MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS }; - final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false); + public Rfc4314Rights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException { + Rfc4314Rights[] positiveNegativePair = { MailboxACL.NO_RIGHTS, MailboxACL.NO_RIGHTS }; + final EntryKey queryKey = requestUser == null ? null : new EntryKey(requestUser, NameType.user, false); MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL; resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair); @@ -418,11 +416,11 @@ public class UnionMailboxACLResolver implements MailboxACLResolver { * @param positiveNegativePair * @throws UnsupportedRightException */ - private void resolveRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, Map<MailboxACLEntryKey, MailboxACLRights> entries, String resourceOwner, boolean resourceOwnerIsGroup, MailboxACL.MailboxACLRights[] positiveNegativePair) + private void resolveRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, Map<EntryKey, Rfc4314Rights> entries, String resourceOwner, boolean resourceOwnerIsGroup, Rfc4314Rights[] positiveNegativePair) throws UnsupportedRightException { if (entries != null) { - for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) { - final MailboxACLEntryKey key = entry.getKey(); + for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) { + final EntryKey key = entry.getKey(); if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup)) { if (key.isNegative()) { positiveNegativePair[NEGATIVE_INDEX] = positiveNegativePair[NEGATIVE_INDEX].union(entry.getValue()); http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java index 8ca5dcd..26b711c 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java @@ -20,7 +20,7 @@ package org.apache.james.mailbox.exception; -import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight; +import org.apache.james.mailbox.model.MailboxACL.Right; /** * Thrown when the current system does not support the given right. @@ -41,7 +41,7 @@ public class UnsupportedRightException extends MailboxSecurityException { this.unsupportedRight = right; } - public UnsupportedRightException(MailboxACLRight unsupportedRight) { + public UnsupportedRightException(Right unsupportedRight) { this(unsupportedRight.asCharacter()); } http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java index b6e97d2..09dcbbb 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java @@ -19,235 +19,686 @@ package org.apache.james.mailbox.model; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.tuple.Pair; import org.apache.james.mailbox.exception.UnsupportedRightException; +import com.github.fge.lambdas.Throwing; +import com.github.steveash.guavate.Guavate; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + /** * Stores an Access Control List (ACL) applicable to a mailbox. Inspired by * RFC4314 IMAP4 Access Control List (ACL) Extension. - * + * * Implementations must be immutable. Implementations should override * {@link #hashCode()} and {@link #equals(Object)}. - * + * */ -public interface MailboxACL { +public class MailboxACL { /** * SETACL command mode. */ - enum EditMode { + public enum EditMode { ADD, REMOVE, REPLACE } + public enum NameType { + group, special, user + } + /** - * The key used in {@link MailboxACL#getEntries()}. Implementations should - * override {@link #hashCode()} and {@link #equals(Object)} in such a way - * that all of {@link #getName()}, {@link #getNameType()} and - * {@link #isNegative()} are significant. - * + * Special name literals. */ - interface MailboxACLEntryKey { - /** - * Returns the name of a user or of a group to which this - * {@link MailboxACLEntryKey} applies. - * - * @return User name, group name or special name. - */ - String getName(); + public enum SpecialName { + anybody, authenticated, owner + } - /** - * Tells of what type is the name returned by {@link #getName()}. - * - * @return type of the name returned by {@link #getName()} - */ - NameType getNameType(); + /** + * SETACL third argument prefix + */ + public static final char ADD_RIGHTS_MARKER = '+'; - /** - * If true the {@link MailboxACLRights} returned by - * {@link MailboxACLEntry#getRights()} should be interpreted as - * "negative rights" as described in RFC4314: If the identifier "-fred" - * is granted the "w" right, that indicates that the "w" right is to be - * removed from users matching the identifier "fred", even though the - * user "fred" might have the "w" right as a consequence of some other - * identifier in the ACL. - * - * Note that {@link MailboxACLEntry#getName()} does not start with "-" - * when {@link MailboxACLEntry#getRights()} returns true. - * - * @return - */ - boolean isNegative(); + /** + * Marks groups when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_GROUP_MARKER = '$'; - /** - * Returns a serialized form of this {@link MailboxACLEntryKey} as a - * {@link String}. Implementations should choose a consistent way how - * all of {@link #getName()}, {@link #getNameType()} and - * {@link #isNegative()} get serialized. - * - * RFC4314 sction 2. states: All user name strings accepted by the LOGIN - * or AUTHENTICATE commands to authenticate to the IMAP server are - * reserved as identifiers for the corresponding users. Identifiers - * starting with a dash ("-") are reserved for "negative rights", - * described below. All other identifier strings are interpreted in an - * implementation-defined manner. - * - * Dovecot and Cyrus mark groups with '$' prefix. See <a - * href="http://wiki2.dovecot.org/SharedMailboxes/Shared" - * >http://wiki2.dovecot.org/SharedMailboxes/Shared</a>: - * - * <cite>The $group syntax is not a standard, but it is mentioned in RFC - * 4314 examples and is also understood by at least Cyrus IMAP. Having - * '-' before the identifier specifies negative rights.</cite> - * - * @see MailboxACL#DEFAULT_GROUP_MARKER - * @see MailboxACL#DEFAULT_NEGATIVE_MARKER - * - * @return serialized form as a {@link String} - */ - String serialize(); - } + /** + * Marks negative when (de)serializing {@link MailboxACLEntryKey}s. + * + * @see MailboxACLEntryKey#serialize() + */ + public static final char DEFAULT_NEGATIVE_MARKER = '-'; + + /** + * SETACL third argument prefix + */ + public static final char REMOVE_RIGHTS_MARKER = '-'; /** * Single right applicable to a mailbox. */ - interface MailboxACLRight { + public enum Right { + Administer('a'), // (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) + PerformExpunge('e'), //perform EXPUNGE and expunge as a part of CLOSE + Insert('i'), //insert (perform APPEND, COPY into mailbox) + /* + * create mailboxes (CREATE new sub-mailboxes in any + * implementation-defined hierarchy, parent mailbox for the new mailbox + * name in RENAME) + * */ + CreateMailbox('k'), + Lookup('l'), //lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox) + Post('p'), //post (send mail to submission address for mailbox, not enforced by IMAP4 itself) + Read('r'), //read (SELECT the mailbox, perform STATUS) + /** + * keep seen/unseen information across sessions (set or clear \SEEN + * flag via STORE, also set \SEEN during APPEND/COPY/ FETCH BODY[...]) + */ + WriteSeenFlag('s'), + DeleteMessages('t'), //delete messages (set or clear \DELETED flag via STORE, set \DELETED flag during APPEND/COPY) + /** + * write (set or clear flags other than \SEEN and \DELETED via + * STORE, also set them during APPEND/COPY) + */ + Write('w'), + DeleteMailbox('x'); //delete mailbox (DELETE mailbox, old mailbox name in RENAME) + + private final char rightCharacter; + + Right(char rightCharacter) { + this.rightCharacter = rightCharacter; + } + /** * Returns the char representation of this right. - * + * * @return char representation of this right */ - char asCharacter(); + public char asCharacter() { + return rightCharacter; + } + + public static final EnumSet<Right> allRights = EnumSet.allOf(Right.class); + + public static Right forChar(char c) throws UnsupportedRightException { + return Right.allRights + .stream() + .filter(r -> r.asCharacter() == c) + .findFirst() + .orElseThrow(() -> new UnsupportedRightException(c)); + } } /** - * Iterable set of {@link MailboxACLRight}s. - * + * Holds the collection of {@link MailboxACL.Right}s. + * * Implementations may decide to support only a specific range of rights, * e.g. the Standard Rights of RFC 4314 section 2.1. - * + * * Implementations must not allow adding or removing of elements once this * MailboxACLRights are initialized. */ - interface MailboxACLRights extends Iterable<MailboxACLRight> { + public static class Rfc4314Rights { + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + public enum CompatibilityMode { + ck_detx, ckx_det, NO_COMPATIBILITY + } + + private static final char c_ObsoleteCreate = 'c'; + private static final char d_ObsoleteDelete = 'd'; + + /** + * See RFC 4314 section 2.1.1. Obsolete Rights. + */ + private final CompatibilityMode compatibilityMode = CompatibilityMode.ckx_det; + + private final EnumSet<Right> value; + + private Rfc4314Rights(EnumSet<Right> rights) { + this.value = EnumSet.copyOf(rights); + } + + private Rfc4314Rights() { + this(EnumSet.noneOf(Right.class)); + } + + public Rfc4314Rights(Right... rights) { + this(EnumSet.copyOf(Arrays.asList(rights))); + } + + public Rfc4314Rights(Right right) throws UnsupportedRightException { + this.value = EnumSet.of(Right.forChar(right.asCharacter())); + } + + /* Used for json serialization (probably a bad idea) */ + public Rfc4314Rights(int serializedRights) { + List<Right> rights = Right.allRights.stream() + .filter(right -> ((serializedRights >> right.ordinal()) & 1) != 0) + .collect(Collectors.toList()); + if (rights.isEmpty()) { + this.value = EnumSet.noneOf(Right.class); + } else { + this.value = EnumSet.copyOf(rights); + } + } + + public Rfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException { + List<Right> rights = serializedRfc4314Rights.chars() + .mapToObj(i -> (char) i) + .flatMap(Throwing.function(this::convert).sneakyThrow()) + .collect(Collectors.toList()); + if (rights.isEmpty()) { + this.value = EnumSet.noneOf(Right.class); + } else { + this.value = EnumSet.copyOf(rights); + } + } + + private Stream<Right> convert(char flag) throws UnsupportedRightException { + switch (flag) { + case c_ObsoleteCreate: + return convertObsoleteCreate(flag); + case d_ObsoleteDelete: + return convertObsoleteDelete(flag); + default: + return Stream.of(Right.forChar(flag)); + } + } + + private Stream<Right> convertObsoleteDelete(char flag) throws UnsupportedRightException { + switch (compatibilityMode) { + case ck_detx: + return Stream.of(Right.PerformExpunge, Right.DeleteMessages, Right.DeleteMailbox); + case ckx_det: + return Stream.of(Right.PerformExpunge, Right.DeleteMessages); + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + } + + private Stream<Right> convertObsoleteCreate(char flag) throws UnsupportedRightException { + switch (compatibilityMode) { + case ck_detx: + return Stream.of(Right.CreateMailbox); + case ckx_det: + return Stream.of(Right.CreateMailbox, Right.DeleteMailbox); + case NO_COMPATIBILITY: + throw new UnsupportedRightException(flag); + default: + throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name()); + } + } /** * Tells whether this contains the given right. - * - * @param right - * @return - * @throws UnsupportedRightException - * iff the given right is not supported. + * + * @throws UnsupportedRightException if the given right is not supported. */ - boolean contains(MailboxACLRight right) throws UnsupportedRightException; + public boolean contains(char flag) throws UnsupportedRightException { + return contains(Right.forChar(flag)); + } + + public boolean contains(Right right) throws UnsupportedRightException { + return value.contains(Right.forChar(right.asCharacter())); + } + + /* Used for json serialization (probably a bad idea) */ + public int serializeAsInteger() { + return value.stream().mapToInt(x -> 1 << x.ordinal()).sum(); + } + + public boolean equals(Object o) { + if (o instanceof Rfc4314Rights) { + Rfc4314Rights that = (Rfc4314Rights) o; + return this.value.equals(that.value); + } + return false; + } /** * Performs the set theoretic operation of relative complement of * toRemove MailboxACLRights in this MailboxACLRights. - * + * * A schematic example: "lrw".except("w") returns "lr". - * + * * Implementations must return a new unmodifiable instance of - * {@link MailboxACLRights}. However, implementations may decide to + * {@link MailboxACL.MailboxACLRights}. However, implementations may decide to * return this or toRemove parameter value in case the result would be * equal to the respective one of those. - * - * @param toRemove - * @return + * * @throws UnsupportedRightException */ - MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException; + public Rfc4314Rights except(Rfc4314Rights toRemove) throws UnsupportedRightException { + EnumSet<Right> copy = EnumSet.copyOf(value); + copy.removeAll(convertRightsToList(toRemove)); + return new Rfc4314Rights(copy); + } /** * Tells if this set of rights is empty. - * + * * @return true if there are no rights in this set; false otherwise. */ - boolean isEmpty(); + public boolean isEmpty() { + return value.isEmpty(); + } /** * Tells whether the implementation supports the given right. - * + * * @param right * @return true if this supports the given right. */ - boolean isSupported(MailboxACLRight right); + public boolean isSupported(Right right) { + try { + contains(right.asCharacter()); + return true; + } catch (UnsupportedRightException e) { + return false; + } + } + + public Iterator<Right> iterator() { + ImmutableList<Right> rights = ImmutableList.copyOf(value); + return rights.iterator(); + } + + public List<Right> list() { + return ImmutableList.copyOf(value); + } /** - * Returns a serialized form of this {@link MailboxACLRights} as + * Returns a serialized form of this {@link MailboxACL.Right} as * {@link String}. - * + * * @return a {@link String} */ - String serialize(); + public String serialize() { + return value.stream() + .map(Right::asCharacter) + .map(String::valueOf) + .collect(Collectors.joining()); + } + + public String toString() { + return serialize(); + } /** - * Performs the set theoretic operation of union of this - * MailboxACLRights and toAdd MailboxACLRights. - * + * Performs the theoretic operation of union of this + * Rfc4314Rights and toAdd Rfc4314Rights. + * * A schematic example: "lr".union("rw") returns "lrw". - * + * * Implementations must return a new unmodifiable instance of - * {@link MailboxACLRights}. However, implementations may decide to - * return this or toAdd parameter value in case the result would be - * equal to the respective one of those. - * + * {@link MailboxACL.Rfc4314Rights}. + * * @param toAdd * @return union of this and toAdd * @throws UnsupportedRightException - * + * */ - MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException; + public Rfc4314Rights union(Rfc4314Rights toAdd) throws UnsupportedRightException { + Preconditions.checkNotNull(toAdd); + EnumSet<Right> rightUnion = EnumSet.noneOf(Right.class); + rightUnion.addAll(value); + rightUnion.addAll(convertRightsToList(toAdd)); + return new Rfc4314Rights(rightUnion); + } + + private List<Right> convertRightsToList(Rfc4314Rights toAdd) { + return Optional.ofNullable(toAdd).orElse(Rfc4314Rights.empty()) + .list() + .stream() + .map(Throwing.function(right -> Right.forChar(right.asCharacter()))) + .collect(Guavate.toImmutableList()); + } + + private static Rfc4314Rights empty() { + return new Rfc4314Rights(); + } + + } + /** + * A utility implementation of + * {@code Map.Entry<EntryKey, Rfc4314Rights>}. + */ + public static class Entry extends AbstractMap.SimpleEntry<EntryKey, Rfc4314Rights> { + public Entry(EntryKey key, Rfc4314Rights value) { + super(key, value); + } + + public Entry(String key, String value) throws UnsupportedRightException { + this(EntryKey.deserialize(key), new Rfc4314Rights(value)); + } } /** - * Allows distinguishing between users, groups and special names (see - * {@link SpecialName}). + * The key used in {@link MailboxACL#getEntries()}. Implementations should + * override {@link #hashCode()} and {@link #equals(Object)} in such a way + * that all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} are significant. + * */ + public static class EntryKey { + public static EntryKey createGroup(String name) { + return new EntryKey(name, NameType.group, false); + } + + public static EntryKey createGroup(String name, boolean negative) { + return new EntryKey(name, NameType.group, negative); + } + + public static EntryKey createUser(String name) { + return new EntryKey(name, NameType.user, false); + } - interface MailboxACLCommand { - MailboxACLEntryKey getEntryKey(); + public static EntryKey createUser(String name, boolean negative) { + return new EntryKey(name, NameType.user, negative); + } - EditMode getEditMode(); + private final String name; + private final NameType nameType; + private final boolean negative; + + /** + * Creates a new instance of SimpleMailboxACLEntryKey from the given + * serialized {@link String}. It supposes that negative rights are + * marked with {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and that + * groups are marked with {@link MailboxACL#DEFAULT_GROUP_MARKER}. + * + * @param serialized + */ + public static EntryKey deserialize(String serialized) { + Preconditions.checkNotNull(serialized, "Cannot parse null"); + Preconditions.checkArgument(!serialized.isEmpty(), "Cannot parse an empty string"); + + boolean negative = serialized.charAt(0) == DEFAULT_NEGATIVE_MARKER; + int nameStart = negative ? 1 : 0; + boolean isGroup = serialized.charAt(nameStart) == DEFAULT_GROUP_MARKER; + Optional<NameType> explicitNameType = isGroup ? Optional.of(NameType.group) : Optional.empty(); + String name = isGroup ? serialized.substring(nameStart + 1) : serialized.substring(nameStart); + + if (name.isEmpty()) { + throw new IllegalStateException("Cannot parse a string with empty name"); + } + NameType nameType = explicitNameType.orElseGet(() -> computeImplicitNameType(name)); + + return new EntryKey(name, nameType, negative); + } + + private static NameType computeImplicitNameType(String name) { + boolean isSpecialName = Arrays.stream(SpecialName.values()) + .anyMatch(specialName -> specialName.name().equals(name)); + if (isSpecialName) { + return NameType.special; + } + return NameType.user; + } + + public EntryKey(String name, NameType nameType, boolean negative) { + Preconditions.checkNotNull(name, "Provide a name for this " + getClass().getName()); + Preconditions.checkNotNull(nameType, "Provide a nameType for this " + getClass().getName()); + + this.name = name; + this.nameType = nameType; + this.negative = negative; + } + + public boolean equals(Object o) { + if (o instanceof EntryKey) { + EntryKey other = (EntryKey) o; + return Objects.equals(this.name, other.getName()) + && Objects.equals(this.nameType, other.getNameType()) + && Objects.equals(this.negative, other.isNegative()); + } + return false; + } - MailboxACLRights getRights(); + /** + * Returns the name of a user or of a group to which this + * {@link MailboxACL.MailboxACLEntryKey} applies. + * + * @return User name, group name or special name. + */ + public String getName() { + return name; + } + + /** + * Tells of what type is the name returned by {@link #getName()}. + * + * @return type of the name returned by {@link #getName()} + */ + public NameType getNameType() { + return nameType; + } + + public final int hashCode() { + return Objects.hash(negative, nameType, name); + } + + /** + * If true the {@link MailboxACL.MailboxACLRights} returned by + * {@link MailboxACLEntry#getRights()} should be interpreted as + * "negative rights" as described in RFC4314: If the identifier "-fred" + * is granted the "w" right, that indicates that the "w" right is to be + * removed from users matching the identifier "fred", even though the + * user "fred" might have the "w" right as a consequence of some other + * identifier in the ACL. + * + * Note that {@link MailboxACLEntry#getName()} does not start with "-" + * when {@link MailboxACLEntry#getRights()} returns true. + * + * @return + */ + public boolean isNegative() { + return negative; + } + + /** + * Returns a serialized form of this {@link MailboxACL.MailboxACLEntryKey} as a + * {@link String}. Implementations should choose a consistent way how + * all of {@link #getName()}, {@link #getNameType()} and + * {@link #isNegative()} get serialized. + * + * RFC4314 sction 2. states: All user name strings accepted by the LOGIN + * or AUTHENTICATE commands to authenticate to the IMAP server are + * reserved as identifiers for the corresponding users. Identifiers + * starting with a dash ("-") are reserved for "negative rights", + * described below. All other identifier strings are interpreted in an + * implementation-defined manner. + * + * Dovecot and Cyrus mark groups with '$' prefix. See <a + * href="http://wiki2.dovecot.org/SharedMailboxes/Shared" + * >http://wiki2.dovecot.org/SharedMailboxes/Shared</a>: + * + * <cite>The $group syntax is not a standard, but it is mentioned in RFC + * 4314 examples and is also understood by at least Cyrus IMAP. Having + * '-' before the identifier specifies negative rights.</cite> + * + * @see MailboxACL#DEFAULT_GROUP_MARKER + * @see MailboxACL#DEFAULT_NEGATIVE_MARKER + * + * @return serialized form as a {@link String} + */ + public String serialize() { + String negativePart = negative ? String.valueOf(DEFAULT_NEGATIVE_MARKER) : ""; + String nameTypePart = nameType == NameType.group ? String.valueOf(DEFAULT_GROUP_MARKER) : ""; + + return negativePart + nameTypePart + name; + } + + public String toString() { + return serialize(); + } } - enum NameType { - group, special, user + + public static class ACLCommand { + private final EntryKey key; + private final EditMode editMode; + private final Rfc4314Rights rights; + + public ACLCommand(EntryKey key, EditMode editMode, Rfc4314Rights rights) { + this.key = key; + this.editMode = editMode; + this.rights = rights; + } + + public EntryKey getEntryKey() { + return key; + } + + public EditMode getEditMode() { + return editMode; + } + + public Rfc4314Rights getRights() { + return rights; + } + + public final boolean equals(Object o) { + if (o instanceof ACLCommand) { + ACLCommand that = (ACLCommand) o; + + return Objects.equals(this.key, that.key) + && Objects.equals(this.editMode, that.editMode) + && Objects.equals(this.rights, that.rights); + } + return false; + } + + public final int hashCode() { + return Objects.hash(key, editMode, rights); + } } - /** - * Special name literals. - */ - enum SpecialName { - anybody, authenticated, owner + public static final EntryKey ANYBODY_KEY; + public static final EntryKey ANYBODY_NEGATIVE_KEY; + public static final EntryKey AUTHENTICATED_KEY; + public static final EntryKey AUTHENTICATED_NEGATIVE_KEY; + public static final MailboxACL EMPTY; + + public static final Rfc4314Rights FULL_RIGHTS; + + public static final Rfc4314Rights NO_RIGHTS; + public static final MailboxACL OWNER_FULL_ACL; + public static final MailboxACL OWNER_FULL_EXCEPT_ADMINISTRATION_ACL; + + public static final EntryKey OWNER_KEY; + public static final EntryKey OWNER_NEGATIVE_KEY; + + static { + try { + ANYBODY_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, false); + ANYBODY_NEGATIVE_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, true); + AUTHENTICATED_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, false); + AUTHENTICATED_NEGATIVE_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, true); + EMPTY = new MailboxACL(); + FULL_RIGHTS = new Rfc4314Rights(Right.allRights); + NO_RIGHTS = new Rfc4314Rights(); + OWNER_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, false); + OWNER_NEGATIVE_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, true); + OWNER_FULL_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS) }); + OWNER_FULL_EXCEPT_ADMINISTRATION_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS.except(new Rfc4314Rights(Right.Administer))) }); + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + } + + private static Map<EntryKey, Rfc4314Rights> toMap(Properties props) throws UnsupportedRightException { + ImmutableMap.Builder<EntryKey, Rfc4314Rights> builder = ImmutableMap.builder(); + for (Map.Entry prop : props.entrySet()) { + builder.put(EntryKey.deserialize((String) prop.getKey()), new Rfc4314Rights((String) prop.getValue())); + } + return builder.build(); } + + private final Map<EntryKey, Rfc4314Rights> entries; /** - * SETACL third argument prefix + * Creates a new instance of SimpleMailboxACL containing no entries. + * */ - char ADD_RIGHTS_MARKER = '+'; + public MailboxACL() { + this(ImmutableMap.of()); + } /** - * Marks groups when (de)serializing {@link MailboxACLEntryKey}s. + * Creates a new instance of SimpleMailboxACL from the given array of + * entries. * - * @see MailboxACLEntryKey#serialize() + * @param entries */ - char DEFAULT_GROUP_MARKER = '$'; + public MailboxACL(Map.Entry<EntryKey, Rfc4314Rights>... entries) { + this(ImmutableMap.copyOf( + Optional.ofNullable(entries) + .map(array -> Arrays.stream(array) + .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))) + .orElse(ImmutableMap.of()))); + } /** - * Marks negative when (de)serializing {@link MailboxACLEntryKey}s. - * - * @see MailboxACLEntryKey#serialize() + * Creates a new instance of SimpleMailboxACL from the given {@link Map} of + * entries. + * + * @param entries */ - char DEFAULT_NEGATIVE_MARKER = '-'; + public MailboxACL(Map<EntryKey, Rfc4314Rights> entries) { + Preconditions.checkNotNull(entries); + + this.entries = ImmutableMap.copyOf(entries); + } /** - * SETACL third argument prefix + * Creates a new instance of SimpleMailboxACL from {@link Properties}. The + * keys and values from the <code>props</code> parameter are parsed by the + * {@link String} constructors of {@link EntryKey} and + * {@link Rfc4314Rights} respectively. + * + * @param props + * @throws UnsupportedRightException */ - char REMOVE_RIGHTS_MARKER = '-'; + public MailboxACL(Properties props) throws UnsupportedRightException { + this(toMap(props)); + } + + public boolean equals(Object o) { + if (o instanceof MailboxACL) { + MailboxACL acl = (MailboxACL) o; + return Objects.equals(this.getEntries(), acl.getEntries()); + } + return false; + } + + public int hashCode() { + return Objects.hash(entries); + } /** * Apply the given ACL update on current ACL and return the result as a new ACL. @@ -256,96 +707,144 @@ public interface MailboxACL { * @return Copy of current ACL updated * @throws UnsupportedRightException */ - MailboxACL apply(MailboxACLCommand aclUpdate) throws UnsupportedRightException; + public MailboxACL apply(ACLCommand aclUpdate) throws UnsupportedRightException { + switch (aclUpdate.getEditMode()) { + case ADD: + return union(aclUpdate.getEntryKey(), aclUpdate.getRights()); + case REMOVE: + return except(aclUpdate.getEntryKey(), aclUpdate.getRights()); + case REPLACE: + return replace(aclUpdate.getEntryKey(), aclUpdate.getRights()); + } + throw new RuntimeException("Unknown edit mode"); + } /** * Performs the set theoretic operation of relative complement of toRemove * {@link MailboxACL} in this {@link MailboxACL}. - * + * * A schematic example: "user1:lr;user2:lrwt".except("user1:w;user2:t") * returns "user1:lr;user2:lrw". - * + * * Implementations must return a new unmodifiable instance of * {@link MailboxACL}. However, implementations may decide to return this or * toRemove parameter value in case the result would be equal to the * respective one of those. - * + * * Implementations must ensure that the result does not contain entries with * empty rigths. E.g. "user1:lr;user2:lrwt".except("user1:lr") should return * "user2:lrwt" rather than "user1:;user2:lrwt" - * + * * @param toRemove * @return * @throws UnsupportedRightException */ - MailboxACL except(MailboxACL toRemove) throws UnsupportedRightException; + public MailboxACL except(MailboxACL other) throws UnsupportedRightException { + return new MailboxACL(entries.entrySet() + .stream() + .map(entry -> Pair.of(entry.getKey(), + Optional.ofNullable(other.getEntries().get(entry.getKey())) + .map(Throwing.function(exceptValue -> entry.getValue().except(exceptValue))) + .orElse(entry.getValue()))) + .filter(pair -> !pair.getValue().isEmpty()) + .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); + } - /** - * TODO except. - * - * @param key - * @param toRemove - * @return - * @throws UnsupportedRightException - */ - MailboxACL except(MailboxACLEntryKey key, MailboxACLRights toRemove) throws UnsupportedRightException; + public MailboxACL except(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException { + return except(new MailboxACL(new Entry(key, mailboxACLRights))); + } /** * {@link Map} of entries. - * + * * @return the entries. */ - Map<MailboxACLEntryKey, MailboxACLRights> getEntries(); + public Map<EntryKey, Rfc4314Rights> getEntries() { + return entries; + } /** * Replaces the entry corresponding to the given {@code key} with * {@code toAdd}link MailboxACLRights}. - * + * * Implementations must return a new unmodifiable instance of * {@link MailboxACL}. However, implementations may decide to return this in * case the result would be equal to it. - * + * * Implementations must ensure that the result does not contain entries with * empty rigths. E.g. "user1:lr;user2:lrwt".replace("user1", * MailboxACLRights.EMPTY) should return "user2:lrwt" rather than * "user1:;user2:lrwt". The same result should be returned by * "user1:lr;user2:lrwt".replace("user1", null). - * + * * @param key * @param toAdd * @return * @throws UnsupportedRightException */ - MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + public MailboxACL replace(EntryKey key, Rfc4314Rights replacement) throws UnsupportedRightException { + if (entries.containsKey(key)) { + return new MailboxACL( + entries.entrySet() + .stream() + .map(entry -> Pair.of(entry.getKey(), + entry.getKey().equals(key) ? replacement : entry.getValue())) + .filter(pair -> pair.getValue() != null && !pair.getValue().isEmpty()) + .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue))); + } else { + return new MailboxACL( + ImmutableMap.<EntryKey, Rfc4314Rights>builder() + .putAll(entries) + .put(key, replacement) + .build()); + } + } + + public String toString() { + return MoreObjects.toStringHelper(this) + .add("entries", entries) + .toString(); + } /** * Performs the set theoretic operation of union of this {@link MailboxACL} * and toAdd {@link MailboxACL}. - * + * * A schematic example: * "user1:lr;user2:lrwt".union("user1:at;-$group1:lrwt") returns * "user1:alrt;user2:lrwt;-$group1:lrwt". - * + * * Implementations must return a new unmodifiable instance of * {@link MailboxACL}. However, implementations may decide to return this or * toAdd parameter value in case the result would be equal to the respective * one of those. - * - * + * + * * @param toAdd * @return * @throws UnsupportedRightException */ - MailboxACL union(MailboxACL toAdd) throws UnsupportedRightException; + public MailboxACL union(MailboxACL other) throws UnsupportedRightException { + return new MailboxACL( + Stream.concat( + this.entries.entrySet().stream(), + other.getEntries().entrySet().stream()) + .collect(Guavate.toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)) + .asMap() + .entrySet() + .stream() + .map(entry -> Pair.of(entry.getKey(), + entry.getValue() + .stream() + .reduce( + new Rfc4314Rights(), + Throwing.binaryOperator(Rfc4314Rights::union)))) + .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue))); + } + - /** - * TODO union. - * - * @param key - * @param toAdd - * @return - * @throws UnsupportedRightException - */ - MailboxACL union(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException; + public MailboxACL union(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException { + return union(new MailboxACL(new Entry(key, mailboxACLRights))); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org