This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push: new 4c4d6d3df8 James-4097 Allow disabling same-domain requirement when assigning rights (#2573) 4c4d6d3df8 is described below commit 4c4d6d3df8f47e7c598e4ef86764bab0f9effccd Author: j-hellenberg <47941549+j-hellenb...@users.noreply.github.com> AuthorDate: Mon Dec 23 23:13:55 2024 +0100 James-4097 Allow disabling same-domain requirement when assigning rights (#2573) --- docs/modules/servers/partials/configure/jvm.adoc | 15 +++++- .../james/mailbox/store/StoreRightManager.java | 21 +++++--- .../james/mailbox/store/StoreRightManagerTest.java | 57 ++++++++++++++++++++-- .../docker-configuration/jvm.properties | 5 +- .../sample-configuration/jvm.properties | 5 +- .../docker-configuration/jvm.properties | 5 +- .../sample-configuration/jvm.properties | 5 +- .../docker-configuration/jvm.properties | 5 +- .../sample-configuration/jvm.properties | 5 +- .../jpa-app/sample-configuration/jvm.properties | 5 +- .../memory-app/sample-configuration/jvm.properties | 5 +- 11 files changed, 113 insertions(+), 20 deletions(-) diff --git a/docs/modules/servers/partials/configure/jvm.adoc b/docs/modules/servers/partials/configure/jvm.adoc index 15e409047b..fdc71ef205 100644 --- a/docs/modules/servers/partials/configure/jvm.adoc +++ b/docs/modules/servers/partials/configure/jvm.adoc @@ -172,4 +172,17 @@ james.deduplicating.blobstore.thread.switch.threshold=32768 # Count of octet from which streams are buffered to files and not to memory james.deduplicating.blobstore.file.threshold=10240 ----- \ No newline at end of file +---- + +== Allow users to have rights for shares of different domain + +Typically, preventing users to obtain rights for shares of another domain is a useful security layer. +However, in multi-tenancy deployments, this can be useful (for example, students might be given access to a shared mailbox +residing under the @university.edu domain with their @student.university.edu address). + +Optional. Boolean. Defaults to false. + +Ex in `jvm.properties` +---- +james.rights.crossdomain.allow=false +---- diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java index 1a22ae328e..49ad62fe69 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java @@ -61,6 +61,9 @@ import com.google.common.collect.ImmutableMap; import reactor.core.publisher.Mono; public class StoreRightManager implements RightManager { + @VisibleForTesting + static Boolean IS_CROSS_DOMAIN_ACCESS_ALLOWED = Boolean.parseBoolean(System.getProperty("james.rights.crossdomain.allow", "false")); + private final EventBus eventBus; private final MailboxSessionMapperFactory mailboxSessionMapperFactory; private final MailboxACLResolver aclResolver; @@ -185,7 +188,7 @@ public class StoreRightManager implements RightManager { @Override public Mono<Void> applyRightsCommandReactive(MailboxPath mailboxPath, ACLCommand mailboxACLCommand, MailboxSession session) { return Mono.just(mailboxSessionMapperFactory.getMailboxMapper(session)) - .doOnNext(Throwing.consumer(mapper -> assertSharesBelongsToUserDomain(mailboxPath.getUser(), mailboxACLCommand))) + .doOnNext(Throwing.consumer(mapper -> assertUserHasAccessToShareeDomains(mailboxPath.getUser(), mailboxACLCommand))) .flatMap(mapper -> mapper.findMailboxByPath(mailboxPath) .doOnNext(Throwing.consumer(mailbox -> assertHaveAccessTo(mailbox, session))) .flatMap(mailbox -> mapper.updateACL(mailbox, mailboxACLCommand) @@ -211,8 +214,8 @@ public class StoreRightManager implements RightManager { applyRightsCommand(mailbox.generateAssociatedPath(), mailboxACLCommand, session); } - private void assertSharesBelongsToUserDomain(Username user, ACLCommand mailboxACLCommand) throws DifferentDomainException { - assertSharesBelongsToUserDomain(user, ImmutableMap.of(mailboxACLCommand.getEntryKey(), mailboxACLCommand.getRights())); + private void assertUserHasAccessToShareeDomains(Username user, ACLCommand mailboxACLCommand) throws DifferentDomainException { + assertUserHasAccessToShareeDomains(user, ImmutableMap.of(mailboxACLCommand.getEntryKey(), mailboxACLCommand.getRights())); } public boolean isReadWrite(MailboxSession session, Mailbox mailbox, Flags sharedPermanentFlags) { @@ -263,7 +266,7 @@ public class StoreRightManager implements RightManager { @Override public void setRights(MailboxPath mailboxPath, MailboxACL mailboxACL, MailboxSession session) throws MailboxException { - assertSharesBelongsToUserDomain(mailboxPath.getUser(), mailboxACL.getEntries()); + assertUserHasAccessToShareeDomains(mailboxPath.getUser(), mailboxACL.getEntries()); MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); block(mapper.findMailboxByPath(mailboxPath) @@ -285,7 +288,13 @@ public class StoreRightManager implements RightManager { } @VisibleForTesting - void assertSharesBelongsToUserDomain(Username user, Map<EntryKey, Rfc4314Rights> entries) throws DifferentDomainException { + void assertUserHasAccessToShareeDomains(Username user, Map<EntryKey, Rfc4314Rights> entries) throws DifferentDomainException { + if (IS_CROSS_DOMAIN_ACCESS_ALLOWED) { + // In this case, we impose no limitations. + return; + } + + // If cross-domain sharing is disabled, a user may only access their own domain. if (entries.keySet().stream() .filter(entry -> !entry.getNameType().equals(NameType.special)) .map(EntryKey::getName) @@ -342,7 +351,7 @@ public class StoreRightManager implements RightManager { */ public Mono<Void> setRightsReactiveWithoutAccessControl(MailboxPath mailboxPath, MailboxACL mailboxACL, MailboxSession session) { try { - assertSharesBelongsToUserDomain(mailboxPath.getUser(), mailboxACL.getEntries()); + assertUserHasAccessToShareeDomains(mailboxPath.getUser(), mailboxACL.getEntries()); } catch (DifferentDomainException e) { return Mono.error(e); } diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreRightManagerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreRightManagerTest.java index 6a8e189d1b..cb5b7f7b75 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreRightManagerTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreRightManagerTest.java @@ -24,18 +24,23 @@ import static org.apache.james.mailbox.fixture.MailboxFixture.BOB; import static org.apache.james.mailbox.fixture.MailboxFixture.CEDRIC; import static org.apache.james.mailbox.fixture.MailboxFixture.INBOX_ALICE; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import jakarta.mail.Flags; import org.apache.james.core.Username; +import org.apache.james.events.Event; import org.apache.james.events.EventBus; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MailboxSessionUtil; +import org.apache.james.mailbox.acl.ACLDiff; import org.apache.james.mailbox.acl.MailboxACLResolver; import org.apache.james.mailbox.acl.UnionMailboxACLResolver; +import org.apache.james.mailbox.events.MailboxIdRegistrationKey; import org.apache.james.mailbox.exception.DifferentDomainException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxNotFoundException; @@ -65,6 +70,7 @@ class StoreRightManagerTest { MailboxSession aliceSession; MailboxACLResolver mailboxAclResolver; MailboxMapper mockedMailboxMapper; + EventBus eventBus; @BeforeEach void setup() { @@ -72,7 +78,7 @@ class StoreRightManagerTest { MailboxSessionMapperFactory mockedMapperFactory = mock(MailboxSessionMapperFactory.class); mockedMailboxMapper = mock(MailboxMapper.class); mailboxAclResolver = new UnionMailboxACLResolver(); - EventBus eventBus = mock(EventBus.class); + eventBus = mock(EventBus.class); when(mockedMapperFactory.getMailboxMapper(aliceSession)) .thenReturn(mockedMailboxMapper); @@ -259,22 +265,37 @@ class StoreRightManagerTest { } @Test - void assertSharesBelongsToUserDomainShouldThrowWhenOneDomainIsDifferent() throws Exception { + void assertUserHasAccessToShareeDomainsShouldThrowWhenOneDomainIsDifferent() throws Exception { MailboxACL mailboxACL = new MailboxACL(new MailboxACL.Entry("a...@domain.org", Right.Write), new MailboxACL.Entry("b...@otherdomain.org", Right.Write), new MailboxACL.Entry("c...@domain.org", Right.Write)); - assertThatThrownBy(() -> storeRightManager.assertSharesBelongsToUserDomain(Username.of("u...@domain.org"), mailboxACL.getEntries())) + assertThatThrownBy(() -> storeRightManager.assertUserHasAccessToShareeDomains(Username.of("u...@domain.org"), mailboxACL.getEntries())) .isInstanceOf(DifferentDomainException.class); } @Test - void assertSharesBelongsToUserDomainShouldNotThrowWhenDomainsAreIdentical() throws Exception { + void assertUserHasAccessToShareeDomainsShouldNotThrowWhenDomainsAreIdentical() throws Exception { MailboxACL mailboxACL = new MailboxACL(new MailboxACL.Entry("a...@domain.org", Right.Write), new MailboxACL.Entry("b...@domain.org", Right.Write), new MailboxACL.Entry("c...@domain.org", Right.Write)); - storeRightManager.assertSharesBelongsToUserDomain(Username.of("u...@domain.org"), mailboxACL.getEntries()); + storeRightManager.assertUserHasAccessToShareeDomains(Username.of("u...@domain.org"), mailboxACL.getEntries()); + } + + @Test + void assertUserHasAccessToShareDomainsShouldNotThrowOnDifferentDomainsWhenCrossDomainAccessEnabled() throws Exception { + try { + StoreRightManager.IS_CROSS_DOMAIN_ACCESS_ALLOWED = true; + + MailboxACL mailboxACL = new MailboxACL(new MailboxACL.Entry("a...@domain.org", Right.Write), + new MailboxACL.Entry("b...@otherdomain.org", Right.Write), + new MailboxACL.Entry("c...@domain.org", Right.Write)); + + storeRightManager.assertUserHasAccessToShareeDomains(Username.of("u...@domain.org"), mailboxACL.getEntries()); + } finally { + StoreRightManager.IS_CROSS_DOMAIN_ACCESS_ALLOWED = false; + } } @Test @@ -288,4 +309,30 @@ class StoreRightManagerTest { assertThatThrownBy(() -> storeRightManager.applyRightsCommand(mailboxPath, aclCommand, aliceSession)) .isInstanceOf(DifferentDomainException.class); } + + @Test + void applyRightsCommandShouldNotThrowOnDifferentDomainsWhenCrossDomainEnabled() throws MailboxException { + try { + StoreRightManager.IS_CROSS_DOMAIN_ACCESS_ALLOWED = true; + + MailboxPath mailboxPath = MailboxPath.forUser(Username.of("u...@domain.org"), "mailbox"); + Mailbox mailbox = new Mailbox(mailboxPath, UID_VALIDITY, MAILBOX_ID); + mailbox.setACL(new MailboxACL(new MailboxACL.Entry(MailboxFixture.ALICE.asString(), Right.Administer))); + ACLCommand aclCommand = MailboxACL.command() + .forUser(Username.of("otheru...@otherdomain.org")) + .rights(Right.Read) + .asAddition(); + + when(mockedMailboxMapper.findMailboxByPath(mailboxPath)).thenReturn(Mono.just(mailbox)); + when(mockedMailboxMapper.updateACL(mailbox, aclCommand)).thenReturn(Mono.just(ACLDiff.computeDiff(MailboxACL.EMPTY, new MailboxACL( + new MailboxACL.Entry("u...@domain.org", Right.Read) + )))); + when(eventBus.dispatch(any(Event.class), any(MailboxIdRegistrationKey.class))).thenReturn(Mono.empty()); + + assertThatCode(() -> storeRightManager.applyRightsCommand(mailboxPath, aclCommand, aliceSession)) + .doesNotThrowAnyException(); + } finally { + StoreRightManager.IS_CROSS_DOMAIN_ACCESS_ALLOWED = false; + } + } } \ No newline at end of file diff --git a/server/apps/cassandra-app/docker-configuration/jvm.properties b/server/apps/cassandra-app/docker-configuration/jvm.properties index f638855ef4..85e932e7c9 100644 --- a/server/apps/cassandra-app/docker-configuration/jvm.properties +++ b/server/apps/cassandra-app/docker-configuration/jvm.properties @@ -49,4 +49,7 @@ sun.rmi.dgc.client.gcInterval=3600000000 # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/cassandra-app/sample-configuration/jvm.properties b/server/apps/cassandra-app/sample-configuration/jvm.properties index 3cc3d17386..82ea594769 100644 --- a/server/apps/cassandra-app/sample-configuration/jvm.properties +++ b/server/apps/cassandra-app/sample-configuration/jvm.properties @@ -72,4 +72,7 @@ jmx.remote.x.mlet.allow.getMBeansFromURL=false # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/distributed-app/docker-configuration/jvm.properties b/server/apps/distributed-app/docker-configuration/jvm.properties index b7183468f0..573d064bcc 100644 --- a/server/apps/distributed-app/docker-configuration/jvm.properties +++ b/server/apps/distributed-app/docker-configuration/jvm.properties @@ -49,4 +49,7 @@ sun.rmi.dgc.client.gcInterval=3600000000 # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/distributed-app/sample-configuration/jvm.properties b/server/apps/distributed-app/sample-configuration/jvm.properties index 3b20e8ddc7..a98626760d 100644 --- a/server/apps/distributed-app/sample-configuration/jvm.properties +++ b/server/apps/distributed-app/sample-configuration/jvm.properties @@ -96,4 +96,7 @@ jmx.remote.x.mlet.allow.getMBeansFromURL=false # james.deduplicating.blobstore.file.threshold=10240 # From which point should range operation be used? Defaults to 3. -# james.jmap.email.set.range.threshold=3 \ No newline at end of file +# james.jmap.email.set.range.threshold=3 + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false diff --git a/server/apps/distributed-pop3-app/docker-configuration/jvm.properties b/server/apps/distributed-pop3-app/docker-configuration/jvm.properties index f638855ef4..85e932e7c9 100644 --- a/server/apps/distributed-pop3-app/docker-configuration/jvm.properties +++ b/server/apps/distributed-pop3-app/docker-configuration/jvm.properties @@ -49,4 +49,7 @@ sun.rmi.dgc.client.gcInterval=3600000000 # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties index 2612e150ff..3901fdf50c 100644 --- a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties +++ b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties @@ -62,4 +62,7 @@ jmx.remote.x.mlet.allow.getMBeansFromURL=false # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/jpa-app/sample-configuration/jvm.properties b/server/apps/jpa-app/sample-configuration/jvm.properties index 68859135ac..d6d2e0ae4b 100644 --- a/server/apps/jpa-app/sample-configuration/jvm.properties +++ b/server/apps/jpa-app/sample-configuration/jvm.properties @@ -60,4 +60,7 @@ openjpa.Multithreaded=true # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file diff --git a/server/apps/memory-app/sample-configuration/jvm.properties b/server/apps/memory-app/sample-configuration/jvm.properties index 692bc6bf46..317261dd03 100644 --- a/server/apps/memory-app/sample-configuration/jvm.properties +++ b/server/apps/memory-app/sample-configuration/jvm.properties @@ -62,4 +62,7 @@ jmx.remote.x.mlet.allow.getMBeansFromURL=false # Relax validating `*` and `%` characters in the mailbox name. Defaults to false. # Be careful turning on this as `%` and `*` are ambiguous for the LIST / LSUB commands that interpret those as wildcard thus returning all mailboxes matching the pattern. -#james.relaxed.mailbox.name.validation=true \ No newline at end of file +#james.relaxed.mailbox.name.validation=true + +# Allow users to have rights for shares of different domain. Defaults to false. +#james.rights.crossdomain.allow=false \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org