You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/05/26 10:30:48 UTC

[james-project] 03/07: JAMES-3909 IdentityUserDeletionTaskStep

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 8d732379d4d768154dc574be3a65a08702e25781
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri May 19 15:28:24 2023 +0700

    JAMES-3909 IdentityUserDeletionTaskStep
---
 .../identity/CassandraCustomIdentityDAO.scala      |  9 ++++
 .../jmap/api/identity/CustomIdentityDAO.scala      |  2 +
 .../identity/IdentityUserDeletionTaskStep.scala    | 35 ++++++++++++++
 .../memory/identity/MemoryCustomIdentityDAO.scala  |  2 +
 .../api/identity/CustomIdentityDAOContract.scala   | 27 ++++++++++-
 .../IdentityUserDeletionTaskStepTest.scala         | 55 ++++++++++++++++++++++
 6 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
index 1aa1628fc5..9078b49b31 100644
--- a/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
+++ b/server/data/data-jmap-cassandra/src/main/scala/org/apache/james/jmap/cassandra/identity/CassandraCustomIdentityDAO.scala
@@ -71,6 +71,10 @@ case class CassandraCustomIdentityDAO @Inject()(session: CqlSession,
     .whereColumn(ID).isEqualTo(bindMarker(ID))
     .build())
 
+  val deleteAllStatement: PreparedStatement = session.prepare(deleteFrom(TABLE_NAME)
+    .whereColumn(USER).isEqualTo(bindMarker(USER))
+    .build())
+
   override def save(user: Username, creationRequest: IdentityCreationRequest): SMono[Identity] =
     save(user, IdentityId.generate, creationRequest)
 
@@ -106,6 +110,11 @@ case class CassandraCustomIdentityDAO @Inject()(session: CqlSession,
         .setUuid(ID, id.id)))
       .`then`()
 
