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