diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java index 20eedb35853..6ba2c89ff52 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java @@ -530,6 +530,18 @@ public final class Entity { return entity; } + public static T findEntityByNameOrNull(String entityType, String fqn, Include include) { + return findByNameOrNull(entityType, fqn, include); + } + + /** Retrieve the entity using id from given entity reference and fields */ + public static T findByNameOrNull(String entityType, String fqn, Include include) { + EntityRepository entityRepository = Entity.getEntityRepository(entityType); + @SuppressWarnings("unchecked") + T entity = (T) entityRepository.findByNameOrNull(fqn, include); + return entity; + } + public static T getEntityByName( String entityType, String fqn, String fields, Include include) { return getEntityByName(entityType, fqn, fields, include, true); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java index 8386cf91c2f..3c89aa68e5c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java @@ -48,7 +48,7 @@ import static org.openmetadata.service.Entity.FIELD_TAGS; import static org.openmetadata.service.Entity.FIELD_VOTES; import static org.openmetadata.service.Entity.TEAM; import static org.openmetadata.service.Entity.USER; -import static org.openmetadata.service.Entity.getEntityByName; +import static org.openmetadata.service.Entity.findEntityByNameOrNull; import static org.openmetadata.service.Entity.getEntityFields; import static org.openmetadata.service.Entity.getEntityReferenceById; import static org.openmetadata.service.exception.CatalogExceptionMessage.csvNotSupported; @@ -3660,10 +3660,16 @@ public abstract class EntityRepository { this.original = original; this.updated = updated; this.operation = operation; - this.updatingUser = + User updatingUser = updated.getUpdatedBy().equalsIgnoreCase(ADMIN_USER_NAME) ? new User().withName(ADMIN_USER_NAME).withIsAdmin(true) - : getEntityByName(USER, updated.getUpdatedBy(), "", NON_DELETED); + : findEntityByNameOrNull(USER, updated.getUpdatedBy(), ALL); + if (updatingUser == null) { + // user not found, create a new user with name + // This is to handle the case where the user is not found in the system. maybe deleted + updatingUser = new User().withName(updated.getUpdatedBy()).withIsAdmin(false); + } + this.updatingUser = updatingUser; this.changeSource = changeSource; this.useOptimisticLocking = useOptimisticLocking; } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java index ba1462ad58f..c6f8af51654 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/teams/UserResourceTest.java @@ -2552,4 +2552,65 @@ public class UserResourceTest extends EntityResourceTest { // Verify user no longer exists in RDF after hard delete RdfTestUtils.verifyEntityNotInRdf(user.getFullyQualifiedName()); } + + @Test + void test_loginWithDeletedUpdatedByUser_200_ok(TestInfo test) throws HttpResponseException { + // Create an admin user to update another user + String username = "tempAdmin"; + Map TEMP_ADMIN_AUTH_HEADERS = authHeaders(username + "@open-metadata.org"); + User adminUser = + createEntity(createRequest("tempAdmin").withIsAdmin(true), TEMP_ADMIN_AUTH_HEADERS); + + // Create a target user that will be updated by the admin + User targetUser = + createEntity( + createRequest(test) + .withName("targetUser") + .withDisplayName("Target User") + .withEmail("targetuser@email.com") + .withIsBot(false) + .withCreatePasswordType(CreateUser.CreatePasswordType.ADMIN_CREATE) + .withPassword("Test@1234") + .withConfirmPassword("Test@1234"), + TEMP_ADMIN_AUTH_HEADERS); + + assertEquals(adminUser.getName(), targetUser.getUpdatedBy()); + + // Delete the admin user who updated the target user + deleteEntity(adminUser.getId(), ADMIN_AUTH_HEADERS); + + // Verify admin user is deleted + assertResponse( + () -> getEntity(adminUser.getId(), ADMIN_AUTH_HEADERS), + NOT_FOUND, + CatalogExceptionMessage.entityNotFound(Entity.USER, adminUser.getId())); + + // Try to login with the target user - this should not throw an exception + // even though the updatedBy user (adminUser) has been deleted + LoginRequest loginRequest = + new LoginRequest() + .withEmail("targetuser@email.com") + .withPassword(encodePassword("Test@1234")); + + // This login should succeed without throwing an exception + // The bug fix ensures that when updateUserLastLoginTime is called, + // it handles the case where the updatedBy user no longer exists + JwtResponse jwtResponse = + TestUtils.post( + getResource("users/login"), + loginRequest, + JwtResponse.class, + OK.getStatusCode(), + ADMIN_AUTH_HEADERS); + + assertNotNull(jwtResponse); + assertNotNull(jwtResponse.getAccessToken()); + + // Verify the target user still has the deleted admin user name in updatedBy + User loggedInUser = getEntity(targetUser.getId(), ADMIN_AUTH_HEADERS); + assertEquals(adminUser.getName(), loggedInUser.getUpdatedBy()); + + // Clean up + deleteEntity(targetUser.getId(), ADMIN_AUTH_HEADERS); + } }