+  override def delete(username: Username): SMono[Unit] =
+    SMono(executor.executeVoid(deleteOneStatement.bind()
+        .setString(USER, username.asString())))
+      .`then`()
+
   private def insert(username: Username, identity: Identity): SMono[Identity] = {
     val replyTo: java.util.Set[UdtValue] = toJavaSet(identity.replyTo.getOrElse(List()))
     val bcc: java.util.Set[UdtValue] = toJavaSet(identity.bcc.getOrElse(List()))
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
index c3f80b8836..2a2fc672a7 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/CustomIdentityDAO.scala
@@ -147,6 +147,8 @@ trait CustomIdentityDAO {
   def upsert(user: Username, patch: Identity): SMono[Unit]
 
   def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit]
+
+  def delete(username: Username): Publisher[Unit]
 }
 
 class DefaultIdentitySupplier @Inject()(canSendFrom: CanSendFrom, usersRepository: UsersRepository) {
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala
new file mode 100644
index 0000000000..7eb3f2eb96
--- /dev/null
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/identity/IdentityUserDeletionTaskStep.scala
@@ -0,0 +1,35 @@
+/****************************************************************
+ * 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.api.identity
+
+import com.google.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.user.api.DeleteUserDataTaskStep
+import org.apache.james.user.api.DeleteUserDataTaskStep.StepName
+import org.reactivestreams.Publisher
+import reactor.core.publisher.Mono
+
+class IdentityUserDeletionTaskStep @Inject()(customIdentityDAO: CustomIdentityDAO) extends DeleteUserDataTaskStep {
+  override val name: StepName = new StepName("IdentityUserDeletionTaskStep")
+
+  override val priority: Int = 1
+
+  override def deleteUserData(username: Username): Publisher[Void] = Mono.from(customIdentityDAO.delete(username)).`then`()
+}
diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
index 9935e1570e..f9810ff176 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/memory/identity/MemoryCustomIdentityDAO.scala
@@ -56,4 +56,6 @@ class MemoryCustomIdentityDAO extends CustomIdentityDAO {
   override def delete(username: Username, ids: Seq[IdentityId]): Publisher[Unit] = SFlux.fromIterable(ids)
     .doOnNext(id => table.remove(username, id))
     .`then`()
+
+  override def delete(username: Username): Publisher[Unit] = SMono.fromCallable(() => table.rowMap().remove(username))
 }
\ No newline at end of file
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
index 7b12a95d6c..f692860d84 100644
--- a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/identity/CustomIdentityDAOContract.scala
@@ -30,8 +30,8 @@ import reactor.core.scala.publisher.{SFlux, SMono}
 import scala.jdk.OptionConverters._
 
 object CustomIdentityDAOContract {
-  private val bob: Username = Username.of("bob@localhost")
-  private val CREATION_REQUEST: IdentityCreationRequest = IdentityCreationRequest(name = Some(IdentityName("Bob (custom address)")),
+  val bob: Username = Username.of("bob@localhost")
+  val CREATION_REQUEST: IdentityCreationRequest = IdentityCreationRequest(name = Some(IdentityName("Bob (custom address)")),
     email = bob.asMailAddress(),
     replyTo = Some(List(EmailAddress(Some(EmailerName("My Boss")), new MailAddress("boss@domain.tld")))),
     bcc = Some(List(EmailAddress(Some(EmailerName("My Boss 2")), new MailAddress("boss2@domain.tld")))),
@@ -124,6 +124,17 @@ trait CustomIdentityDAOContract {
       .isEmpty()
   }
 
+  @Test
+  def saveShouldNotReturnDeletedAllValues(): Unit = {
+    val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee().delete(bob)).block()
+
+    assertThat(SFlux(testee().list(bob)).asJava().collectList().block())
+      .isEmpty()
+  }
+
   @Test
   def saveShouldDefineDefaultValuesInCaseSomePropertiesEmpty(): Unit = {
     val identity: Identity = SMono(testee().save(bob,
@@ -158,6 +169,18 @@ trait CustomIdentityDAOContract {
       .isEmpty()
   }
 
+  @Test
+  def deleteAllShouldBeIdempotent(): Unit = {
+    val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee().delete(bob)).block()
+    SMono(testee().delete(bob)).block()
+
+    assertThat(SFlux(testee().list(bob)).asJava().collectList().block())
+      .isEmpty()
+  }
+
   @Test
   def updateShouldModifyUnderlyingRecord(): Unit = {
     val identity: Identity = SMono(testee().save(bob, CREATION_REQUEST))
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala
new file mode 100644
index 0000000000..23e9db7df9
--- /dev/null
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/memory/identity/IdentityUserDeletionTaskStepTest.scala
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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.memory.identity
+
+import org.apache.james.jmap.api.identity.CustomIdentityDAOContract.{CREATION_REQUEST, bob}
+import org.apache.james.jmap.api.identity.IdentityUserDeletionTaskStep
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import reactor.core.publisher.Flux
+import reactor.core.scala.publisher.SMono
+
+class IdentityUserDeletionTaskStepTest {
+  var identityDAO: MemoryCustomIdentityDAO = _
+  var testee: IdentityUserDeletionTaskStep = _
+
+  @BeforeEach
+  def setUp(): Unit = {
+    identityDAO = new MemoryCustomIdentityDAO()
+    testee = new IdentityUserDeletionTaskStep(identityDAO)
+  }
+
+  @Test
+  def shouldDeleteUserIdentity(): Unit = {
+    SMono(identityDAO
+      .save(bob, CREATION_REQUEST))
+      .block()
+
+    SMono(testee.deleteUserData(bob)).block()
+
+    assertThat(Flux.from(identityDAO.list(bob)).collectList().block()).isEmpty()
+  }
+
+  @Test
+  def shouldBeIdempotent(): Unit = {
+    assertThatCode(() => SMono(testee.deleteUserData(bob)).block())
+      .doesNotThrowAnyException()
+  }
+}


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