fix: resolve actor name from Logto for audit log entries
AuditService now looks up username/name/email from Logto Management API when actorEmail is null, with an in-memory cache to avoid repeated calls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package net.siegeln.cameleer.saas.audit;
|
||||
|
||||
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -7,20 +10,29 @@ import org.springframework.stereotype.Service;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class AuditService {
|
||||
|
||||
private final AuditRepository auditRepository;
|
||||
private static final Logger log = LoggerFactory.getLogger(AuditService.class);
|
||||
|
||||
public AuditService(AuditRepository auditRepository) {
|
||||
private final AuditRepository auditRepository;
|
||||
private final LogtoManagementClient logtoClient;
|
||||
private final ConcurrentHashMap<String, String> userNameCache = new ConcurrentHashMap<>();
|
||||
|
||||
public AuditService(AuditRepository auditRepository, LogtoManagementClient logtoClient) {
|
||||
this.auditRepository = auditRepository;
|
||||
this.logtoClient = logtoClient;
|
||||
}
|
||||
|
||||
public void log(UUID actorId, String actorEmail, UUID tenantId,
|
||||
AuditAction action, String resource,
|
||||
String environment, String sourceIp,
|
||||
String result, Map<String, Object> metadata) {
|
||||
if (actorEmail == null && actorId != null) {
|
||||
actorEmail = resolveActorName(actorId.toString());
|
||||
}
|
||||
var entry = new AuditEntity();
|
||||
entry.setActorId(actorId);
|
||||
entry.setActorEmail(actorEmail);
|
||||
@@ -39,4 +51,23 @@ public class AuditService {
|
||||
Pageable pageable) {
|
||||
return auditRepository.findFiltered(tenantId, action, result, from, to, search, pageable);
|
||||
}
|
||||
|
||||
private String resolveActorName(String userId) {
|
||||
return userNameCache.computeIfAbsent(userId, id -> {
|
||||
try {
|
||||
var user = logtoClient.getUser(id);
|
||||
if (user == null) return id;
|
||||
var username = user.get("username");
|
||||
if (username != null && !username.toString().isBlank()) return username.toString();
|
||||
var name = user.get("name");
|
||||
if (name != null && !name.toString().isBlank()) return name.toString();
|
||||
var email = user.get("primaryEmail");
|
||||
if (email != null && !email.toString().isBlank()) return email.toString();
|
||||
return id;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to resolve actor name for {}: {}", id, e.getMessage());
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +398,22 @@ public class LogtoManagementClient {
|
||||
.toBodilessEntity();
|
||||
}
|
||||
|
||||
/** Get a user by ID. Returns username, primaryEmail, name. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> getUser(String userId) {
|
||||
if (!isAvailable() || userId == null) return null;
|
||||
try {
|
||||
return (Map<String, Object>) restClient.get()
|
||||
.uri(config.getLogtoEndpoint() + "/api/users/" + userId)
|
||||
.header("Authorization", "Bearer " + getAccessToken())
|
||||
.retrieve()
|
||||
.body(Map.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get user {}: {}", userId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String MGMT_API_RESOURCE = "https://default.logto.app/api";
|
||||
|
||||
private synchronized String getAccessToken() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.siegeln.cameleer.saas.audit;
|
||||
|
||||
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -20,11 +21,14 @@ class AuditServiceTest {
|
||||
@Mock
|
||||
private AuditRepository auditRepository;
|
||||
|
||||
@Mock
|
||||
private LogtoManagementClient logtoClient;
|
||||
|
||||
private AuditService auditService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
auditService = new AuditService(auditRepository);
|
||||
auditService = new AuditService(auditRepository, logtoClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user