You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2020/07/30 04:15:16 UTC

[james-project] 03/12: JAMES-3093 Port UserProvisioner from draft to jmap-rfc-8621

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

commit e1ae434403a20d977a4f1e46968975c6323fde14
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Jul 21 16:56:03 2020 +0700

    JAMES-3093 Port UserProvisioner from draft to jmap-rfc-8621
---
 .../apache/james/jmap/http/UserProvisioning.scala  |  64 +++++++++++++
 .../james/jmap/http/UserProvisioningTest.scala     | 103 +++++++++++++++++++++
 2 files changed, 167 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
new file mode 100644
index 0000000..95895e5
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.http
+
+import java.util.UUID
+
+import javax.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.metrics.api.MetricFactory
+import org.apache.james.metrics.api.TimeMetric.ExecutionResult.DEFAULT_100_MS_THRESHOLD
+import org.apache.james.user.api.{AlreadyExistInUsersRepositoryException, UsersRepository, UsersRepositoryException}
+import reactor.core.scala.publisher.SMono
+
+class UserProvisioning @Inject() (usersRepository: UsersRepository, metricFactory: MetricFactory) {
+
+  def provisionUser(session: MailboxSession): SMono[Unit] =
+    if (session != null && !usersRepository.isReadOnly) {
+      SMono.fromCallable(() => createAccountIfNeeded(session))
+        .`then`
+    } else {
+      SMono.empty
+    }
+
+  private def createAccountIfNeeded(session: MailboxSession): Unit = {
+    val timeMetric = metricFactory.timer("JMAP-RFC-8621-user-provisioning")
+    try {
+      val username = session.getUser
+      if (needsAccountCreation(username)) {
+        createAccount(username)
+      }
+    } catch {
+      case exception: AlreadyExistInUsersRepositoryException => // Ignore
+      case exception: UsersRepositoryException => throw new RuntimeException(exception)
+    } finally {
+      timeMetric.stopAndPublish.logWhenExceedP99(DEFAULT_100_MS_THRESHOLD)
+    }
+  }
+
+  @throws[UsersRepositoryException]
+  private def createAccount(username: Username): Unit = usersRepository.addUser(username, generatePassword)
+
+  @throws[UsersRepositoryException]
+  private def needsAccountCreation(username: Username): Boolean = !usersRepository.contains(username)
+
+  private def generatePassword: String = UUID.randomUUID.toString
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala
new file mode 100644
index 0000000..ff799a0
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala
@@ -0,0 +1,103 @@
+/****************************************************************
+ * 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.http
+
+import java.time.Duration
+
+import org.apache.james.core.Username
+import org.apache.james.domainlist.api.DomainList
+import org.apache.james.mailbox.{MailboxSession, MailboxSessionUtil}
+import org.apache.james.metrics.tests.RecordingMetricFactory
+import org.apache.james.user.api.{UsersRepository, UsersRepositoryException}
+import org.apache.james.user.memory.MemoryUsersRepository
+import org.apache.james.util.concurrency.ConcurrentTestRunner
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import org.mockito.Mockito.{mock, verify, verifyNoMoreInteractions, when}
+
+object UserProvisioningTest {
+  private val USERNAME: Username = Username.of("username")
+  private val USERNAME_WITH_DOMAIN: Username = Username.of("username@james.org")
+  private val NO_DOMAIN_LIST: DomainList = null
+}
+
+class UserProvisioningTest {
+  import UserProvisioningTest._
+
+  var testee: UserProvisioning = _
+  var usersRepository: MemoryUsersRepository = _
+
+  @BeforeEach
+  def setup(): Unit = {
+    usersRepository = MemoryUsersRepository.withoutVirtualHosting(NO_DOMAIN_LIST)
+    testee = new UserProvisioning(usersRepository, new RecordingMetricFactory)
+  }
+
+  @Test
+  def filterShouldDoNothingOnNullSession(): Unit = {
+    testee.provisionUser(null).block()
+
+    assertThat(usersRepository.list).toIterable
+      .isEmpty
+  }
+
+  @Test
+  def filterShouldAddUsernameWhenNoVirtualHostingAndMailboxSessionContainsUsername(): Unit = {
+    usersRepository.setEnableVirtualHosting(false)
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME)
+
+    testee.provisionUser(mailboxSession).block()
+
+    assertThat(usersRepository.list).toIterable
+      .contains(USERNAME)
+  }
+
+  @Test
+  def filterShouldFailOnInvalidVirtualHosting(): Unit = {
+    usersRepository.setEnableVirtualHosting(false)
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME_WITH_DOMAIN)
+
+    assertThatThrownBy(() => testee.provisionUser(mailboxSession).block())
+      .hasCauseInstanceOf(classOf[UsersRepositoryException])
+  }
+
+  @Test
+  def filterShouldNotTryToAddUserWhenReadOnlyUsersRepository(): Unit = {
+    val usersRepository: UsersRepository = mock(classOf[UsersRepository])
+    when(usersRepository.isReadOnly).thenReturn(true)
+    testee = new UserProvisioning(usersRepository, new RecordingMetricFactory)
+
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME_WITH_DOMAIN)
+    testee.provisionUser(mailboxSession).block()
+
+    verify(usersRepository).isReadOnly
+    verifyNoMoreInteractions(usersRepository)
+  }
+
+  @Test
+  def testConcurrentAccessToFilterShouldNotThrow(): Unit = {
+    val session: MailboxSession = MailboxSessionUtil.create(USERNAME)
+
+    ConcurrentTestRunner.builder
+      .operation((threadNumber: Int, step: Int) => testee.provisionUser(session))
+      .threadCount(2)
+      .runSuccessfullyWithin(Duration.ofMinutes(1))
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org