Skip to content

Commit 41239e7

Browse files
authored
Refactor core audit log (#453)
* Refactor handleWithAudit in AuthMiddleware * Make auditlog fields case consistent * Move logic to populate auditlog from operator key * Fix audit field names and update unit tests
1 parent cd714fb commit 41239e7

6 files changed

Lines changed: 76 additions & 33 deletions

File tree

src/main/java/com/uid2/shared/audit/Audit.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private String defaultIfNull(String s) {
174174
return s != null ? s : "unknown";
175175
}
176176

177-
public static final String USER_DETAILS = "userDetails";
177+
public static final String USER_DETAILS = "user_details";
178178

179179
public void log(RoutingContext ctx, AuditParams params) {
180180
Objects.requireNonNull(ctx, "RoutingContext must not be null");
@@ -190,8 +190,8 @@ public void log(RoutingContext ctx, AuditParams params) {
190190
HttpServerRequest request = ctx.request();
191191
HttpServerResponse response = ctx.response();
192192

193-
userDetails.put("User-Agent", defaultIfNull(request.getHeader("User-Agent")));
194-
userDetails.put("IP", defaultIfNull(request.remoteAddress() != null ? request.remoteAddress().host() : null));
193+
userDetails.put("user_agent", defaultIfNull(request.getHeader("User-Agent")));
194+
userDetails.put("ip", defaultIfNull(request.remoteAddress() != null ? request.remoteAddress().host() : null));
195195

196196
int status = response != null ? response.getStatusCode() : -1;
197197
String method = request.method() != null ? request.method().name() : "UNKNOWN";

src/main/java/com/uid2/shared/middleware/AttestationMiddleware.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,6 @@ public void handle(RoutingContext rc) {
7373

7474
final IAuthorizable profile = AuthMiddleware.getAuthClient(rc);
7575
if (profile instanceof OperatorKey operatorKey) {
76-
JsonObject auditLogUserDetails = new JsonObject();
77-
auditLogUserDetails.put("operatorKeyName", operatorKey.getName());
78-
auditLogUserDetails.put("operatorKeyContact", operatorKey.getContact());
79-
auditLogUserDetails.put("operatorKeySiteId", operatorKey.getSiteId());
8076
final String protocol = operatorKey.getProtocol();
8177
final String userToken = AuthMiddleware.getAuthToken(rc);
8278
final String jwt = getAttestationJWT(rc);
@@ -99,6 +95,7 @@ public void handle(RoutingContext rc) {
9995
isJwtValid = false;
10096
LOGGER.info("JWT missing required role. Required roles: {}, JWT Presented Roles: {}, SiteId: {}, Name: {}, Contact: {}", this.roleBasedJwtClaimValidator.getRequiredRoles(), response.getRoles(), operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact());
10197
}
98+
JsonObject auditLogUserDetails = rc.get(Audit.USER_DETAILS, new JsonObject());
10299
if (CollectionUtils.isNotEmpty(response.getRoles())) {
103100
auditLogUserDetails.put("jwt_roles", new ArrayList<>(response.getRoles()));
104101
}
@@ -109,6 +106,7 @@ public void handle(RoutingContext rc) {
109106
isJwtValid = false;
110107
LOGGER.info("JWT failed validation of Subject. JWT Presented Roles: {}, SiteId: {}, Name: {}, Contact: {}, JWT Subject: {}, Operator Subject: {}", response.getRoles(), operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact(), response.getSubject(), subject);
111108
}
109+
rc.put(Audit.USER_DETAILS, auditLogUserDetails);
112110
}
113111
} catch (JwtService.ValidationException e) {
114112
LOGGER.info("Error validating JWT. Attestation validation failed. SiteId: {}, Name: {}, Contact: {}. Error: {}", operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact(), e.toString());
@@ -119,7 +117,6 @@ public void handle(RoutingContext rc) {
119117
}
120118
}
121119
}
122-
rc.put(Audit.USER_DETAILS, auditLogUserDetails);
123120
}
124121

125122
if (success && !isJwtValid && this.enforceJwt) {

src/main/java/com/uid2/shared/middleware/AuthMiddleware.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.vertx.core.http.HttpHeaders;
99
import io.vertx.core.json.JsonObject;
1010
import io.vertx.ext.web.RoutingContext;
11+
import org.apache.commons.collections4.CollectionUtils;
1112

1213
import java.util.*;
1314

@@ -56,6 +57,13 @@ public static void setAuthClient(RoutingContext rc, IAuthorizable profile) {
5657
rc.data().put(API_CLIENT_PROP, profile);
5758
if (profile != null) {
5859
rc.data().put(API_CONTACT_PROP, profile.getContact());
60+
if (profile instanceof OperatorKey operatorKey) {
61+
JsonObject auditLogUserDetails = new JsonObject();
62+
auditLogUserDetails.put("operator_key_name", operatorKey.getName());
63+
auditLogUserDetails.put("operator_key_contact", operatorKey.getContact());
64+
auditLogUserDetails.put("operator_key_site_id", operatorKey.getSiteId());
65+
rc.put(Audit.USER_DETAILS, auditLogUserDetails);
66+
}
5967
}
6068
}
6169

@@ -76,14 +84,21 @@ private Handler<RoutingContext> logAndHandle(Handler<RoutingContext> handler, Au
7684
}
7785

7886
public <E> Handler<RoutingContext> handle(Handler<RoutingContext> handler, E... roles) {
79-
return this.handleWithAudit(handler, null, false, roles);
87+
if (roles == null || roles.length == 0) {
88+
throw new IllegalArgumentException("must specify at least one role");
89+
}
90+
return this.handleWithAudit(handler, null, false, Arrays.asList(roles));
8091
}
8192

82-
public <E> Handler<RoutingContext> handleWithAudit(Handler<RoutingContext> handler, AuditParams params, boolean enableAuditLog, E... roles) {
83-
if (roles == null || roles.length == 0) {
93+
public final <E> Handler<RoutingContext> handleWithAudit(Handler<RoutingContext> handler, List<E> roles) {
94+
return this.handleWithAudit(handler, new AuditParams(), true, roles);
95+
}
96+
97+
public final <E> Handler<RoutingContext> handleWithAudit(Handler<RoutingContext> handler, AuditParams params, boolean enableAuditLog, List<E> roles) {
98+
if (CollectionUtils.isEmpty(roles)) {
8499
throw new IllegalArgumentException("must specify at least one role");
85100
}
86-
final RoleBasedAuthorizationProvider<E> authorizationProvider = new RoleBasedAuthorizationProvider<>(Collections.unmodifiableSet(new HashSet<E>(Arrays.asList(roles))));
101+
final RoleBasedAuthorizationProvider<E> authorizationProvider = new RoleBasedAuthorizationProvider<>(Collections.unmodifiableSet(new HashSet<E>(roles)));
87102
AuthHandler h;
88103
if (enableAuditLog) {
89104
final Handler<RoutingContext> loggedHandler = logAndHandle(handler, params);

src/test/java/com/uid2/shared/audit/AuditTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ public void testAudit() throws JsonProcessingException {
7474

7575
JsonNode actor = jsonNode.get("actor");
7676
assertThat(actor).isNotNull();
77-
assertThat(actor.get("User-Agent").asText()).isEqualTo("JUnit-Test-Agent");
78-
assertThat(actor.get("IP").asText()).isEqualTo("127.0.0.1");
77+
assertThat(actor.get("user_agent").asText()).isEqualTo("JUnit-Test-Agent");
78+
assertThat(actor.get("ip").asText()).isEqualTo("127.0.0.1");
7979

8080
}
8181

src/test/java/com/uid2/shared/middleware/AttestationMiddlewareTest.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,14 @@ void setUp() {
6464

6565
this.data.put(AuthMiddleware.API_CLIENT_PROP, this.operatorKey);
6666
when(this.routingContext.data()).thenReturn(data);
67+
when(this.routingContext.get(anyString(), any(JsonObject.class))).thenReturn(new JsonObject());
6768
}
6869

69-
private JsonObject verifyAuditLogFilled() {
70+
private void verifyAuditLogFilledWithJwt(JwtValidationResponse response) {
7071
ArgumentCaptor<String> keyArgumentCaptor = ArgumentCaptor.forClass(String.class);
7172
ArgumentCaptor<JsonObject> jsonObjectArgumentCaptor = ArgumentCaptor.forClass(JsonObject.class);
7273
verify(routingContext).put(keyArgumentCaptor.capture(), jsonObjectArgumentCaptor.capture());
7374
JsonObject auditLogUserDetailsActual = jsonObjectArgumentCaptor.getValue();
74-
Assertions.assertEquals("userDetails", keyArgumentCaptor.getValue());
75-
Assertions.assertEquals(operatorKey.getName(), auditLogUserDetailsActual.getString("operatorKeyName"));
76-
Assertions.assertEquals(operatorKey.getContact(), auditLogUserDetailsActual.getString("operatorKeyContact"));
77-
Assertions.assertEquals(operatorKey.getSiteId().toString(), auditLogUserDetailsActual.getString("operatorKeySiteId"));
78-
return auditLogUserDetailsActual;
79-
}
80-
81-
private void verifyAuditLogFilledWithJwt(JwtValidationResponse response) {
82-
JsonObject auditLogUserDetailsActual = verifyAuditLogFilled();
8375
JsonArray expectedJwtRoles = null;
8476
if (CollectionUtils.isNotEmpty(response.getRoles())) {
8577
expectedJwtRoles = JsonArray.of(response.getRoles().toArray());
@@ -161,7 +153,6 @@ void trustedNoJwtAndJwtNotEnforcedReturnsSuccess(String jwt) {
161153

162154
verify(nextHandler).handle(routingContext);
163155
verifyNoInteractions(this.jwtService);
164-
verifyAuditLogFilled();
165156
}
166157

167158
@ParameterizedTest
@@ -176,7 +167,6 @@ void trustedNoJwtAndJwtEnforcedReturnsSuccess(String jwt) {
176167

177168
verifyNoInteractions(nextHandler);
178169
verify(routingContext).fail(401);
179-
verifyAuditLogFilled();
180170
}
181171

182172
@Test
@@ -190,7 +180,6 @@ void trustedInvalidJwtReturns401() throws JwtService.ValidationException {
190180

191181
verifyNoInteractions(nextHandler);
192182
verify(routingContext).fail(401);
193-
verifyAuditLogFilled();
194183
}
195184

196185
@Test
@@ -203,7 +192,6 @@ void trustedJwtValidationThrowsErrorReturns401() throws JwtService.ValidationExc
203192

204193
verifyNoInteractions(nextHandler);
205194
verify(routingContext).fail(401);
206-
verifyAuditLogFilled();
207195
}
208196

209197
@Test
@@ -218,7 +206,6 @@ void notTrustedNoAttestationTokenReturns401() throws JwtService.ValidationExcept
218206

219207
verifyNoInteractions(nextHandler);
220208
verify(routingContext).fail(401);
221-
verifyAuditLogFilled();
222209
}
223210

224211
@Test
@@ -236,7 +223,6 @@ void notTrustedWithAttestationTokenReturns401() throws JwtService.ValidationExce
236223

237224
verifyNoInteractions(nextHandler);
238225
verify(routingContext).fail(401);
239-
verifyAuditLogFilled();
240226
}
241227

242228
private AttestationMiddleware getAttestationMiddleware(boolean enforceJwt) {

src/test/java/com/uid2/shared/middleware/AuthMiddlewareTest.java

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package com.uid2.shared.middleware;
22

3-
import com.uid2.shared.audit.AuditParams;
3+
import com.uid2.shared.audit.Audit;
44
import com.uid2.shared.auth.IAuthorizableProvider;
55
import com.uid2.shared.auth.IRoleAuthorizable;
6+
import com.uid2.shared.auth.OperatorKey;
7+
import com.uid2.shared.auth.OperatorType;
68
import com.uid2.shared.auth.Role;
79
import io.vertx.core.Handler;
810
import io.vertx.core.http.HttpServerRequest;
11+
import io.vertx.core.json.JsonObject;
912
import io.vertx.ext.web.RoutingContext;
13+
import org.junit.jupiter.api.Assertions;
1014
import org.junit.jupiter.api.BeforeEach;
1115
import org.junit.jupiter.api.Test;
1216
import org.junit.jupiter.api.extension.ExtendWith;
17+
import org.mockito.ArgumentCaptor;
1318
import org.mockito.ArgumentMatchers;
1419
import org.mockito.Mock;
1520
import org.mockito.junit.jupiter.MockitoExtension;
1621
import org.mockito.junit.jupiter.MockitoSettings;
1722
import org.mockito.quality.Strictness;
1823

24+
import java.util.List;
25+
import java.util.Set;
26+
1927
import static org.mockito.Mockito.*;
2028

2129
@ExtendWith(MockitoExtension.class)
@@ -31,8 +39,10 @@ public class AuthMiddlewareTest {
3139
private Handler<RoutingContext> nextHandler;
3240
@Mock
3341
private IRoleAuthorizable<Role> profile;
42+
private OperatorKey operatorKey = new OperatorKey(null, null, "operator-key", "contact", "trusted", 1000, false, 999, Set.of(Role.OPERATOR), OperatorType.PUBLIC, "test-key-id");
3443
private AuthMiddleware auth;
3544

45+
3646
@BeforeEach
3747
public void setup() {
3848
auth = new AuthMiddleware(authProvider, "app");
@@ -92,11 +102,46 @@ public void authHandlerKeyWithFirstRoleAudit() {
92102
when(request.getHeader("Authorization")).thenReturn("Bearer some-key");
93103
when(authProvider.get("some-key")).thenReturn(profile);
94104
when(profile.hasRole(Role.MAPPER)).thenReturn(true);
95-
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, new AuditParams(), true, Role.MAPPER, Role.ID_READER);
105+
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, List.of(Role.MAPPER, Role.ID_READER));
106+
handler.handle(routingContext);
107+
verify(nextHandler).handle(routingContext);
108+
verify(routingContext, times(0)).fail(any());
109+
verify(routingContext, times(1)).addBodyEndHandler(ArgumentMatchers.<Handler<Void>>any());
110+
}
111+
112+
private void verifyAuditLogFilled() {
113+
ArgumentCaptor<String> keyArgumentCaptor = ArgumentCaptor.forClass(String.class);
114+
ArgumentCaptor<JsonObject> jsonObjectArgumentCaptor = ArgumentCaptor.forClass(JsonObject.class);
115+
verify(routingContext).put(keyArgumentCaptor.capture(), jsonObjectArgumentCaptor.capture());
116+
JsonObject auditLogUserDetailsActual = jsonObjectArgumentCaptor.getValue();
117+
Assertions.assertEquals(Audit.USER_DETAILS, keyArgumentCaptor.getValue());
118+
Assertions.assertEquals(operatorKey.getName(), auditLogUserDetailsActual.getString("operator_key_name"));
119+
Assertions.assertEquals(operatorKey.getContact(), auditLogUserDetailsActual.getString("operator_key_contact"));
120+
Assertions.assertEquals(operatorKey.getSiteId().toString(), auditLogUserDetailsActual.getString("operator_key_site_id"));
121+
}
122+
123+
@Test
124+
public void authHandlerOperatorKeyWithFirstRoleAudit() {
125+
when(request.getHeader("Authorization")).thenReturn("Bearer operator-key");
126+
when(authProvider.get(operatorKey.getName())).thenReturn(operatorKey);
127+
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, List.of(Role.OPERATOR));
96128
handler.handle(routingContext);
97129
verify(nextHandler).handle(routingContext);
98130
verify(routingContext, times(0)).fail(any());
99131
verify(routingContext, times(1)).addBodyEndHandler(ArgumentMatchers.<Handler<Void>>any());
132+
verifyAuditLogFilled();
133+
}
134+
135+
@Test
136+
public void authHandlerOperatorKeyWithUnauthorizedRoleAudit() {
137+
when(request.getHeader("Authorization")).thenReturn("Bearer operator-key");
138+
when(authProvider.get(operatorKey.getName())).thenReturn(operatorKey);
139+
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, List.of(Role.OPTOUT));
140+
handler.handle(routingContext);
141+
verifyNoInteractions(nextHandler);
142+
verify(routingContext).fail(401);
143+
verify(routingContext, times(0)).addBodyEndHandler(ArgumentMatchers.<Handler<Void>>any());
144+
verifyAuditLogFilled();
100145
}
101146

102147
@Test public void authHandlerKeyWithSecondRole() {
@@ -115,7 +160,7 @@ public void authHandlerKeyWithSecondRoleAudit() {
115160
when(request.getHeader("Authorization")).thenReturn("Bearer some-key");
116161
when(authProvider.get("some-key")).thenReturn(profile);
117162
when(profile.hasRole(Role.ID_READER)).thenReturn(true);
118-
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, new AuditParams(), true, Role.MAPPER, Role.ID_READER);
163+
Handler<RoutingContext> handler = auth.handleWithAudit(nextHandler, List.of(Role.MAPPER, Role.ID_READER));
119164
handler.handle(routingContext);
120165
verify(nextHandler).handle(routingContext);
121166
verify(routingContext, times(0)).fail(any());

0 commit comments

Comments
 (0)