Skip to content

Commit e5afe8f

Browse files
aq-ikhwa-techludomikula
authored andcommitted
Add handling for audit logs feature
1 parent 8d0c483 commit e5afe8f

File tree

10 files changed

+324
-61
lines changed

10 files changed

+324
-61
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.lowcoder.infra.event;
2+
3+
import lombok.Getter;
4+
import lombok.experimental.SuperBuilder;
5+
import org.springframework.util.MultiValueMap;
6+
7+
@Getter
8+
@SuperBuilder
9+
public class APICallEvent extends AbstractEvent {
10+
11+
private final EventType type;
12+
private final String httpMethod;
13+
private final String requestUri;
14+
private final MultiValueMap<String, String> headers;
15+
private final MultiValueMap<String, String> queryParams;
16+
17+
@Override
18+
public EventType getEventType() {
19+
return EventType.API_CALL_EVENT;
20+
}
21+
}

server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/event/AbstractEvent.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.lowcoder.infra.event;
22

3+
import java.lang.reflect.Field;
34
import java.util.HashMap;
45
import java.util.Map;
56

@@ -14,9 +15,11 @@ public abstract class AbstractEvent implements LowcoderEvent
1415
{
1516
protected final String orgId;
1617
protected final String userId;
17-
protected Map<String, String> details;
18+
protected final String sessionHash;
19+
protected final Boolean isAnonymous;
20+
protected Map<String, Object> details;
1821

19-
public Map<String, String> details()
22+
public Map<String, Object> details()
2023
{
2124
return this.details;
2225
}
@@ -33,4 +36,20 @@ public B detail(String name, String value)
3336
return self();
3437
}
3538
}
39+
40+
public void populateDetails() {
41+
if (details == null) {
42+
details = new HashMap<>();
43+
}
44+
for(Field f : getClass().getDeclaredFields()){
45+
Object value = null;
46+
try {
47+
f.setAccessible(Boolean.TRUE);
48+
value = f.get(this);
49+
details.put(f.getName(), value);
50+
} catch (Exception e) {
51+
}
52+
53+
}
54+
}
3655
}

server/api-service/lowcoder-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@
215215
<version>0.11.5</version>
216216
<scope>runtime</scope>
217217
</dependency>
218+
<dependency>
219+
<groupId>org.springframework</groupId>
220+
<artifactId>spring-aspects</artifactId>
221+
</dependency>
218222

