1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.helper;
17
18 import static org.codelibs.core.stream.StreamUtil.stream;
19
20 import java.time.ZonedDateTime;
21 import java.time.format.DateTimeFormatter;
22 import java.util.Comparator;
23 import java.util.LinkedHashMap;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.stream.Collectors;
27
28 import org.apache.commons.text.StringEscapeUtils;
29 import org.apache.logging.log4j.LogManager;
30 import org.apache.logging.log4j.Logger;
31 import org.codelibs.core.lang.StringUtil;
32 import org.codelibs.fess.app.web.base.login.FessCredential;
33 import org.codelibs.fess.mylasta.action.FessUserBean;
34 import org.codelibs.fess.util.ComponentUtil;
35 import org.dbflute.optional.OptionalThing;
36 import org.lastaflute.web.login.credential.LoginCredential;
37 import org.lastaflute.web.util.LaRequestUtil;
38
39 import jakarta.annotation.PostConstruct;
40
41
42
43
44
45
46
47 public class ActivityHelper {
48
49
50
51
52 public ActivityHelper() {
53
54 }
55
56
57
58
59 protected Logger logger = null;
60
61
62
63
64 protected String loggerName = "fess.log.audit";
65
66
67
68
69 protected String permissionSeparator = "|";
70
71
72
73
74 protected boolean useEcsFormat = false;
75
76
77
78
79 protected String ecsVersion = "1.2.0";
80
81
82
83
84 protected String ecsServiceName = "fess";
85
86
87
88
89 protected String ecsEventDataset = "app";
90
91
92
93
94 protected Map<String, String> envMap;
95
96
97
98
99 @PostConstruct
100 public void init() {
101 logger = LogManager.getLogger(loggerName);
102 final String logFormat = ComponentUtil.getFessConfig().getAppAuditLogFormat();
103 if (StringUtil.isBlank(logFormat)) {
104 useEcsFormat = "docker".equals(getEnvMap().get("FESS_APP_TYPE"));
105 } else if ("ecs".equals(logFormat)) {
106 useEcsFormat = true;
107 }
108 }
109
110
111
112
113
114 protected Map<String, String> getEnvMap() {
115 if (envMap != null) {
116 return envMap;
117 }
118 return System.getenv();
119 }
120
121
122
123
124
125 public void setEnvMap(final Map<String, String> envMap) {
126 this.envMap = envMap;
127 }
128
129
130
131
132
133 public void login(final OptionalThing<FessUserBean> user) {
134 final Map<String, String> valueMap = new LinkedHashMap<>();
135 valueMap.put("action", Action.LOGIN.name());
136 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
137 valueMap.put("permissions",
138 user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
139 .filter(StringUtil::isNotBlank)
140 .orElse("-"));
141 log(valueMap);
142 }
143
144
145
146
147
148 public void loginFailure(final OptionalThing<LoginCredential> credential) {
149 final Map<String, String> valueMap = new LinkedHashMap<>();
150 valueMap.put("action", Action.LOGIN_FAILURE.name());
151 credential.ifPresent(c -> {
152 valueMap.put("class", c.getClass().getSimpleName());
153 if (c instanceof final FessCredential fessCredential) {
154 valueMap.put("user", fessCredential.getUserId());
155 }
156 });
157 log(valueMap);
158 }
159
160
161
162
163
164 public void logout(final OptionalThing<FessUserBean> user) {
165 final Map<String, String> valueMap = new LinkedHashMap<>();
166 valueMap.put("action", Action.LOGOUT.name());
167 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
168 valueMap.put("permissions",
169 user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
170 .filter(StringUtil::isNotBlank)
171 .orElse("-"));
172 log(valueMap);
173 }
174
175
176
177
178
179
180
181 public void access(final OptionalThing<FessUserBean> user, final String path, final String execute) {
182 final Map<String, String> valueMap = new LinkedHashMap<>();
183 valueMap.put("action", Action.ACCESS.name());
184 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
185 valueMap.put("path", path);
186 valueMap.put("execute", execute);
187 log(valueMap);
188 }
189
190
191
192
193
194 public void permissionChanged(final OptionalThing<FessUserBean> user) {
195 final Map<String, String> valueMap = new LinkedHashMap<>();
196 valueMap.put("action", Action.UPDATE_PERMISSION.name());
197 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
198 valueMap.put("permissions",
199 user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
200 .filter(StringUtil::isNotBlank)
201 .orElse("-"));
202 log(valueMap);
203 }
204
205
206
207
208
209
210
211
212
213 public void scriptExecution(final String scriptType, final String script, final String source, final String user, final String result) {
214 if (!ComponentUtil.getFessConfig().isScriptAuditLogEnabled()) {
215 return;
216 }
217 final Map<String, String> valueMap = new LinkedHashMap<>();
218 valueMap.put("action", Action.SCRIPT_EXECUTION.name());
219 valueMap.put("scriptType", scriptType != null ? scriptType : "-");
220 valueMap.put("source", source != null ? source : "-");
221 valueMap.put("user", user != null ? user : "-");
222 valueMap.put("result", result != null ? result : "-");
223 valueMap.put("script", normalizeScript(script));
224 log(valueMap);
225 }
226
227
228
229
230
231
232
233 protected String normalizeScript(final String script) {
234 if (script == null) {
235 return "-";
236 }
237 final int maxLength = ComponentUtil.getFessConfig().getScriptAuditLogMaxLengthAsInteger();
238 String normalized = script;
239 if (normalized.length() > maxLength) {
240 normalized = normalized.substring(0, maxLength - 3) + "...";
241 }
242 return normalized.replace('\n', ' ').replace('\r', ' ').replace('\t', '_');
243 }
244
245
246
247
248
249
250 public void accessDenied(final OptionalThing<FessUserBean> user, final String path) {
251 final Map<String, String> valueMap = new LinkedHashMap<>();
252 valueMap.put("action", Action.ACCESS_DENIED.name());
253 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
254 valueMap.put("path", path);
255 log(valueMap);
256 }
257
258
259
260
261
262
263
264 public void print(final String action, final OptionalThing<FessUserBean> user, final Map<String, String> params) {
265 final Map<String, String> valueMap = new LinkedHashMap<>();
266 valueMap.put("action", action.replace('\t', '_').toUpperCase(Locale.ENGLISH));
267 valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
268 final Comparator<Map.Entry<String, String>> c = Comparator.comparing(Map.Entry::getKey);
269 params.entrySet().stream().sorted(c).forEach(e -> {
270 valueMap.put(e.getKey(), e.getValue().replace('\t', '_'));
271 });
272 log(valueMap);
273 }
274
275
276
277
278
279 protected void log(final Map<String, String> valueMap) {
280 valueMap.put("ip", getClientIp());
281 valueMap.put("time", DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now()));
282 if (useEcsFormat) {
283 printByEcs(valueMap);
284 } else {
285 printByLtsv(valueMap);
286 }
287 }
288
289
290
291
292
293 protected void printByLtsv(final Map<String, String> valueMap) {
294 printLog(valueMap.entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining("\t")));
295 }
296
297
298
299
300
301 protected void printByEcs(final Map<String, String> valueMap) {
302 final StringBuilder buf = new StringBuilder(100);
303 buf.append("{\"@timestamp\":\"").append(valueMap.remove("time")).append('"');
304 buf.append(",\"log.level\":\"INFO\"");
305 buf.append(",\"ecs.version\":\"").append(ecsVersion).append('"');
306 buf.append(",\"service.name\":\"").append(ecsServiceName).append('"');
307 buf.append(",\"event.dataset\":\"").append(ecsEventDataset).append('"');
308 buf.append(",\"process.thread.name\":\"").append(StringEscapeUtils.escapeJson(Thread.currentThread().getName())).append('"');
309 buf.append(",\"log.logger\":\"").append(StringEscapeUtils.escapeJson(this.getClass().getName())).append('"');
310 valueMap.entrySet()
311 .stream()
312 .forEach(e -> buf.append(",\"labels.")
313 .append(e.getKey())
314 .append("\":\"")
315 .append(StringEscapeUtils.escapeJson(e.getValue()))
316 .append('"'));
317 buf.append('}');
318 printLog(buf.toString());
319 }
320
321
322
323
324
325 protected void printLog(final String message) {
326 logger.info(message);
327 }
328
329
330
331
332
333 protected String getClientIp() {
334 return LaRequestUtil.getOptionalRequest().map(req -> ComponentUtil.getViewHelper().getClientIp(req)).orElse("-");
335 }
336
337
338
339
340 protected enum Action {
341
342
343
344 LOGIN,
345
346
347
348 LOGOUT,
349
350
351
352 ACCESS,
353
354
355
356 LOGIN_FAILURE,
357
358
359
360 UPDATE_PERMISSION,
361
362
363
364 SCRIPT_EXECUTION,
365
366
367
368 ACCESS_DENIED;
369 }
370
371
372
373
374
375 public void setLoggerName(final String loggerName) {
376 this.loggerName = loggerName;
377 }
378
379
380
381
382
383 public void setPermissionSeparator(final String permissionSeparator) {
384 this.permissionSeparator = permissionSeparator;
385 }
386
387
388
389
390
391 public void setEcsVersion(final String ecsVersion) {
392 this.ecsVersion = ecsVersion;
393 }
394
395
396
397
398
399 public void setEcsServiceName(final String ecsServiceName) {
400 this.ecsServiceName = ecsServiceName;
401 }
402
403
404
405
406
407 public void setEcsEventDataset(final String ecsEventDataset) {
408 this.ecsEventDataset = ecsEventDataset;
409 }
410 }