View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.fit.core;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertNotEquals;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  
30  import com.fasterxml.jackson.core.JsonProcessingException;
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.nio.charset.StandardCharsets;
35  import java.nio.file.Files;
36  import java.nio.file.Path;
37  import java.nio.file.Paths;
38  import java.nio.file.StandardOpenOption;
39  import java.time.OffsetDateTime;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Properties;
43  import java.util.Set;
44  import java.util.UUID;
45  import java.util.concurrent.TimeUnit;
46  import java.util.function.Function;
47  import javax.ws.rs.core.Response;
48  import org.apache.commons.io.IOUtils;
49  import org.apache.commons.lang3.SerializationUtils;
50  import org.apache.syncope.client.lib.SyncopeClient;
51  import org.apache.syncope.common.lib.Attr;
52  import org.apache.syncope.common.lib.SyncopeClientException;
53  import org.apache.syncope.common.lib.SyncopeConstants;
54  import org.apache.syncope.common.lib.audit.AuditEntry;
55  import org.apache.syncope.common.lib.audit.EventCategory;
56  import org.apache.syncope.common.lib.request.AnyObjectUR;
57  import org.apache.syncope.common.lib.request.AttrPatch;
58  import org.apache.syncope.common.lib.request.ResourceDR;
59  import org.apache.syncope.common.lib.request.UserUR;
60  import org.apache.syncope.common.lib.to.AnyObjectTO;
61  import org.apache.syncope.common.lib.to.AuditConfTO;
62  import org.apache.syncope.common.lib.to.ConnInstanceTO;
63  import org.apache.syncope.common.lib.to.ConnPoolConfTO;
64  import org.apache.syncope.common.lib.to.GroupTO;
65  import org.apache.syncope.common.lib.to.ImplementationTO;
66  import org.apache.syncope.common.lib.to.PagedResult;
67  import org.apache.syncope.common.lib.to.PullTaskTO;
68  import org.apache.syncope.common.lib.to.PushTaskTO;
69  import org.apache.syncope.common.lib.to.RealmTO;
70  import org.apache.syncope.common.lib.to.ResourceTO;
71  import org.apache.syncope.common.lib.to.UserTO;
72  import org.apache.syncope.common.lib.types.AnyTypeKind;
73  import org.apache.syncope.common.lib.types.AuditElements;
74  import org.apache.syncope.common.lib.types.AuditLoggerName;
75  import org.apache.syncope.common.lib.types.ConnConfProperty;
76  import org.apache.syncope.common.lib.types.ConnectorCapability;
77  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
78  import org.apache.syncope.common.lib.types.ImplementationEngine;
79  import org.apache.syncope.common.lib.types.MatchingRule;
80  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
81  import org.apache.syncope.common.lib.types.ResourceOperation;
82  import org.apache.syncope.common.lib.types.UnmatchingRule;
83  import org.apache.syncope.common.rest.api.RESTHeaders;
84  import org.apache.syncope.common.rest.api.beans.AnyQuery;
85  import org.apache.syncope.common.rest.api.beans.AuditQuery;
86  import org.apache.syncope.common.rest.api.beans.ReconQuery;
87  import org.apache.syncope.core.logic.ConnectorLogic;
88  import org.apache.syncope.core.logic.GroupLogic;
89  import org.apache.syncope.core.logic.ReportLogic;
90  import org.apache.syncope.core.logic.ResourceLogic;
91  import org.apache.syncope.core.logic.UserLogic;
92  import org.apache.syncope.fit.AbstractITCase;
93  import org.junit.jupiter.api.Test;
94  
95  public class AuditITCase extends AbstractITCase {
96  
97      private static AuditConfTO buildAuditConf(final String auditLoggerName, final boolean active) {
98          AuditConfTO auditConfTO = new AuditConfTO();
99          auditConfTO.setActive(active);
100         auditConfTO.setKey(auditLoggerName);
101         return auditConfTO;
102     }
103 
104     private static AuditEntry queryWithFailure(final AuditQuery query, final int maxWaitSeconds) {
105         List<AuditEntry> results = query(query, maxWaitSeconds);
106         if (results.isEmpty()) {
107             fail("Timeout when executing query for key " + query.getEntityKey());
108             return null;
109         }
110         return results.get(0);
111     }
112 
113     @Test
114     public void userReadAndSearchYieldsNoAudit() {
115         UserTO userTO = createUser(UserITCase.getUniqueSample("audit@syncope.org")).getEntity();
116         assertNotNull(userTO.getKey());
117 
118         AuditQuery query = new AuditQuery.Builder().entityKey(userTO.getKey()).build();
119         int entriesBefore = query(query, MAX_WAIT_SECONDS).size();
120 
121         PagedResult<UserTO> usersTOs = USER_SERVICE.search(
122                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
123                         fiql(SyncopeClient.getUserSearchConditionBuilder().
124                                 is("username").equalTo(userTO.getUsername()).query()).
125                         build());
126         assertNotNull(usersTOs);
127         assertFalse(usersTOs.getResult().isEmpty());
128 
129         int entriesAfter = query(query, MAX_WAIT_SECONDS).size();
130         assertEquals(entriesBefore, entriesAfter);
131     }
132 
133     @Test
134     public void findByUser() {
135         UserTO userTO = createUser(UserITCase.getUniqueSample("audit@syncope.org")).getEntity();
136         assertNotNull(userTO.getKey());
137 
138         AuditQuery query = new AuditQuery.Builder().
139                 entityKey(userTO.getKey()).
140                 before(OffsetDateTime.now().plusSeconds(30)).
141                 page(1).
142                 size(1).
143                 orderBy("event_date desc").
144                 build();
145         AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
146         assertNotNull(entry);
147         USER_SERVICE.delete(userTO.getKey());
148     }
149 
150     @Test
151     public void findByUserAndOther() {
152         UserTO userTO = createUser(UserITCase.getUniqueSample("audit-2@syncope.org")).getEntity();
153         assertNotNull(userTO.getKey());
154 
155         AuditQuery query = new AuditQuery.Builder().
156                 entityKey(userTO.getKey()).
157                 orderBy("event_date desc").
158                 page(1).
159                 size(1).
160                 type(AuditElements.EventCategoryType.LOGIC).
161                 category(UserLogic.class.getSimpleName()).
162                 event("create").
163                 result(AuditElements.Result.SUCCESS).
164                 after(OffsetDateTime.now().minusSeconds(30)).
165                 build();
166         AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
167         assertNotNull(entry);
168         USER_SERVICE.delete(userTO.getKey());
169     }
170 
171     @Test
172     public void findByGroup() {
173         GroupTO groupTO = createGroup(GroupITCase.getBasicSample("AuditGroup")).getEntity();
174         assertNotNull(groupTO.getKey());
175 
176         AuditQuery query = new AuditQuery.Builder().entityKey(groupTO.getKey()).orderBy("event_date desc").
177                 page(1).size(1).build();
178         AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
179         assertNotNull(entry);
180         GROUP_SERVICE.delete(groupTO.getKey());
181     }
182 
183     @Test
184     public void groupReadAndSearchYieldsNoAudit() {
185         GroupTO groupTO = createGroup(GroupITCase.getBasicSample("AuditGroupSearch")).getEntity();
186         assertNotNull(groupTO.getKey());
187 
188         AuditQuery query = new AuditQuery.Builder().entityKey(groupTO.getKey()).build();
189         int entriesBefore = query(query, MAX_WAIT_SECONDS).size();
190 
191         PagedResult<GroupTO> groups = GROUP_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
192                 fiql(SyncopeClient.getGroupSearchConditionBuilder().is("name").equalTo(groupTO.getName()).query()).
193                 build());
194         assertNotNull(groups);
195         assertFalse(groups.getResult().isEmpty());
196 
197         int entriesAfter = query(query, MAX_WAIT_SECONDS).size();
198         assertEquals(entriesBefore, entriesAfter);
199     }
200 
201     @Test
202     public void findByAnyObject() {
203         AnyObjectTO anyObjectTO = createAnyObject(AnyObjectITCase.getSample("Italy")).getEntity();
204         assertNotNull(anyObjectTO.getKey());
205         AuditQuery query = new AuditQuery.Builder().entityKey(anyObjectTO.getKey()).
206                 orderBy("event_date desc").page(1).size(1).build();
207         AuditEntry entry = queryWithFailure(query, MAX_WAIT_SECONDS);
208         assertNotNull(entry);
209         ANY_OBJECT_SERVICE.delete(anyObjectTO.getKey());
210     }
211 
212     @Test
213     public void anyObjectReadAndSearchYieldsNoAudit() {
214         AnyObjectTO anyObjectTO = createAnyObject(AnyObjectITCase.getSample("USA")).getEntity();
215         assertNotNull(anyObjectTO);
216 
217         AuditQuery query = new AuditQuery.Builder().entityKey(anyObjectTO.getKey()).build();
218         int entriesBefore = query(query, MAX_WAIT_SECONDS).size();
219 
220         PagedResult<AnyObjectTO> anyObjects = ANY_OBJECT_SERVICE.search(
221                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
222                         fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyObjectTO.getType()).query()).
223                         build());
224         assertNotNull(anyObjects);
225         assertFalse(anyObjects.getResult().isEmpty());
226 
227         int entriesAfter = query(query, MAX_WAIT_SECONDS).size();
228         assertEquals(entriesBefore, entriesAfter);
229     }
230 
231     @Test
232     public void findByConnector() throws JsonProcessingException {
233         String connectorKey = "74141a3b-0762-4720-a4aa-fc3e374ef3ef";
234 
235         AuditQuery query = new AuditQuery.Builder().
236                 entityKey(connectorKey).
237                 orderBy("event_date desc").
238                 type(AuditElements.EventCategoryType.LOGIC).
239                 category(ConnectorLogic.class.getSimpleName()).
240                 event("update").
241                 result(AuditElements.Result.SUCCESS).
242                 build();
243         List<AuditEntry> entries = AUDIT_SERVICE.search(query).getResult();
244         int pre = entries.size();
245 
246         ConnInstanceTO ldapConn = CONNECTOR_SERVICE.read(connectorKey, null);
247         String originalDisplayName = ldapConn.getDisplayName();
248         Set<ConnectorCapability> originalCapabilities = new HashSet<>(ldapConn.getCapabilities());
249         ConnConfProperty originalConfProp = SerializationUtils.clone(
250                 ldapConn.getConf("maintainPosixGroupMembership").get());
251         assertEquals(1, originalConfProp.getValues().size());
252         assertEquals("false", originalConfProp.getValues().get(0));
253 
254         ldapConn.setDisplayName(originalDisplayName + " modified");
255         ldapConn.getCapabilities().clear();
256         ldapConn.getConf("maintainPosixGroupMembership").get().getValues().set(0, "true");
257         CONNECTOR_SERVICE.update(ldapConn);
258 
259         ldapConn = CONNECTOR_SERVICE.read(connectorKey, null);
260         assertNotEquals(originalDisplayName, ldapConn.getDisplayName());
261         assertNotEquals(originalCapabilities, ldapConn.getCapabilities());
262         assertNotEquals(originalConfProp, ldapConn.getConf("maintainPosixGroupMembership"));
263 
264         entries = query(query, MAX_WAIT_SECONDS);
265         assertEquals(pre + 1, entries.size());
266 
267         ConnInstanceTO restore = JSON_MAPPER.readValue(entries.get(0).getBefore(), ConnInstanceTO.class);
268         CONNECTOR_SERVICE.update(restore);
269 
270         ldapConn = CONNECTOR_SERVICE.read(connectorKey, null);
271         assertEquals(originalDisplayName, ldapConn.getDisplayName());
272         assertEquals(originalCapabilities, ldapConn.getCapabilities());
273         assertEquals(originalConfProp, ldapConn.getConf("maintainPosixGroupMembership").get());
274     }
275 
276     @Test
277     public void enableDisable() {
278         AuditLoggerName auditLoggerName = new AuditLoggerName(
279                 AuditElements.EventCategoryType.LOGIC,
280                 ReportLogic.class.getSimpleName(),
281                 null,
282                 "deleteExecution",
283                 AuditElements.Result.FAILURE);
284 
285         List<AuditConfTO> audits = AUDIT_SERVICE.list();
286         assertFalse(audits.stream().anyMatch(a -> a.getKey().equals(auditLoggerName.toAuditKey())));
287 
288         AuditConfTO audit = new AuditConfTO();
289         audit.setKey(auditLoggerName.toAuditKey());
290         audit.setActive(true);
291         AUDIT_SERVICE.set(audit);
292 
293         audits = AUDIT_SERVICE.list();
294         assertTrue(audits.stream().anyMatch(a -> a.getKey().equals(auditLoggerName.toAuditKey())));
295 
296         AUDIT_SERVICE.delete(audit.getKey());
297 
298         audits = AUDIT_SERVICE.list();
299         assertFalse(audits.stream().anyMatch(a -> a.getKey().equals(auditLoggerName.toAuditKey())));
300     }
301 
302     @Test
303     public void listAuditEvents() {
304         List<EventCategory> events = AUDIT_SERVICE.events();
305 
306         boolean found = false;
307 
308         for (EventCategory eventCategoryTO : events) {
309             if (UserLogic.class.getSimpleName().equals(eventCategoryTO.getCategory())) {
310                 assertEquals(AuditElements.EventCategoryType.LOGIC, eventCategoryTO.getType());
311                 assertTrue(eventCategoryTO.getEvents().contains("create"));
312                 assertTrue(eventCategoryTO.getEvents().contains("search"));
313                 assertFalse(eventCategoryTO.getEvents().contains("doCreate"));
314                 assertFalse(eventCategoryTO.getEvents().contains("setStatusOnWfAdapter"));
315                 assertFalse(eventCategoryTO.getEvents().contains("resolveReference"));
316                 found = true;
317             }
318         }
319         assertTrue(found);
320 
321         found = false;
322         for (EventCategory eventCategoryTO : events) {
323             if (GroupLogic.class.getSimpleName().equals(eventCategoryTO.getCategory())) {
324                 assertEquals(AuditElements.EventCategoryType.LOGIC, eventCategoryTO.getType());
325                 assertTrue(eventCategoryTO.getEvents().contains("create"));
326                 assertTrue(eventCategoryTO.getEvents().contains("search"));
327                 assertFalse(eventCategoryTO.getEvents().contains("resolveReference"));
328                 found = true;
329             }
330         }
331         assertTrue(found);
332 
333         found = false;
334         for (EventCategory eventCategoryTO : events) {
335             if (ResourceLogic.class.getSimpleName().equals(eventCategoryTO.getCategory())) {
336                 assertEquals(AuditElements.EventCategoryType.LOGIC, eventCategoryTO.getType());
337                 assertTrue(eventCategoryTO.getEvents().contains("create"));
338                 assertTrue(eventCategoryTO.getEvents().contains("read"));
339                 assertTrue(eventCategoryTO.getEvents().contains("delete"));
340                 assertFalse(eventCategoryTO.getEvents().contains("resolveReference"));
341                 found = true;
342             }
343         }
344         assertTrue(found);
345 
346         found = false;
347         for (EventCategory eventCategoryTO : events) {
348             if (AnyTypeKind.USER.name().equals(eventCategoryTO.getCategory())) {
349                 if (RESOURCE_NAME_LDAP.equals(eventCategoryTO.getSubcategory())
350                         && AuditElements.EventCategoryType.PULL == eventCategoryTO.getType()) {
351 
352                     assertTrue(eventCategoryTO.getEvents().contains(ResourceOperation.DELETE.name().toLowerCase()));
353                     found = true;
354                 }
355             }
356         }
357         assertTrue(found);
358 
359         found = false;
360         for (EventCategory eventCategoryTO : events) {
361             if (AnyTypeKind.USER.name().equals(eventCategoryTO.getCategory())) {
362                 if (RESOURCE_NAME_CSV.equals(eventCategoryTO.getSubcategory())
363                         && AuditElements.EventCategoryType.PROPAGATION == eventCategoryTO.getType()) {
364 
365                     assertTrue(eventCategoryTO.getEvents().contains(ResourceOperation.CREATE.name().toLowerCase()));
366                     assertTrue(eventCategoryTO.getEvents().contains(ResourceOperation.UPDATE.name().toLowerCase()));
367                     assertTrue(eventCategoryTO.getEvents().contains(ResourceOperation.DELETE.name().toLowerCase()));
368                     found = true;
369                 }
370             }
371         }
372         assertTrue(found);
373 
374         found = false;
375         for (EventCategory eventCategoryTO : events) {
376             if (AuditElements.EventCategoryType.TASK == eventCategoryTO.getType()
377                     && "PullJobDelegate".equals(eventCategoryTO.getCategory())) {
378                 found = true;
379             }
380         }
381         assertTrue(found);
382     }
383 
384     private static void checkLogFileFor(
385             final Path path,
386             final Function<String, Boolean> checker,
387             final int maxWaitSeconds)
388             throws IOException {
389 
390         await().atMost(maxWaitSeconds, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
391             try {
392                 return checker.apply(Files.readString(path, StandardCharsets.UTF_8));
393             } catch (Exception e) {
394                 return false;
395             }
396         });
397     }
398 
399     @Test
400     public void saveAuditEvent() {
401         AuditEntry auditEntry = new AuditEntry();
402         auditEntry.setWho("syncope-user " + UUID.randomUUID().toString());
403         auditEntry.setLogger(new AuditLoggerName(
404                 AuditElements.EventCategoryType.WA,
405                 null,
406                 AuditElements.AUTHENTICATION_CATEGORY.toUpperCase(),
407                 "validate",
408                 AuditElements.Result.SUCCESS));
409         auditEntry.setDate(OffsetDateTime.now());
410         auditEntry.setBefore(UUID.randomUUID().toString());
411         auditEntry.setOutput(UUID.randomUUID().toString());
412         assertDoesNotThrow(() -> AUDIT_SERVICE.create(auditEntry));
413 
414         if (IS_EXT_SEARCH_ENABLED) {
415             try {
416                 Thread.sleep(2000);
417             } catch (InterruptedException ex) {
418                 // ignore
419             }
420         }
421 
422         PagedResult<AuditEntry> events = AUDIT_SERVICE.search(new AuditQuery.Builder().
423                 size(1).
424                 type(auditEntry.getLogger().getType()).
425                 category(auditEntry.getLogger().getCategory()).
426                 subcategory(auditEntry.getLogger().getSubcategory()).
427                 event(auditEntry.getLogger().getEvent()).
428                 result(auditEntry.getLogger().getResult()).
429                 build());
430         assertNotNull(events);
431         assertEquals(1, events.getSize());
432     }
433 
434     @Test
435     public void saveAuthEvent() {
436         AuditEntry auditEntry = new AuditEntry();
437         auditEntry.setWho("syncope-user " + UUID.randomUUID().toString());
438         auditEntry.setLogger(new AuditLoggerName(
439                 AuditElements.EventCategoryType.WA,
440                 null,
441                 "AuthenticationEvent",
442                 "auth",
443                 AuditElements.Result.SUCCESS));
444         auditEntry.setDate(OffsetDateTime.now());
445         auditEntry.setBefore(UUID.randomUUID().toString());
446         auditEntry.setOutput(UUID.randomUUID().toString());
447         assertDoesNotThrow(() -> AUDIT_SERVICE.create(auditEntry));
448 
449         if (IS_EXT_SEARCH_ENABLED) {
450             try {
451                 Thread.sleep(2000);
452             } catch (InterruptedException ex) {
453                 // ignore
454             }
455         }
456 
457         PagedResult<AuditEntry> events = AUDIT_SERVICE.search(new AuditQuery.Builder().
458                 size(1).
459                 type(auditEntry.getLogger().getType()).
460                 category(auditEntry.getLogger().getCategory()).
461                 subcategory(auditEntry.getLogger().getSubcategory()).
462                 event(auditEntry.getLogger().getEvent()).
463                 result(auditEntry.getLogger().getResult()).
464                 build());
465         assertNotNull(events);
466         assertEquals(1, events.getSize());
467     }
468 
469     @Test
470     public void customAuditAppender() throws IOException, InterruptedException {
471         try (InputStream propStream = getClass().getResourceAsStream("/test.properties")) {
472             Properties props = new Properties();
473             props.load(propStream);
474 
475             Path auditFilePath = Paths.get(props.getProperty("test.log.dir")
476                     + File.separator + "audit_for_Master_file.log");
477             Files.write(auditFilePath, new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
478 
479             Path auditNoRewriteFilePath = Paths.get(props.getProperty("test.log.dir")
480                     + File.separator + "audit_for_Master_norewrite_file.log");
481             Files.write(auditNoRewriteFilePath, new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
482 
483             // check that resource update is transformed and logged onto an audit file.
484             ResourceTO resource = RESOURCE_SERVICE.read(RESOURCE_NAME_CSV);
485             assertNotNull(resource);
486             resource.setPropagationPriority(100);
487             RESOURCE_SERVICE.update(resource);
488 
489             ConnInstanceTO connector = CONNECTOR_SERVICE.readByResource(RESOURCE_NAME_CSV, null);
490             assertNotNull(connector);
491             connector.setPoolConf(new ConnPoolConfTO());
492             CONNECTOR_SERVICE.update(connector);
493 
494             // check audit_for_Master_file.log, it should contain only a static message
495             checkLogFileFor(
496                     auditFilePath,
497                     content -> content.contains(
498                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
499                             + " - This is a static test message"),
500                     10);
501 
502             // nothing expected in audit_for_Master_norewrite_file.log instead
503             checkLogFileFor(
504                     auditNoRewriteFilePath,
505                     content -> !content.contains(
506                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
507                             + " - This is a static test message"),
508                     10);
509         } catch (IOException e) {
510             fail("Unable to read/write log files", e);
511         }
512     }
513 
514     @Test
515     public void issueSYNCOPE976() {
516         List<EventCategory> events = AUDIT_SERVICE.events();
517         assertNotNull(events);
518 
519         EventCategory userLogic = events.stream().
520                 filter(object -> "UserLogic".equals(object.getCategory())).findAny().get();
521         assertNotNull(userLogic);
522         assertEquals(1, userLogic.getEvents().stream().filter("create"::equals).count());
523     }
524 
525     @Test
526     public void issueSYNCOPE1446() {
527         AuditLoggerName createSuccess = new AuditLoggerName(
528                 AuditElements.EventCategoryType.PROPAGATION,
529                 AnyTypeKind.ANY_OBJECT.name(),
530                 RESOURCE_NAME_DBSCRIPTED,
531                 "create",
532                 AuditElements.Result.SUCCESS);
533         AuditLoggerName createFailure = new AuditLoggerName(
534                 AuditElements.EventCategoryType.PROPAGATION,
535                 AnyTypeKind.ANY_OBJECT.name(),
536                 RESOURCE_NAME_DBSCRIPTED,
537                 "create",
538                 AuditElements.Result.FAILURE);
539         AuditLoggerName updateSuccess = new AuditLoggerName(
540                 AuditElements.EventCategoryType.PROPAGATION,
541                 AnyTypeKind.ANY_OBJECT.name(),
542                 RESOURCE_NAME_DBSCRIPTED,
543                 "update",
544                 AuditElements.Result.SUCCESS);
545         AuditLoggerName updateFailure = new AuditLoggerName(
546                 AuditElements.EventCategoryType.PROPAGATION,
547                 AnyTypeKind.ANY_OBJECT.name(),
548                 RESOURCE_NAME_DBSCRIPTED,
549                 "update",
550                 AuditElements.Result.FAILURE);
551         try {
552             // 1. setup audit for propagation
553             AuditConfTO audit = new AuditConfTO();
554             audit.setKey(createSuccess.toAuditKey());
555             audit.setActive(true);
556             AUDIT_SERVICE.set(audit);
557 
558             audit.setKey(createFailure.toAuditKey());
559             AUDIT_SERVICE.set(audit);
560 
561             audit.setKey(updateSuccess.toAuditKey());
562             AUDIT_SERVICE.set(audit);
563 
564             audit.setKey(updateFailure.toAuditKey());
565             AUDIT_SERVICE.set(audit);
566 
567             // 2. push on resource
568             PushTaskTO pushTask = new PushTaskTO();
569             pushTask.setPerformCreate(true);
570             pushTask.setPerformUpdate(true);
571             pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
572             pushTask.setMatchingRule(MatchingRule.UPDATE);
573             RECONCILIATION_SERVICE.push(new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).
574                     anyKey("fc6dbc3a-6c07-4965-8781-921e7401a4a5").build(), pushTask);
575         } catch (Exception e) {
576             LOG.error("Unexpected exception", e);
577             fail(e::getMessage);
578         } finally {
579             try {
580                 AUDIT_SERVICE.delete(createSuccess.toAuditKey());
581             } catch (Exception e) {
582                 // ignore
583             }
584             try {
585                 AUDIT_SERVICE.delete(createFailure.toAuditKey());
586             } catch (Exception e) {
587                 // ignore
588             }
589             try {
590                 AUDIT_SERVICE.delete(updateSuccess.toAuditKey());
591             } catch (Exception e) {
592                 // ignore
593             }
594             try {
595                 AUDIT_SERVICE.delete(updateFailure.toAuditKey());
596             } catch (Exception e) {
597                 // ignore
598             }
599         }
600     }
601 
602     @Test
603     public void issueSYNCOPE1695() {
604         // add audit conf for pull
605         AUDIT_SERVICE.set(buildAuditConf(
606                 "syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", true));
607         AUDIT_SERVICE.set(buildAuditConf(
608                 "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", true));
609         AUDIT_SERVICE.set(buildAuditConf(
610                 "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]", true));
611 
612         UserTO pullFromLDAP = null;
613         try {
614             // pull from resource-ldap -> generates an audit entry
615             PullTaskTO pullTaskTO = new PullTaskTO();
616             pullTaskTO.setPerformCreate(true);
617             pullTaskTO.setPerformUpdate(true);
618             pullTaskTO.getActions().add("LDAPMembershipPullActions");
619             pullTaskTO.setDestinationRealm(SyncopeConstants.ROOT_REALM);
620             pullTaskTO.setMatchingRule(MatchingRule.UPDATE);
621             pullTaskTO.setUnmatchingRule(UnmatchingRule.ASSIGN);
622             RECONCILIATION_SERVICE.pull(new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).
623                     fiql("uid==pullFromLDAP").build(), pullTaskTO);
624 
625             // update pullTaskTO -> another audit entry
626             pullFromLDAP = updateUser(new UserUR.Builder(USER_SERVICE.read("pullFromLDAP").getKey()).
627                     plainAttr(new AttrPatch.Builder(new Attr.Builder("ctype").value("abcdef").build()).build()).
628                     build()).getEntity();
629 
630             // search by empty type and category events and get both events on testfromLDAP
631             if (IS_EXT_SEARCH_ENABLED) {
632                 try {
633                     Thread.sleep(2000);
634                 } catch (InterruptedException ex) {
635                     // ignore
636                 }
637             }
638 
639             assertEquals(1, AUDIT_SERVICE.search(new AuditQuery.Builder().
640                     entityKey(pullFromLDAP.getKey()).
641                     page(1).
642                     size(10).
643                     events(List.of("matchingrule_update", "unmatchingrule_assign", "unmatchingrule_provision")).
644                     result(AuditElements.Result.SUCCESS).
645                     build()).getTotalCount());
646         } finally {
647             if (pullFromLDAP != null) {
648                 USER_SERVICE.deassociate(new ResourceDR.Builder()
649                         .key(pullFromLDAP.getKey())
650                         .resource(RESOURCE_NAME_LDAP)
651                         .action(ResourceDeassociationAction.UNLINK)
652                         .build());
653                 USER_SERVICE.delete(pullFromLDAP.getKey());
654 
655                 // restore previous audit
656                 AUDIT_SERVICE.set(buildAuditConf(
657                         "syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", false));
658                 AUDIT_SERVICE.set(buildAuditConf(
659                         "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", false));
660                 AUDIT_SERVICE.set(buildAuditConf(
661                         "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]", false));
662             }
663         }
664     }
665 
666     @Test
667     public void issueSYNCOPE1791() throws IOException {
668         ImplementationTO logicActions;
669         try {
670             logicActions = IMPLEMENTATION_SERVICE.read(
671                     IdRepoImplementationType.LOGIC_ACTIONS, "CustomAuditLogicActions");
672         } catch (SyncopeClientException e) {
673             logicActions = new ImplementationTO();
674             logicActions.setKey("CustomAuditLogicActions");
675             logicActions.setEngine(ImplementationEngine.GROOVY);
676             logicActions.setType(IdRepoImplementationType.LOGIC_ACTIONS);
677             logicActions.setBody(IOUtils.toString(
678                     getClass().getResourceAsStream("/CustomAuditLogicActions.groovy"), StandardCharsets.UTF_8));
679             Response response = IMPLEMENTATION_SERVICE.create(logicActions);
680             logicActions = IMPLEMENTATION_SERVICE.read(
681                     logicActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
682         }
683         assertNotNull(logicActions);
684 
685         RealmTO root = getRealm(SyncopeConstants.ROOT_REALM).orElseThrow();
686         root.getActions().add(logicActions.getKey());
687         REALM_SERVICE.update(root);
688 
689         AuditQuery query = new AuditQuery.Builder().type(AuditElements.EventCategoryType.CUSTOM).build();
690         int before = query(query, MAX_WAIT_SECONDS).size();
691         try {
692             AUDIT_SERVICE.set(buildAuditConf("syncope.audit.[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", true));
693 
694             AnyObjectTO printer = createAnyObject(AnyObjectITCase.getSample("syncope-1791")).getEntity();
695             updateAnyObject(new AnyObjectUR.Builder(printer.getKey()).
696                     plainAttr(attrAddReplacePatch("location", "new" + getUUIDString())).
697                     build());
698 
699             int after = query(query, MAX_WAIT_SECONDS).size();
700             assertEquals(before + 1, after);
701         } finally {
702             AUDIT_SERVICE.set(buildAuditConf("syncope.audit.[CUSTOM]:[]:[]:[MY_EVENT]:[SUCCESS]", false));
703 
704             root.getActions().remove(logicActions.getKey());
705             REALM_SERVICE.update(root);
706         }
707     }
708 }