219223
</dependencies>
220224

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.lowcoder.api.framework.filter;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.http.server.reactive.ServerHttpRequest;
5+
import org.springframework.web.server.ServerWebExchange;
6+
import org.springframework.web.server.WebFilter;
7+
import org.springframework.web.server.WebFilterChain;
8+
import reactor.core.publisher.Mono;
9+
10+
@Configuration
11+
public class ReactiveRequestContextFilter implements WebFilter {
12+
@Override
13+
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
14+
ServerHttpRequest request = exchange.getRequest();
15+
return chain.filter(exchange)
16+
.contextWrite(ctx -> ctx.put(ReactiveRequestContextHolder.SERVER_HTTP_REQUEST, request));
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.lowcoder.api.framework.filter;
2+
3+
import org.springframework.http.server.reactive.ServerHttpRequest;
4+
import reactor.core.publisher.Mono;
5+
6+
public class ReactiveRequestContextHolder {
7+
public static final Class<ServerHttpRequest> SERVER_HTTP_REQUEST = ServerHttpRequest.class;
8+
9+
public static Mono<ServerHttpRequest> getRequest() {
10+
return Mono.subscriberContext()
11+
.map(ctx -> ctx.get(SERVER_HTTP_REQUEST));
12+
}
13+
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/data/PluginServerRequest.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package org.lowcoder.api.framework.plugin.data;
22

3+
import org.lowcoder.plugin.api.PluginEndpoint;
4+
import org.lowcoder.plugin.api.PluginEndpoint.Method;
5+
import org.lowcoder.plugin.api.data.EndpointRequest;
6+
import org.springframework.http.HttpCookie;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.http.HttpMethod;
9+
import org.springframework.web.reactive.function.server.ServerRequest;
10+
311
import java.net.URI;
412
import java.security.Principal;
513
import java.util.AbstractMap.SimpleEntry;
@@ -10,14 +18,6 @@
1018
import java.util.Map.Entry;
1119
import java.util.concurrent.CompletableFuture;
1220

13-
import org.lowcoder.plugin.api.PluginEndpoint;
14-
import org.lowcoder.plugin.api.PluginEndpoint.Method;
15-
import org.lowcoder.plugin.api.data.EndpointRequest;
16-
import org.springframework.http.HttpCookie;
17-
import org.springframework.http.HttpHeaders;
18-
import org.springframework.http.HttpMethod;
19-
import org.springframework.web.reactive.function.server.ServerRequest;
20-
2121
public class PluginServerRequest implements EndpointRequest
2222
{
2323
private URI uri;
@@ -27,6 +27,8 @@ public class PluginServerRequest implements EndpointRequest
2727
private Map<String, List<Map.Entry<String, String>>> cookies;
2828
private Map<String, Object> attributes;
2929
private Map<String, String> pathVariables;
30+
31+
private Map<String, List<String>> queryParams;
3032
private CompletableFuture<? extends Principal> principal;
3133

3234

@@ -36,6 +38,7 @@ public PluginServerRequest()
3638
cookies = new HashMap<>();
3739
attributes = new HashMap<>();
3840
pathVariables = new HashMap<>();
41+
queryParams = new HashMap<>();
3942
}
4043

4144
public static PluginServerRequest fromServerRequest(ServerRequest request)
@@ -74,6 +77,14 @@ public static PluginServerRequest fromServerRequest(ServerRequest request)
7477
psr.pathVariables.put(entry.getKey(), entry.getValue());
7578
});
7679
}
80+
81+
if (request.queryParams() != null)
82+
{
83+
request.queryParams().entrySet()
84+
.forEach(entry -> {
85+
psr.queryParams.put(entry.getKey(), entry.getValue());
86+
});
87+
}
7788

7889
psr.principal = request.principal().toFuture();
7990

