JAMES-2162 Introduce JMAP Rights object
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/672f24f1 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/672f24f1 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/672f24f1 Branch: refs/heads/master Commit: 672f24f1e1034d02f9e11bfae0674a5d8409272d Parents: 6e9ad73 Author: benwa <[email protected]> Authored: Wed Sep 27 17:43:00 2017 +0700 Committer: benwa <[email protected]> Committed: Tue Oct 3 07:52:07 2017 +0700 ---------------------------------------------------------------------- .../apache/james/mailbox/model/MailboxACL.java | 5 + .../apache/james/jmap/model/mailbox/Rights.java | 240 +++++++++++++++++++ .../james/jmap/model/mailbox/RightsTest.java | 164 +++++++++++++ 3 files changed, 409 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/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 d1e1487..a831302 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 @@ -333,6 +333,11 @@ public class MailboxACL { public Entry(String key, String value) throws UnsupportedRightException { this(EntryKey.deserialize(key), Rfc4314Rights.fromSerializedRfc4314Rights(value)); } + + public Entry(String key, Right... rights) throws UnsupportedRightException { + this(EntryKey.deserialize(key), new Rfc4314Rights(rights)); + } + } /** http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java new file mode 100644 index 0000000..d44e4d3 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java @@ -0,0 +1,240 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.model.mailbox; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BinaryOperator; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.EntryKey; +import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights; +import org.apache.james.util.GuavaUtils; +import org.apache.james.util.OptionalUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.github.fge.lambdas.Throwing; +import com.github.steveash.guavate.Guavate; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; + +public class Rights { + public enum Right { + Administer(MailboxACL.Right.Administer), + Expunge(MailboxACL.Right.PerformExpunge), + Insert(MailboxACL.Right.Insert), + Lookup(MailboxACL.Right.Lookup), + Read(MailboxACL.Right.Read), + Seen(MailboxACL.Right.WriteSeenFlag), + DeleteMessages(MailboxACL.Right.DeleteMessages), + Write(MailboxACL.Right.Write); + + private final MailboxACL.Right right; + + Right(MailboxACL.Right right) { + this.right = right; + } + + @JsonValue + public char asCharacter() { + return right.asCharacter(); + } + + public MailboxACL.Right toMailboxRight() { + return right; + } + + public static Optional<Right> forRight(MailboxACL.Right right) { + return OptionalUtils.peekOnEmpty( + Arrays.stream(values()) + .filter(jmapRight -> jmapRight.right == right) + .findAny(), + () -> LOGGER.warn("Non handled right '" + right + "'")); + } + + public static Right forChar(char c) { + return Arrays.stream(values()) + .filter(right -> right.asCharacter() == c) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("No matching right for '" + c + "'")); + } + } + + public static class Username { + private final String value; + + // @JsonCreator + public Username(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof Username) { + Username username = (Username) o; + + return Objects.equals(this.value, username.value); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(value); + } + } + + public static class Builder { + private Multimap<Username, Right> rights; + + public Builder() { + rights = ArrayListMultimap.create(); + } + + public Builder delegateTo(Username username, Right... rights) { + delegateTo(username, Arrays.asList(rights)); + return this; + } + + public Builder delegateTo(Username username, Collection<Right> rights) { + this.rights.putAll(username, rights); + return this; + } + + public Builder combine(Builder builder) { + this.rights.putAll(builder.rights); + return this; + } + + public Rights build() { + return new Rights(rights); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static Rights fromACL(MailboxACL acl) { + return acl.getEntries() + .entrySet() + .stream() + .filter(entry -> isSupported(entry.getKey())) + .map(Rights::toRightsBuilder) + .reduce(builder(), Builder::combine) + .build(); + } + + private static Builder toRightsBuilder(Map.Entry<EntryKey, MailboxACL.Rfc4314Rights> entry) { + return builder().delegateTo( + new Username(entry.getKey().getName()), + fromACL(entry.getValue())); + } + + private static List<Right> fromACL(MailboxACL.Rfc4314Rights rights) { + return rights.list() + .stream() + .flatMap(right -> OptionalUtils.toStream(Right.forRight(right))) + .collect(Guavate.toImmutableList()); + } + + private static boolean isSupported(EntryKey key) { + if (key.isNegative()) { + LOGGER.info("Negative keys are not supported"); + return false; + } + if (key.getNameType() != MailboxACL.NameType.user) { + LOGGER.info(key.getNameType() + " is not supported. Only 'user' is."); + return false; + } + return true; + } + + public static final Rights EMPTY = new Rights(ArrayListMultimap.create()); + + private static final Logger LOGGER = LoggerFactory.getLogger(Rights.class); + + private final Multimap<Username, Right> rights; + + @JsonCreator + public Rights(Map<Username, List<Right>> rights) { + this(GuavaUtils.toMultimap(rights)); + } + + private Rights(Multimap<Username, Right> rights) { + this.rights = rights; + } + + @JsonAnyGetter + public Map<Username, Collection<Right>> getRights() { + return rights.asMap(); + } + + public MailboxACL toMailboxAcl() { + BinaryOperator<MailboxACL> union = Throwing.binaryOperator(MailboxACL::union); + + return rights.asMap() + .entrySet() + .stream() + .map(entry -> new MailboxACL( + ImmutableMap.of( + EntryKey.createUser(entry.getKey().value), + toMailboxAclRights(entry.getValue())))) + .reduce(MailboxACL.EMPTY, union); + } + + private Rfc4314Rights toMailboxAclRights(Collection<Right> rights) { + BinaryOperator<Rfc4314Rights> union = Throwing.binaryOperator(Rfc4314Rights::union); + + return rights.stream() + .map(Right::toMailboxRight) + .map(Throwing.function(Rfc4314Rights::new)) + .reduce(new Rfc4314Rights(), union); + } + + @Override + public final boolean equals(Object o) { + if (o instanceof Rights) { + Rights that = (Rights) o; + + return Objects.equals(this.rights, that.rights); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(rights); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java new file mode 100644 index 0000000..dae6130 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java @@ -0,0 +1,164 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.model.mailbox; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.Entry; +import org.apache.james.mailbox.model.MailboxACL.EntryKey; +import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +import nl.jqno.equalsverifier.EqualsVerifier; + +public class RightsTest { + + public static final boolean NEGATIVE = true; + + @Test + public void rightsShouldMatchBeanContract() { + EqualsVerifier.forClass(Rights.class) + .allFieldsShouldBeUsed() + .verify(); + } + + @Test + public void usernameShouldMatchBeanContract() { + EqualsVerifier.forClass(Rights.Username.class) + .allFieldsShouldBeUsed() + .verify(); + } + + @Test + public void forCharShouldReturnRightWhenA() { + assertThat(Rights.Right.forChar('a')) + .isEqualTo(Rights.Right.Administer); + } + + @Test + public void forCharShouldReturnRightWhenE() { + assertThat(Rights.Right.forChar('e')) + .isEqualTo(Rights.Right.Expunge); + } + + @Test + public void forCharShouldReturnRightWhenI() { + assertThat(Rights.Right.forChar('i')) + .isEqualTo(Rights.Right.Insert); + } + + @Test + public void forCharShouldReturnRightWhenL() { + assertThat(Rights.Right.forChar('l')) + .isEqualTo(Rights.Right.Lookup); + } + + @Test + public void forCharShouldReturnRightWhenR() { + assertThat(Rights.Right.forChar('r')) + .isEqualTo(Rights.Right.Read); + } + + @Test + public void forCharShouldReturnRightWhenW() { + assertThat(Rights.Right.forChar('w')) + .isEqualTo(Rights.Right.Write); + } + + @Test + public void forCharShouldReturnRightWhenT() { + assertThat(Rights.Right.forChar('t')) + .isEqualTo(Rights.Right.DeleteMessages); + } + + @Test + public void forCharShouldThrowOnUnsupportedRight() { + assertThatThrownBy(() -> Rights.Right.forChar('k')) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void fromACLShouldFilterOutGroups() throws Exception { + MailboxACL acl = new MailboxACL(ImmutableMap.of( + EntryKey.createGroup("group"), Rfc4314Rights.fromSerializedRfc4314Rights("aet"))); + + assertThat(Rights.fromACL(acl)) + .isEqualTo(Rights.EMPTY); + } + + @Test + public void fromACLShouldFilterNegatedUsers() throws Exception { + MailboxACL acl = new MailboxACL(ImmutableMap.of( + EntryKey.createUser("user", NEGATIVE), Rfc4314Rights.fromSerializedRfc4314Rights("aet"))); + + assertThat(Rights.fromACL(acl)) + .isEqualTo(Rights.EMPTY); + } + + @Test + public void fromACLShouldAcceptUsers() throws Exception { + MailboxACL acl = new MailboxACL(ImmutableMap.of( + EntryKey.createUser("user"), Rfc4314Rights.fromSerializedRfc4314Rights("aet"))); + + assertThat(Rights.fromACL(acl)) + .isEqualTo(Rights.builder() + .delegateTo(new Rights.Username("user"), Rights.Right.Administer, Rights.Right.Expunge, Rights.Right.DeleteMessages) + .build()); + } + + @Test + public void fromACLShouldFilterOutUnknownRights() throws Exception { + MailboxACL acl = new MailboxACL(ImmutableMap.of( + EntryKey.createUser("user"), Rfc4314Rights.fromSerializedRfc4314Rights("aetpk"))); + + assertThat(Rights.fromACL(acl)) + .isEqualTo(Rights.builder() + .delegateTo(new Rights.Username("user"), Rights.Right.Administer, Rights.Right.Expunge, Rights.Right.DeleteMessages) + .build()); + } + + @Test + public void toMailboxAclShouldReturnEmptyAclWhenEmpty() { + Rights rights = Rights.EMPTY; + + assertThat(rights.toMailboxAcl()) + .isEqualTo(new MailboxACL()); + } + + @Test + public void toMailboxAclShouldReturnAclConversion() throws Exception { + String user1 = "user1"; + String user2 = "user2"; + Rights rights = Rights.builder() + .delegateTo(new Rights.Username(user1), Rights.Right.Administer, Rights.Right.DeleteMessages) + .delegateTo(new Rights.Username(user2), Rights.Right.Expunge, Rights.Right.Lookup) + .build(); + + assertThat(rights.toMailboxAcl()) + .isEqualTo(new MailboxACL( + new Entry(user1, MailboxACL.Right.Administer, MailboxACL.Right.DeleteMessages), + new Entry(user2, MailboxACL.Right.PerformExpunge, MailboxACL.Right.Lookup))); + } + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
