diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java index 171ba9491be..f94faccf5f8 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/UserRepository.java @@ -21,6 +21,7 @@ import io.gravitee.am.model.common.Page; import io.gravitee.am.repository.common.CrudRepository; import io.gravitee.am.repository.management.api.search.FilterCriteria; +import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Single; @@ -60,4 +61,6 @@ public interface UserRepository extends CommonUserRepository { Single countByApplication(String domain, String application); Single> statistics(AnalyticsQuery query); + + Completable deleteByReference(ReferenceType referenceType, String referenceId); } diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcUserRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcUserRepository.java index ad61672fc87..30ac392144a 100644 --- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcUserRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcUserRepository.java @@ -538,4 +538,20 @@ private Single completeUser(User userToComplete) { ); } + @Override + public Completable deleteByReference(ReferenceType referenceType, String referenceId) { + LOGGER.debug("deleteByReference({}, {})", referenceType, referenceId); + TransactionalOperator trx = TransactionalOperator.create(tm); + Mono delete = dbClient.execute("DELETE FROM users WHERE reference_type = :refType AND reference_id = :refId").bind("refType", referenceType.name()).bind("refId", referenceId).fetch().rowsUpdated(); + return monoToCompletable(deleteChildEntitiesByDomain(referenceType.name(), referenceId).then(delete).as(trx::transactional)); + } + + + private Mono deleteChildEntitiesByDomain(String refType, String refId) { + Mono deleteRoles = dbClient.execute("DELETE FROM user_roles WHERE user_id IN (SELECT id FROM users u WHERE u.reference_type = :refType AND u.reference_id = :refId)").bind("refType", refType).bind("refId", refId).fetch().rowsUpdated(); + Mono deleteAddresses = dbClient.execute("DELETE FROM user_addresses WHERE user_id IN (SELECT id FROM users u WHERE u.reference_type = :refType AND u.reference_id = :refId)").bind("refType", refType).bind("refId", refId).fetch().rowsUpdated(); + Mono deleteAttributes = dbClient.execute("DELETE FROM user_attributes WHERE user_id IN (SELECT id FROM users u WHERE u.reference_type = :refType AND u.reference_id = :refId)").bind("refType", refType).bind("refId", refId).fetch().rowsUpdated(); + Mono deleteEntitlements = dbClient.execute("DELETE FROM user_entitlements WHERE user_id IN (SELECT id FROM users u WHERE u.reference_type = :refType AND u.reference_id = :refId)").bind("refType", refType).bind("refId", refId).fetch().rowsUpdated(); + return deleteRoles.then(deleteAddresses).then(deleteAttributes).then(deleteEntitlements); + } } diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/resources/logback.xml b/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/resources/logback.xml new file mode 100644 index 00000000000..e3a8481469b --- /dev/null +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/resources/logback.xml @@ -0,0 +1,35 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%thread] [%X{domain}] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java index 4b73772b0b7..10bdeeaa22c 100644 --- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoUserRepository.java @@ -24,10 +24,8 @@ import io.gravitee.am.model.analytics.AnalyticsQuery; import io.gravitee.am.repository.management.api.UserRepository; import io.gravitee.am.repository.mongodb.management.internal.model.UserMongo; -import io.reactivex.Flowable; -import io.reactivex.Maybe; import io.reactivex.Observable; -import io.reactivex.Single; +import io.reactivex.*; import org.bson.Document; import org.bson.conversions.Bson; import org.springframework.stereotype.Component; @@ -160,4 +158,9 @@ private Single> registrationsStatusRepartition(AnalyticsQuer protected UserMongo convert(User user) { return convert(user, new UserMongo()); } + + @Override + public Completable deleteByReference(ReferenceType referenceType, String referenceId) { + return Completable.fromPublisher(usersCollection.deleteMany(and(eq(FIELD_REFERENCE_TYPE, referenceType.name()), eq(FIELD_REFERENCE_ID, referenceId)))); + } } diff --git a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/UserRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/UserRepositoryTest.java index acd5b8966d6..f200c5001e7 100644 --- a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/UserRepositoryTest.java +++ b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/UserRepositoryTest.java @@ -32,6 +32,7 @@ import io.gravitee.am.repository.management.api.search.FilterCriteria; import io.reactivex.observers.TestObserver; import io.reactivex.subscribers.TestSubscriber; +import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -324,10 +325,7 @@ public void testUpdate() throws TechnicalException { @Test public void testDelete() throws TechnicalException { // create user - User user = new User(); - user.setReferenceType(ReferenceType.DOMAIN); - user.setReferenceId("domainId"); - user.setUsername("testsUsername"); + User user = buildUser(); User userCreated = userRepository.create(user).blockingGet(); // fetch user @@ -345,6 +343,46 @@ public void testDelete() throws TechnicalException { userRepository.findById(userCreated.getId()).test().assertEmpty(); } + @Test + public void testDeleteByDomain() throws TechnicalException { + final String DOMAIN_1 = "domain1"; + final String DOMAIN_2 = "domain2"; + + // create user + User user = buildUser(); + user.setReferenceId(DOMAIN_1); + user.setReferenceType(ReferenceType.DOMAIN); + userRepository.create(user).blockingGet(); + + user = buildUser(); + user.setReferenceId(DOMAIN_1); + user.setReferenceType(ReferenceType.DOMAIN); + userRepository.create(user).blockingGet(); + + user = buildUser(); + user.setReferenceId(DOMAIN_2); + user.setReferenceType(ReferenceType.DOMAIN); + userRepository.create(user).blockingGet(); + + final long usersDomain1 = userRepository.findAll(ReferenceType.DOMAIN, DOMAIN_1).count().blockingGet(); + Assert.assertEquals("Domain1 should have 2 users", 2, usersDomain1); + long usersDomain2 = userRepository.findAll(ReferenceType.DOMAIN, DOMAIN_2).count().blockingGet(); + Assert.assertEquals("Domain2 should have 1 users", 1, usersDomain2); + + // delete user + TestObserver testObserver1 = userRepository.deleteByReference(ReferenceType.DOMAIN, DOMAIN_1).test(); + testObserver1.awaitTerminalEvent(); + + // fetch user + final TestSubscriber find = userRepository.findAll(ReferenceType.DOMAIN, DOMAIN_1).test(); + find.awaitTerminalEvent(); + find.assertNoValues(); + + usersDomain2 = userRepository.findAll(ReferenceType.DOMAIN, DOMAIN_2).count().blockingGet(); + Assert.assertEquals("Domain2 should have 1 users", 1, usersDomain2); + + } + @Test public void testSearch_byUsername_strict() { testSearch_strict("testUsername"); diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java index 4461151a624..8663abbcda9 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/UserService.java @@ -58,4 +58,5 @@ public interface UserService extends CommonUserService { Single> statistics(AnalyticsQuery query); + Completable deleteByDomain(String domainId); } diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java index b4dc5f9cb6d..df33667bbbe 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java @@ -380,9 +380,7 @@ public Completable delete(String domainId, User principal) { }) ) // delete users - .andThen(userService.findByDomain(domainId) - .flatMapCompletable(user -> - userService.delete(user.getId())) + .andThen(userService.deleteByDomain(domainId) ) // delete groups .andThen(groupService.findByDomain(domainId) diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java index 7c785bc1698..73fd70864b3 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/UserServiceImpl.java @@ -17,7 +17,6 @@ import io.gravitee.am.common.event.Action; import io.gravitee.am.common.event.Type; -import io.gravitee.am.model.Group; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.User; import io.gravitee.am.model.analytics.AnalyticsQuery; @@ -25,13 +24,12 @@ import io.gravitee.am.model.common.event.Event; import io.gravitee.am.model.common.event.Payload; import io.gravitee.am.repository.management.api.UserRepository; -import io.gravitee.am.service.GroupService; -import io.gravitee.am.service.RoleService; import io.gravitee.am.service.UserService; import io.gravitee.am.service.exception.AbstractManagementException; import io.gravitee.am.service.exception.TechnicalManagementException; import io.gravitee.am.service.model.NewUser; import io.gravitee.am.service.model.UpdateUser; +import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Single; @@ -40,7 +38,6 @@ import org.springframework.stereotype.Component; import java.util.*; -import java.util.stream.Collectors; /** * @author David BRASSELY (david.brassely at graviteesource.com) @@ -176,4 +173,9 @@ public Single> statistics(AnalyticsQuery query) { String.format("An error occurs while count users analytics : %s", query), ex)); }); } -} + + public Completable deleteByDomain(String domain) { + LOGGER.debug("Delete all users from domain {}", domain); + return userRepository.deleteByReference(ReferenceType.DOMAIN, domain); + } +} \ No newline at end of file diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java index a48848b988e..1a2cc065858 100644 --- a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java +++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java @@ -573,9 +573,7 @@ public void shouldDelete() { when(role.getId()).thenReturn(ROLE_ID); when(roleService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.singleton(role))); when(roleService.delete(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), anyString())).thenReturn(Completable.complete()); - when(user.getId()).thenReturn(USER_ID); - when(userService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.just(user)); - when(userService.delete(anyString())).thenReturn(Completable.complete()); + when(userService.deleteByDomain(any())).thenReturn(Completable.complete()); when(scope.getId()).thenReturn(SCOPE_ID); when(scopeService.findByDomain(DOMAIN_ID, 0, Integer.MAX_VALUE)).thenReturn(Single.just(new Page<>(Collections.singleton(scope),0,1))); when(scopeService.delete(SCOPE_ID, true)).thenReturn(Completable.complete()); @@ -619,7 +617,7 @@ public void shouldDelete() { verify(identityProviderService, times(1)).delete(DOMAIN_ID, IDP_ID); verify(extensionGrantService, times(1)).delete(DOMAIN_ID, EXTENSION_GRANT_ID); verify(roleService, times(1)).delete(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), eq(ROLE_ID)); - verify(userService, times(1)).delete(USER_ID); + verify(userService, times(1)).deleteByDomain(DOMAIN_ID); verify(scopeService, times(1)).delete(SCOPE_ID, true); verify(groupService, times(1)).delete(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), eq(GROUP_ID)); verify(formService, times(1)).delete(eq(DOMAIN_ID), eq(FORM_ID)); @@ -640,7 +638,7 @@ public void shouldDeleteWithoutRelatedData() { when(identityProviderService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.empty()); when(extensionGrantService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.empty()); when(roleService.findByDomain(DOMAIN_ID)).thenReturn(Single.just(Collections.emptySet())); - when(userService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.empty()); + when(userService.deleteByDomain(DOMAIN_ID)).thenReturn(Completable.complete()); when(scopeService.findByDomain(DOMAIN_ID, 0, Integer.MAX_VALUE)).thenReturn(Single.just(new Page<>(Collections.emptySet(),0,1))); when(groupService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.empty()); when(formService.findByDomain(DOMAIN_ID)).thenReturn(Flowable.empty()); @@ -665,7 +663,7 @@ public void shouldDeleteWithoutRelatedData() { verify(identityProviderService, never()).delete(anyString(), anyString()); verify(extensionGrantService, never()).delete(anyString(), anyString()); verify(roleService, never()).delete(eq(ReferenceType.DOMAIN), eq(DOMAIN_ID), anyString()); - verify(userService, never()).delete(anyString()); + verify(userService).deleteByDomain(anyString()); verify(scopeService, never()).delete(anyString(), anyBoolean()); verify(formService, never()).delete(anyString(), anyString()); verify(emailTemplateService, never()).delete(anyString());