Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1c93b63
Added audit logging functionality with `AuditAspect`, `AuditLog`, `Au…
JohanHiths Apr 23, 2026
7f6a559
Enhanced audit logging system: added `AuditAction` annotation for met…
JohanHiths Apr 24, 2026
e51fc7f
Improved user signup auditing: added `AuditAction` annotation to `Sig…
JohanHiths Apr 24, 2026
c258810
update
JohanHiths Apr 24, 2026
523b8d1
Expanded audit logging system: added HTTP method tracking in `AuditLo…
JohanHiths Apr 24, 2026
df24570
Added detailed auditing for file actions: integrated `AuditAction` an…
JohanHiths Apr 24, 2026
03db623
update
JohanHiths Apr 25, 2026
df9f9fc
Refactored audit service usage in tests: added `AuditService` and `Au…
JohanHiths Apr 25, 2026
acdaaaa
removed responsebody
JohanHiths Apr 25, 2026
a7dc04d
Added registry access management: introduced `RegistryAccessEntity`, …
JohanHiths Apr 25, 2026
5659cb6
Added admin registry access management: created `AdminRegistryAccessC…
JohanHiths Apr 26, 2026
0c4bbf5
Update, added exception handler and more functions to admin registry
JohanHiths Apr 26, 2026
5e7b32c
Refactored user and registry-related endpoints: replaced user ID refe…
JohanHiths Apr 26, 2026
8d8161c
update
JohanHiths Apr 26, 2026
f9f9f37
update
JohanHiths Apr 26, 2026
0ed8eeb
Refactored templates and controllers: replaced direct ID usage with `…
JohanHiths Apr 27, 2026
440741f
Replaced user ID references with names in admin templates and control…
JohanHiths Apr 27, 2026
fde6036
Added case management to admin: introduced `CASE_OFFICER` role, imple…
JohanHiths Apr 27, 2026
df567cc
Merge branch 'main' into feature/admin/authorization
JohanHiths Apr 27, 2026
05996a8
update
JohanHiths Apr 27, 2026
1903428
Merge remote-tracking branch 'origin/feature/admin/authorization' int…
JohanHiths Apr 27, 2026
4c07155
update
JohanHiths Apr 27, 2026
011eb82
update
JohanHiths Apr 27, 2026
d666b5e
Refactor database migrations and update entity configuration
JohanHiths Apr 27, 2026
876ad8b
update
JohanHiths Apr 27, 2026
bc9aa1e
update
JohanHiths Apr 27, 2026
c96c0a3
Merge branch 'refs/heads/main' into feature/admin/authorization
JohanHiths Apr 27, 2026
9aa2bdf
update
JohanHiths Apr 27, 2026
5d6ac75
update
JohanHiths Apr 27, 2026
e21cfa2
update
JohanHiths Apr 27, 2026
2510bd4
update flyway
JohanHiths Apr 27, 2026
63e1a3b
updat
JohanHiths Apr 27, 2026
c095d34
Merge branch 'refs/heads/main' into feature/admin/authorization
JohanHiths Apr 28, 2026
fe9556a
Add auditing for key actions across controllers and services
JohanHiths Apr 28, 2026
c2cdda4
Remove responsebody
JohanHiths Apr 30, 2026
5ed223c
Remove responsebody
JohanHiths Apr 30, 2026
8b930e1
Update flyway so it matches
JohanHiths Apr 30, 2026
fd4491a
remove responsebody
JohanHiths May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/backendlab/team4you/Team4youApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.webauthn.api.Bytes;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package backendlab.team4you.application;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.*;