@@ -125,6 +136,11 @@ public Map<String, Object> attributes() {
125136
public Map<String, String> pathVariables() {
126137
return pathVariables;
127138
}
139+
140+
@Override
141+
public Map<String, List<String>> queryParams() {
142+
return queryParams;
143+
}
128144
@Override
129145
public CompletableFuture<? extends Principal> principal() {
130146
return principal;

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public interface SessionUserService {
1818
@NonEmptyMono
1919
Mono<OrgMember> getVisitorOrgMemberCache();
2020

21+
Mono<OrgMember> getVisitorOrgMemberCacheSilent();
22+
2123
Mono<OrgMember> getVisitorOrgMember();
2224

2325
Mono<Boolean> isAnonymousUser();
@@ -33,4 +35,6 @@ public interface SessionUserService {
3335
Mono<User> resolveSessionUserForJWT(Claims claims, String token);
3436

3537
Mono<Boolean> tokenExist(String token);
38+
39+
Mono<String> getVisitorToken();
3640
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.api.home;
22

33
import static org.lowcoder.sdk.constants.GlobalContext.CURRENT_ORG_MEMBER;
4+
import static org.lowcoder.sdk.constants.GlobalContext.VISITOR_TOKEN;
45
import static org.lowcoder.sdk.exception.BizError.UNABLE_TO_FIND_VALID_ORG;
56
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
67
import static org.lowcoder.sdk.util.JsonUtils.fromJsonQuietly;
@@ -74,6 +75,17 @@ public Mono<OrgMember> getVisitorOrgMemberCache() {
7475
.switchIfEmpty(deferredError(UNABLE_TO_FIND_VALID_ORG, "UNABLE_TO_FIND_VALID_ORG"));
7576
}
7677

78+
@Override
79+
public Mono<OrgMember> getVisitorOrgMemberCacheSilent() {
80+
return Mono.deferContextual(contextView -> (Mono<OrgMember>) contextView.get(CURRENT_ORG_MEMBER))
81+
.delayUntil(Mono::just);
82+
}
83+
84+
@Override
85+
public Mono<String> getVisitorToken() {
86+
return Mono.deferContextual(contextView -> Mono.just(contextView.get(VISITOR_TOKEN)));
87+
}
88+
7789
@Override
7890
public Mono<OrgMember> getVisitorOrgMember() {
7991
return getVisitorId()
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.lowcoder.api.util;
2+
3+
import com.google.common.hash.Hashing;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.aspectj.lang.ProceedingJoinPoint;
6+
import org.aspectj.lang.annotation.Around;
7+
import org.aspectj.lang.annotation.Aspect;
8+
import org.aspectj.lang.annotation.Pointcut;
9+
import org.lowcoder.api.framework.filter.ReactiveRequestContextHolder;
10+
import org.lowcoder.api.home.SessionUserService;
11+
import org.lowcoder.domain.organization.model.OrgMember;
12+
import org.lowcoder.infra.event.APICallEvent;
13+
import org.lowcoder.plugin.api.event.LowcoderEvent.EventType;
14+
import org.lowcoder.sdk.constants.Authentication;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.context.ApplicationEventPublisher;
17+
import org.springframework.http.server.reactive.ServerHttpRequest;
18+
import org.springframework.stereotype.Component;
19+
import org.springframework.util.MultiValueMap;
20+
import reactor.core.publisher.Mono;
21+
22+
import java.nio.charset.StandardCharsets;
23+
24+
import static org.springframework.http.HttpHeaders.writableHttpHeaders;
25+
26+
@Slf4j
27+
@Aspect
28+
@Component
29+
public class ApiCallEventPublisher {
30+
31+
@Autowired
32+
private ApplicationEventPublisher applicationEventPublisher;
33+
@Autowired
34+
private SessionUserService sessionUserService;
35+
36+
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
37+
public void getMapping(){}
38+
39+
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
40+
public void postMapping(){}
41+
42+
@Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
43+
public void putMapping(){}
44+
45+
@Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
46+
public void deleteMapping(){}
47+
48+
@Pointcut("@annotation(org.springframework.web.bind.annotation.PatchMapping)")
49+
public void patchMapping(){}
50+
51+
@Around("(getMapping() || postMapping() || putMapping() || deleteMapping() || patchMapping())")
52+
public Object handleAPICallEvent(ProceedingJoinPoint joinPoint) throws Throwable {
53+
54+
return sessionUserService.getVisitorToken()
55+
.zipWith(sessionUserService.getVisitorOrgMemberCacheSilent().defaultIfEmpty(OrgMember.NOT_EXIST))
56+
.zipWith(ReactiveRequestContextHolder.getRequest())
57+
.doOnNext(
58+
tuple -> {
59+
String token = tuple.getT1().getT1();
60+
OrgMember orgMember = tuple.getT1().getT2();
61+
ServerHttpRequest request = tuple.getT2();
62+
if (orgMember == OrgMember.NOT_EXIST) {
63+
return;
64+
}
65+
MultiValueMap<String, String> headers = writableHttpHeaders(request.getHeaders());
66+
headers.remove("Cookie");
67+
headers.remove("X-Real-IP");
68+
APICallEvent event = APICallEvent.builder()
69+
.userId(orgMember.getUserId())
70+
.orgId(orgMember.getOrgId())
71+
.type(EventType.API_CALL_EVENT)
72+
.isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId()))
73+
.sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString())
74+
.httpMethod(request.getMethod().name())
75+
.requestUri(request.getURI().getPath())
76+
.headers(headers)
77+
.queryParams(request.getQueryParams())
78+
.build();
79+
event.populateDetails();
80+
applicationEventPublisher.publishEvent(event);
81+
})
82+
.onErrorResume(throwable -> {
83+
log.error("handleAPICallEvent error {} for: {} ", joinPoint.getSignature().getName(), EventType.API_CALL_EVENT, throwable);
84+
return Mono.empty();
85+
})
86+
.then((Mono) joinPoint.proceed());
87+
}
88+
89+
}

0 commit comments

Comments
 (0)