1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.persistence.jpa.dao;
20
21 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import java.io.IOException;
23 import java.time.OffsetDateTime;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.stream.Collectors;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.syncope.common.lib.audit.AuditEntry;
30 import org.apache.syncope.common.lib.types.AuditElements;
31 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
32 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
33 import org.apache.syncope.core.spring.security.AuthContextUtils;
34 import org.apache.syncope.ext.opensearch.client.OpenSearchUtils;
35 import org.opensearch.client.json.JsonData;
36 import org.opensearch.client.opensearch.OpenSearchClient;
37 import org.opensearch.client.opensearch._types.FieldSort;
38 import org.opensearch.client.opensearch._types.FieldValue;
39 import org.opensearch.client.opensearch._types.SearchType;
40 import org.opensearch.client.opensearch._types.SortOptions;
41 import org.opensearch.client.opensearch._types.SortOrder;
42 import org.opensearch.client.opensearch._types.query_dsl.Query;
43 import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
44 import org.opensearch.client.opensearch._types.query_dsl.TextQueryType;
45 import org.opensearch.client.opensearch.core.CountRequest;
46 import org.opensearch.client.opensearch.core.SearchRequest;
47 import org.opensearch.client.opensearch.core.search.Hit;
48 import org.springframework.util.CollectionUtils;
49
50 public class OpenSearchAuditConfDAO extends JPAAuditConfDAO {
51
52 protected final OpenSearchClient client;
53
54 protected final int indexMaxResultWindow;
55
56 public OpenSearchAuditConfDAO(final OpenSearchClient client, final int indexMaxResultWindow) {
57 this.client = client;
58 this.indexMaxResultWindow = indexMaxResultWindow;
59 }
60
61 protected Query getQuery(
62 final String entityKey,
63 final AuditElements.EventCategoryType type,
64 final String category,
65 final String subcategory,
66 final List<String> events,
67 final AuditElements.Result result,
68 final OffsetDateTime before,
69 final OffsetDateTime after) {
70
71 List<Query> queries = new ArrayList<>();
72
73 if (entityKey != null) {
74 queries.add(new Query.Builder().
75 multiMatch(QueryBuilders.multiMatch().
76 fields("message.before", "message.inputs", "message.output", "message.throwable").
77 type(TextQueryType.Phrase).
78 query(entityKey).build()).build());
79 }
80
81 if (type != null) {
82 queries.add(new Query.Builder().
83 term(QueryBuilders.term().field("message.logger.type").value(FieldValue.of(type.name())).build()).
84 build());
85 }
86
87 if (StringUtils.isNotBlank(category)) {
88 queries.add(new Query.Builder().
89 term(QueryBuilders.term().field("message.logger.category").value(FieldValue.of(category)).build()).
90 build());
91 }
92
93 if (StringUtils.isNotBlank(subcategory)) {
94 queries.add(new Query.Builder().
95 term(QueryBuilders.term().field("message.logger.subcategory").
96 value(FieldValue.of(subcategory)).build()).
97 build());
98 }
99
100 List<Query> eventQueries = events.stream().map(event -> new Query.Builder().
101 term(QueryBuilders.term().field("message.logger.event").value(FieldValue.of(event)).build()).
102 build()).
103 collect(Collectors.toList());
104 if (!eventQueries.isEmpty()) {
105 queries.add(new Query.Builder().disMax(QueryBuilders.disMax().queries(eventQueries).build()).build());
106 }
107
108 if (result != null) {
109 queries.add(new Query.Builder().
110 term(QueryBuilders.term().field("message.logger.result").
111 value(FieldValue.of(result.name())).build()).
112 build());
113 }
114
115 if (before != null) {
116 queries.add(new Query.Builder().
117 range(QueryBuilders.range().
118 field("instant").lte(JsonData.of(before.toInstant().toEpochMilli())).build()).
119 build());
120 }
121
122 if (after != null) {
123 queries.add(new Query.Builder().
124 range(QueryBuilders.range().
125 field("instant").gte(JsonData.of(after.toInstant().toEpochMilli())).build()).
126 build());
127 }
128
129 return new Query.Builder().bool(QueryBuilders.bool().must(queries).build()).build();
130 }
131
132 @Override
133 public int countEntries(
134 final String entityKey,
135 final AuditElements.EventCategoryType type,
136 final String category,
137 final String subcategory,
138 final List<String> events,
139 final AuditElements.Result result,
140 final OffsetDateTime before,
141 final OffsetDateTime after) {
142
143 CountRequest request = new CountRequest.Builder().
144 index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
145 query(getQuery(entityKey, type, category, subcategory, events, result, before, after)).
146 build();
147 try {
148 return (int) client.count(request).count();
149 } catch (IOException e) {
150 LOG.error("Search error", e);
151 return 0;
152 }
153 }
154
155 protected List<SortOptions> sortBuilders(final List<OrderByClause> orderBy) {
156 return orderBy.stream().map(clause -> {
157 String sortField = clause.getField();
158 if ("EVENT_DATE".equalsIgnoreCase(sortField)) {
159 sortField = "message.date";
160 }
161
162 return new SortOptions.Builder().field(
163 new FieldSort.Builder().
164 field(sortField).
165 order(clause.getDirection() == OrderByClause.Direction.ASC
166 ? SortOrder.Asc : SortOrder.Desc).
167 build()).
168 build();
169 }).collect(Collectors.toList());
170 }
171
172 @Override
173 public List<AuditEntry> searchEntries(
174 final String entityKey,
175 final int page,
176 final int itemsPerPage,
177 final AuditElements.EventCategoryType type,
178 final String category,
179 final String subcategory,
180 final List<String> events,
181 final AuditElements.Result result,
182 final OffsetDateTime before,
183 final OffsetDateTime after,
184 final List<OrderByClause> orderBy) {
185
186 SearchRequest request = new SearchRequest.Builder().
187 index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
188 searchType(SearchType.QueryThenFetch).
189 query(getQuery(entityKey, type, category, subcategory, events, result, before, after)).
190 fields(f -> f.field("message")).
191 from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
192 size(itemsPerPage < 0 ? indexMaxResultWindow : itemsPerPage).
193 sort(sortBuilders(orderBy)).
194 build();
195
196 List<Hit<ObjectNode>> esResult = null;
197 try {
198 esResult = client.search(request, ObjectNode.class).hits().hits();
199 } catch (Exception e) {
200 LOG.error("While searching in OpenSearch", e);
201 }
202
203 return CollectionUtils.isEmpty(esResult)
204 ? List.of()
205 : esResult.stream().
206 map(hit -> POJOHelper.convertValue(hit.source().get("message"), AuditEntry.class)).
207 filter(Objects::nonNull).collect(Collectors.toList());
208 }
209 }