1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic;
20
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.time.OffsetDateTime;
24 import java.util.ArrayList;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import org.apache.commons.lang3.ArrayUtils;
31 import org.apache.commons.lang3.tuple.Pair;
32 import org.apache.logging.log4j.LogManager;
33 import org.apache.logging.log4j.core.LoggerContext;
34 import org.apache.logging.log4j.core.config.LoggerConfig;
35 import org.apache.syncope.common.lib.SyncopeClientException;
36 import org.apache.syncope.common.lib.audit.AuditEntry;
37 import org.apache.syncope.common.lib.audit.EventCategory;
38 import org.apache.syncope.common.lib.to.AuditConfTO;
39 import org.apache.syncope.common.lib.types.AnyTypeKind;
40 import org.apache.syncope.common.lib.types.AuditElements;
41 import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
42 import org.apache.syncope.common.lib.types.AuditLoggerName;
43 import org.apache.syncope.common.lib.types.ClientExceptionType;
44 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
45 import org.apache.syncope.common.lib.types.MatchingRule;
46 import org.apache.syncope.common.lib.types.ResourceOperation;
47 import org.apache.syncope.common.lib.types.UnmatchingRule;
48 import org.apache.syncope.core.logic.audit.AuditAppender;
49 import org.apache.syncope.core.logic.init.AuditLoader;
50 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
51 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
52 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
53 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
54 import org.apache.syncope.core.persistence.api.entity.AuditConf;
55 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
56 import org.apache.syncope.core.provisioning.api.AuditManager;
57 import org.apache.syncope.core.provisioning.api.data.AuditDataBinder;
58 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
59 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
60 import org.apache.syncope.core.spring.security.AuthContextUtils;
61 import org.springframework.boot.logging.LogLevel;
62 import org.springframework.boot.logging.LoggingSystem;
63 import org.springframework.core.io.Resource;
64 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
65 import org.springframework.core.io.support.ResourcePatternResolver;
66 import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
67 import org.springframework.core.type.classreading.MetadataReader;
68 import org.springframework.core.type.classreading.MetadataReaderFactory;
69 import org.springframework.security.access.prepost.PreAuthorize;
70 import org.springframework.transaction.annotation.Transactional;
71 import org.springframework.util.ClassUtils;
72 import org.springframework.util.SystemPropertyUtils;
73
74 public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
75
76 protected static final List<EventCategory> EVENTS = new ArrayList<>();
77
78 protected final AuditConfDAO auditConfDAO;
79
80 protected final ExternalResourceDAO resourceDAO;
81
82 protected final EntityFactory entityFactory;
83
84 protected final AuditDataBinder binder;
85
86 protected final AuditManager auditManager;
87
88 protected final List<AuditAppender> auditAppenders;
89
90 protected final LoggingSystem loggingSystem;
91
92 public AuditLogic(
93 final AuditConfDAO auditConfDAO,
94 final ExternalResourceDAO resourceDAO,
95 final EntityFactory entityFactory,
96 final AuditDataBinder binder,
97 final AuditManager auditManager,
98 final List<AuditAppender> auditAppenders,
99 final LoggingSystem loggingSystem) {
100
101 this.auditConfDAO = auditConfDAO;
102 this.resourceDAO = resourceDAO;
103 this.entityFactory = entityFactory;
104 this.binder = binder;
105 this.auditManager = auditManager;
106 this.auditAppenders = auditAppenders;
107 this.loggingSystem = loggingSystem;
108 }
109
110 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "')")
111 @Transactional(readOnly = true)
112 public List<AuditConfTO> list() {
113 return auditConfDAO.findAll().stream().map(binder::getAuditTO).collect(Collectors.toList());
114 }
115
116 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_READ + "')")
117 @Transactional(readOnly = true)
118 public AuditConfTO read(final String key) {
119 return Optional.ofNullable(auditConfDAO.find(key)).map(binder::getAuditTO).
120 orElseThrow(() -> new NotFoundException("Audit " + key));
121 }
122
123 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_SET + "')")
124 public void set(final AuditConfTO auditTO) {
125 AuditConf audit = Optional.ofNullable(auditConfDAO.find(auditTO.getKey())).
126 orElseGet(() -> {
127 AuditConf ac = entityFactory.newEntity(AuditConf.class);
128 ac.setKey(auditTO.getKey());
129 return ac;
130 });
131 audit.setActive(auditTO.isActive());
132 audit = auditConfDAO.save(audit);
133
134 setLevel(audit.getKey(), audit.isActive() ? LogLevel.DEBUG : LogLevel.OFF);
135 }
136
137 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_DELETE + "')")
138 public void delete(final String key) {
139 AuditConf audit = Optional.ofNullable(auditConfDAO.find(key)).
140 orElseThrow(() -> new NotFoundException("Audit " + key));
141 auditConfDAO.delete(audit);
142
143 setLevel(audit.getKey(), LogLevel.OFF);
144 }
145
146 protected void setLevel(final String key, final LogLevel level) {
147 String auditLoggerName = AuditLoggerName.getAuditEventLoggerName(AuthContextUtils.getDomain(), key);
148
149 LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
150 LoggerConfig logConf = logCtx.getConfiguration().getLoggerConfig(auditLoggerName);
151
152 auditAppenders.stream().
153 filter(appender -> appender.getEvents().stream().
154 anyMatch(event -> key.equalsIgnoreCase(event.toAuditKey()))).
155 forEach(auditAppender -> AuditLoader.addAppenderToLoggerContext(logCtx, auditAppender, logConf));
156
157 loggingSystem.setLogLevel(auditLoggerName, level);
158 }
159
160 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "') "
161 + "or hasRole('" + IdRepoEntitlement.NOTIFICATION_LIST + "')")
162 public List<EventCategory> events() {
163 synchronized (EVENTS) {
164 if (!EVENTS.isEmpty()) {
165 return EVENTS;
166 }
167 }
168
169 Set<EventCategory> events = new HashSet<>();
170
171 EventCategory authenticationEventCategory = new EventCategory();
172 authenticationEventCategory.setCategory(AuditElements.AUTHENTICATION_CATEGORY);
173 authenticationEventCategory.getEvents().add(AuditElements.LOGIN_EVENT);
174 events.add(authenticationEventCategory);
175
176 EventCategory pullTaskEventCategory = new EventCategory(EventCategoryType.TASK);
177 pullTaskEventCategory.setCategory(PullJobDelegate.class.getSimpleName());
178 events.add(pullTaskEventCategory);
179
180 EventCategory pushTaskEventCategory = new EventCategory(EventCategoryType.TASK);
181 pushTaskEventCategory.setCategory(PushJobDelegate.class.getSimpleName());
182 events.add(pushTaskEventCategory);
183
184 events.add(new EventCategory(EventCategoryType.WA));
185 events.add(new EventCategory(EventCategoryType.CUSTOM));
186
187 try {
188 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
189 MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
190
191 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
192 + ClassUtils.convertClassNameToResourcePath(
193 SystemPropertyUtils.resolvePlaceholders(getClass().getPackage().getName()))
194 + "/**/*.class";
195
196 Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
197 for (Resource resource : resources) {
198 if (resource.isReadable()) {
199 MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
200 Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
201
202 if (AbstractLogic.class.isAssignableFrom(clazz)) {
203 EventCategory eventCategory = new EventCategory();
204 eventCategory.setCategory(clazz.getSimpleName());
205 for (Method method : clazz.getDeclaredMethods()) {
206 if (Modifier.isPublic(method.getModifiers())
207 && !eventCategory.getEvents().contains(method.getName())) {
208
209 eventCategory.getEvents().add(method.getName());
210 }
211 }
212
213 events.add(eventCategory);
214 }
215 }
216 }
217
218 for (AnyTypeKind anyTypeKind : AnyTypeKind.values()) {
219 resourceDAO.findAll().forEach(resource -> {
220 EventCategory propEventCategory = new EventCategory(EventCategoryType.PROPAGATION);
221 EventCategory pullEventCategory = new EventCategory(EventCategoryType.PULL);
222 EventCategory pushEventCategory = new EventCategory(EventCategoryType.PUSH);
223
224 propEventCategory.setCategory(anyTypeKind.name());
225 propEventCategory.setSubcategory(resource.getKey());
226
227 pullEventCategory.setCategory(anyTypeKind.name());
228 pushEventCategory.setCategory(anyTypeKind.name());
229 pullEventCategory.setSubcategory(resource.getKey());
230 pushEventCategory.setSubcategory(resource.getKey());
231
232 for (ResourceOperation resourceOperation : ResourceOperation.values()) {
233 propEventCategory.getEvents().add(resourceOperation.name().toLowerCase());
234 }
235 pullEventCategory.getEvents().add(ResourceOperation.DELETE.name().toLowerCase());
236
237 for (UnmatchingRule unmatching : UnmatchingRule.values()) {
238 String event = UnmatchingRule.toEventName(unmatching);
239 pullEventCategory.getEvents().add(event);
240 pushEventCategory.getEvents().add(event);
241 }
242
243 for (MatchingRule matching : MatchingRule.values()) {
244 String event = MatchingRule.toEventName(matching);
245 pullEventCategory.getEvents().add(event);
246 pushEventCategory.getEvents().add(event);
247 }
248
249 events.add(propEventCategory);
250 events.add(pullEventCategory);
251 events.add(pushEventCategory);
252 });
253 }
254 } catch (Exception e) {
255 LOG.error("Failure retrieving audit/notification events", e);
256 }
257
258 EVENTS.addAll(events);
259 return EVENTS;
260 }
261
262 @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_SEARCH + "')")
263 @Transactional(readOnly = true)
264 public Pair<Integer, List<AuditEntry>> search(
265 final String entityKey,
266 final int page,
267 final int size,
268 final AuditElements.EventCategoryType type,
269 final String category,
270 final String subcategory,
271 final List<String> events,
272 final AuditElements.Result result,
273 final OffsetDateTime before,
274 final OffsetDateTime after,
275 final List<OrderByClause> orderBy) {
276
277 int count = auditConfDAO.countEntries(entityKey, type, category, subcategory, events, result, before, after);
278 List<AuditEntry> matching = auditConfDAO.searchEntries(
279 entityKey, page, size, type, category, subcategory, events, result, before, after, orderBy);
280 return Pair.of(count, matching);
281 }
282
283 @PreAuthorize("isAuthenticated()")
284 public void create(final AuditEntry auditEntry) {
285 boolean authorized =
286 AuthContextUtils.getAuthorizations().containsKey(IdRepoEntitlement.AUDIT_SET)
287 || AuthContextUtils.getAuthorizations().containsKey(IdRepoEntitlement.ANONYMOUS)
288 && AuditElements.EventCategoryType.WA == auditEntry.getLogger().getType();
289 if (authorized) {
290 auditManager.audit(
291 auditEntry.getWho(),
292 auditEntry.getLogger().getType(),
293 auditEntry.getLogger().getCategory(),
294 auditEntry.getLogger().getSubcategory(),
295 auditEntry.getLogger().getEvent(),
296 auditEntry.getLogger().getResult(),
297 auditEntry.getBefore(),
298 auditEntry.getOutput(),
299 auditEntry.getInputs());
300 } else {
301 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.DelegatedAdministration);
302 sce.getElements().add("Not allowed to create Audit entries");
303 throw sce;
304 }
305 }
306
307 @Override
308 protected AuditConfTO resolveReference(final Method method, final Object... args)
309 throws UnresolvedReferenceException {
310
311 String key = null;
312
313 if (ArrayUtils.isNotEmpty(args)) {
314 for (int i = 0; key == null && i < args.length; i++) {
315 if (args[i] instanceof String) {
316 key = (String) args[i];
317 } else if (args[i] instanceof AuditConfTO) {
318 key = ((AuditConfTO) args[i]).getKey();
319 }
320 }
321 }
322
323 if (key != null) {
324 try {
325 return binder.getAuditTO(auditConfDAO.find(key));
326 } catch (Throwable ignore) {
327 LOG.debug("Unresolved reference", ignore);
328 throw new UnresolvedReferenceException(ignore);
329 }
330 }
331
332 throw new UnresolvedReferenceException();
333 }
334 }