@Entity
@Table(name = "application_entity")
public class ApplicationEntity {


@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Comment thread
coderabbitai[bot] marked this conversation as resolved.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public void delete(Long id) {

}

public void save(ApplicationEntity applicationEntity) {
applicationRepository.save(applicationEntity);
}
public ApplicationEntity save(ApplicationEntity applicationEntity) {
return applicationRepository.save(applicationEntity);
}

public ApplicationEntity findById(Long id) {
return applicationRepository.findById(id).orElse(null);
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/backendlab/team4you/audit/AspectConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package backendlab.team4you.audit;


import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {



}

13 changes: 13 additions & 0 deletions src/main/java/backendlab/team4you/audit/AuditAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package backendlab.team4you.audit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditAction {
String action();
String entity();
}
136 changes: 136 additions & 0 deletions src/main/java/backendlab/team4you/audit/AuditAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package backendlab.team4you.audit;

import backendlab.team4you.caserecord.CaseRecordRequestDto;
import backendlab.team4you.controller.SignupController;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class AuditAspect {

private static final Logger log = LoggerFactory.getLogger(AuditAspect.class);

private final AuditService auditService;

public AuditAspect(AuditService auditService) {
this.auditService = auditService;
}

@Pointcut("within(backendlab.team4you..*)")
public void controllerMethods() {}

@AfterReturning(pointcut = "@annotation(auditAction)", returning = "result")
public void logAuditSuccess(JoinPoint joinPoint, AuditAction auditAction, Object result) {
record(joinPoint, auditAction, "SUCCESS");
}

@AfterThrowing(pointcut = "@annotation(auditAction)", throwing = "ex")
public void logAuditFailure(JoinPoint joinPoint, AuditAction auditAction, Throwable ex) {
record(joinPoint, auditAction, "FAILURE");
}

private void record(JoinPoint joinPoint, AuditAction auditAction, String status) {
try {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = (auth != null) ? auth.getName() : "anonymous";

ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String ip = "unknown";
String endpoint = "unknown";
String httpMethod = "UNKNOWN";

if (attrs != null) {
ip = attrs.getRequest().getRemoteAddr();
endpoint = attrs.getRequest().getRequestURI();
httpMethod = attrs.getRequest().getMethod();

}




StringBuilder detailsBuilder = new StringBuilder();
String methodName = joinPoint.getSignature().toShortString();
String details = "Executed method: " + methodName;
String finalDetails = detailsBuilder.length() > 0
? detailsBuilder.toString()
: details;
int entityId = 0;

Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof Long id) {
entityId = id.intValue();
detailsBuilder.append("| ID: ").append(id).append(" ");
}


else {
if (arg instanceof CaseRecordRequestDto dto) {
detailsBuilder.append("Ärende: ").append(dto.title())
.append(" (Register: ").append(dto.registryId()).append(")");
}

else if (arg instanceof String str && methodName.toLowerCase().contains("update")) {

detailsBuilder.append("Tilldelad till: ").append(str).append(" ");
}
else if (arg instanceof String str && methodName.toLowerCase().contains("delete")) {
detailsBuilder.append("Raderad av: ").append(str).append(" ");
}
else if (arg instanceof String str && methodName.toLowerCase().contains("create")) {
detailsBuilder.append("Skapad av: ").append(str).append(" ");
}
else if (arg instanceof String str && methodName.toLowerCase().contains("assign")) {
detailsBuilder.append("Tilldelad till: ").append(str).append(" ");
}

else if (arg instanceof SignupController.SignupRequest req) {
detailsBuilder.append("Passkey registrerad för användare: ")
.append(req.getUsername())
.append(" (Display: ").append(req.getDisplayName()).append(")");
}

else {
detailsBuilder.append(arg).append(" ");
}

}

}



auditService.saveLog(
username,
finalDetails,
auditAction.action(),
endpoint,
httpMethod,
ip,
status,
auditAction.entity(),
entityId

);
Comment on lines +69 to +126

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fragile arg-scraping heuristic for entityId and details.

entityId = ((Long) arg).intValue() (a) downcasts to int, silently overflowing for IDs > Integer.MAX_VALUE (the AuditLog.entityId column is Long — keep it Long end-to-end), and (b) takes whichever Long argument happens to come last in the parameter list — which for many controllers will be a page size, registry id, or unrelated id. Likewise, "any String not containing /" overwrites the default details with whatever string happens to be passed (filename, role name, etc.), making audit details unreliable.

Consider extracting entityId/details from a SpEL expression on @AuditAction (e.g. entityIdSpel = "#caseId") or from a parameter-level marker annotation, rather than guessing by type.

🔧 Minimal fix to remove the int narrowing
-            String details = "Executed method: " + methodName;
-            int entityId = 0;
+            String details = "Executed method: " + methodName;
+            Long entityId = null;
@@
-                if (arg instanceof Long) {
-                    entityId = ((Long) arg).intValue();
+                if (arg instanceof Long l) {
+                    entityId = l;
                 } else if (arg instanceof String && !((String) arg).contains("/")) {
                     details = "File/Key: " + arg;
                 }
@@
-                    auditAction.entity(),
-                    entityId
+                    auditAction.entity(),
+                    entityId == null ? 0 : entityId.intValue()  // or refactor saveLog to take Long

Better still: change AuditService.saveLog(...)'s entityId parameter to Long so the narrowing is removed everywhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/audit/AuditAspect.java` around lines 60 -
81, The current arg-scraping in AuditAspect (joinPoint.getArgs()) is fragile and
narrows Long to int and picks arbitrary positional args; change entityId to a
Long (not int) and stop guessing by type — instead add SpEL support on the
`@AuditAction` (e.g. entityIdSpel/detailsSpel properties) or a parameter-level
marker annotation, evaluate the expression against the method's args in
AuditAspect (use Spring's ExpressionParser and EvaluationContext) to derive
entityId and details deterministically, update auditService.saveLog signature
and all callers to accept a Long entityId, and remove the brittle "String
without '/'" details-heuristic in AuditAspect (use the evaluated detailsSpel or
annotated parameter value instead).


} catch (Exception e) {
log.warn("Failed to persist audit log for {}: {}",
joinPoint.getSignature().toShortString(), e.getMessage(), e);
}


}

}
117 changes: 117 additions & 0 deletions src/main/java/backendlab/team4you/audit/AuditLog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package backendlab.team4you.audit;


import jakarta.persistence.*;

import java.time.ZonedDateTime;

@Entity
@Table(name = "audit")
public class AuditLog {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;


private String username;
private String email;

String details;

private String action;

private String httpMethod;

private String endpoint;

private String entityType;

private Long entityId;

@Column(name = "ip_address")
private String ipAddress;


private ZonedDateTime timestamp;

private String status;


public AuditLog() {
}

public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public Long getEntityId() {
return entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}

public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public ZonedDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(ZonedDateTime timestamp) {
this.timestamp = timestamp;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public String getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
}
15 changes: 15 additions & 0 deletions src/main/java/backendlab/team4you/audit/AuditLogRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package backendlab.team4you.audit;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.ZonedDateTime;
import java.util.List;

@Repository
public interface AuditLogRepository extends JpaRepository<AuditLog, Long> {

void deleteByTimestampBefore(ZonedDateTime limit);
List<AuditLog> findAllByOrderByTimestampDesc();

}
Loading
Loading