Repository: gravitee-io/graviteeio-access-management
Pull request #954 (2020-11-06 13:21:34)

fix(users): delete user's webauthn credentials
fixes gravitee-io/issues#4574


Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/endpoint/webauthn/WebAuthnRegisterEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/endpoint/webauthn/WebAuthnRegisterEndpoint.java
index 6ba0660ee5..6858d87b5a 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/endpoint/webauthn/WebAuthnRegisterEndpoint.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-core/src/main/java/io/gravitee/am/gateway/handler/root/resources/endpoint/webauthn/WebAuthnRegisterEndpoint.java
@@ -107,6 +107,7 @@ public class WebAuthnRegisterEndpoint extends WebAuthnEndpoint {
                 routingContext.session().put(WEBAUTHN_SKIPPED_KEY, true);
                 // Now redirect back to the original url
                 doRedirect(routingContext.response(), returnURL);
+                return;
             }
 
             // prepare the context
@@ -129,12 +130,12 @@ public class WebAuthnRegisterEndpoint extends WebAuthnEndpoint {
                     routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
                     routingContext.response().end(res.result());
                 } else {
-                    logger.error("Unable to render WebAuthn login page", res.cause());
+                    logger.error("Unable to render WebAuthn register page", res.cause());
                     routingContext.fail(res.cause());
                 }
             });
         } catch (Exception ex) {
-            logger.error("An error occurs while rendering WebAuthn login page", ex);
+            logger.error("An error has occurred while rendering WebAuthn register page", ex);
             routingContext.fail(503);
         }
     }
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 3146131e6b..8ba31ba5ec 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
@@ -385,7 +385,14 @@ public class UserServiceImpl implements UserService {
                 .flatMapCompletable(user -> {
                     // create event for sync process
                     Event event = new Event(Type.USER, new Payload(user.getId(), user.getReferenceType(), user.getReferenceId(), Action.DELETE));
-                    return userRepository.delete(userId).andThen(eventService.create(event)).toCompletable();
+                    /// delete WebAuthn credentials
+                    return credentialService.findByUserId(user.getReferenceType(), user.getReferenceId(), user.getId())
+                            .flatMapCompletable(credentials -> {
+                                List<Completable> deleteCredentialsCompletable = credentials.stream().map(c -> credentialService.delete(c.getId())).collect(Collectors.toList());
+                                return Completable.concat(deleteCredentialsCompletable);
+                            })
+                            .andThen(userRepository.delete(userId))
+                            .andThen(eventService.create(event).ignoreElement());
                 })
                 .onErrorResumeNext(ex -> {
                     if (ex instanceof AbstractManagementException) {


Test patch:
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/UserServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/UserServiceTest.java
index 6b32638ff9..82971a1f52 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/UserServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/UserServiceTest.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.service;
 
+import io.gravitee.am.model.Credential;
 import io.gravitee.am.model.User;
 import io.gravitee.am.model.common.Page;
 import io.gravitee.am.model.common.event.Event;
@@ -58,6 +59,9 @@ public class UserServiceTest {
     @Mock
     private EventService eventService;
 
+    @Mock
+    private CredentialService credentialService;
+
     private final static String DOMAIN = "domain1";
 
     @Test
@@ -344,12 +348,41 @@ public class UserServiceTest {
     @Test
     public void shouldDelete() {
         User user = new User();
+        user.setId("my-user");
+        user.setReferenceType(ReferenceType.DOMAIN);
+        user.setReferenceId(DOMAIN);
+
+        when(userRepository.findById("my-user")).thenReturn(Maybe.just(user));
+        when(userRepository.delete("my-user")).thenReturn(Completable.complete());
+        when(eventService.create(any())).thenReturn(Single.just(new Event()));
+        when(credentialService.findByUserId(user.getReferenceType(), user.getReferenceId(), user.getId())).thenReturn(Single.just(Collections.emptyList()));
+
+        TestObserver testObserver = userService.delete("my-user").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+
+        verify(userRepository, times(1)).delete("my-user");
+        verify(eventService, times(1)).create(any());
+        verify(credentialService, never()).delete(anyString());
+    }
+
+    @Test
+    public void shouldDelete_with_webauthn_credentials() {
+        User user = new User();
+        user.setId("my-user");
         user.setReferenceType(ReferenceType.DOMAIN);
         user.setReferenceId(DOMAIN);
 
+        Credential credential = new Credential();
+        credential.setId("credential-id");
+
         when(userRepository.findById("my-user")).thenReturn(Maybe.just(user));
         when(userRepository.delete("my-user")).thenReturn(Completable.complete());
         when(eventService.create(any())).thenReturn(Single.just(new Event()));
+        when(credentialService.findByUserId(user.getReferenceType(), user.getReferenceId(), user.getId())).thenReturn(Single.just(Collections.singletonList(credential)));
+        when(credentialService.delete(credential.getId())).thenReturn(Completable.complete());
 
         TestObserver testObserver = userService.delete("my-user").test();
         testObserver.awaitTerminalEvent();
@@ -359,12 +392,12 @@ public class UserServiceTest {
 
         verify(userRepository, times(1)).delete("my-user");
         verify(eventService, times(1)).create(any());
+        verify(credentialService, times(1)).delete("credential-id");
     }
 
     @Test
     public void shouldDelete_technicalException() {
-        when(userRepository.findById("my-user")).thenReturn(Maybe.just(new User()));
-        when(userRepository.delete("my-user")).thenReturn(Completable.error(TechnicalException::new));
+        when(userRepository.findById("my-user")).thenReturn(Maybe.error(TechnicalException::new));
 
         TestObserver testObserver = new TestObserver();
         userService.delete("my-user").subscribe(testObserver);