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.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotEquals;
25  import static org.junit.jupiter.api.Assertions.assertNotNull;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  
30  import java.io.IOException;
31  import java.text.ParseException;
32  import java.time.LocalDate;
33  import java.time.OffsetDateTime;
34  import java.time.ZoneOffset;
35  import java.time.format.DateTimeFormatter;
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.HashSet;
39  import java.util.List;
40  import java.util.Optional;
41  import java.util.Set;
42  import java.util.concurrent.TimeUnit;
43  import javax.ws.rs.core.GenericType;
44  import javax.ws.rs.core.Response;
45  import javax.xml.ws.WebServiceException;
46  import org.apache.commons.lang3.SerializationUtils;
47  import org.apache.commons.lang3.StringUtils;
48  import org.apache.syncope.client.lib.SyncopeClient;
49  import org.apache.syncope.client.lib.batch.BatchRequest;
50  import org.apache.syncope.common.lib.Attr;
51  import org.apache.syncope.common.lib.SyncopeClientException;
52  import org.apache.syncope.common.lib.SyncopeConstants;
53  import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
54  import org.apache.syncope.common.lib.request.AnyObjectCR;
55  import org.apache.syncope.common.lib.request.AnyObjectUR;
56  import org.apache.syncope.common.lib.request.AttrPatch;
57  import org.apache.syncope.common.lib.request.GroupCR;
58  import org.apache.syncope.common.lib.request.GroupUR;
59  import org.apache.syncope.common.lib.request.MembershipUR;
60  import org.apache.syncope.common.lib.request.ResourceDR;
61  import org.apache.syncope.common.lib.request.UserCR;
62  import org.apache.syncope.common.lib.request.UserUR;
63  import org.apache.syncope.common.lib.to.AnyObjectTO;
64  import org.apache.syncope.common.lib.to.AnyTypeClassTO;
65  import org.apache.syncope.common.lib.to.ConnObject;
66  import org.apache.syncope.common.lib.to.ExecTO;
67  import org.apache.syncope.common.lib.to.GroupTO;
68  import org.apache.syncope.common.lib.to.ImplementationTO;
69  import org.apache.syncope.common.lib.to.Item;
70  import org.apache.syncope.common.lib.to.MembershipTO;
71  import org.apache.syncope.common.lib.to.PagedResult;
72  import org.apache.syncope.common.lib.to.PlainSchemaTO;
73  import org.apache.syncope.common.lib.to.PropagationTaskTO;
74  import org.apache.syncope.common.lib.to.Provision;
75  import org.apache.syncope.common.lib.to.ProvisioningResult;
76  import org.apache.syncope.common.lib.to.ReconStatus;
77  import org.apache.syncope.common.lib.to.RelationshipTO;
78  import org.apache.syncope.common.lib.to.ResourceTO;
79  import org.apache.syncope.common.lib.to.TaskTO;
80  import org.apache.syncope.common.lib.to.UserTO;
81  import org.apache.syncope.common.lib.types.AnyTypeKind;
82  import org.apache.syncope.common.lib.types.AttrSchemaType;
83  import org.apache.syncope.common.lib.types.ExecStatus;
84  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
85  import org.apache.syncope.common.lib.types.ImplementationEngine;
86  import org.apache.syncope.common.lib.types.MappingPurpose;
87  import org.apache.syncope.common.lib.types.PatchOperation;
88  import org.apache.syncope.common.lib.types.PolicyType;
89  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
90  import org.apache.syncope.common.lib.types.ResourceOperation;
91  import org.apache.syncope.common.lib.types.SchemaType;
92  import org.apache.syncope.common.lib.types.TaskType;
93  import org.apache.syncope.common.rest.api.RESTHeaders;
94  import org.apache.syncope.common.rest.api.beans.ExecQuery;
95  import org.apache.syncope.common.rest.api.beans.ExecSpecs;
96  import org.apache.syncope.common.rest.api.beans.ReconQuery;
97  import org.apache.syncope.common.rest.api.beans.TaskQuery;
98  import org.apache.syncope.common.rest.api.service.TaskService;
99  import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
100 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
101 import org.apache.syncope.fit.core.reference.DateToDateItemTransformer;
102 import org.apache.syncope.fit.core.reference.DateToLongItemTransformer;
103 import org.identityconnectors.framework.common.objects.Attribute;
104 import org.identityconnectors.framework.common.objects.AttributeUtil;
105 import org.identityconnectors.framework.common.objects.Name;
106 import org.identityconnectors.framework.common.objects.OperationalAttributes;
107 import org.junit.jupiter.api.BeforeAll;
108 import org.junit.jupiter.api.Test;
109 import org.springframework.dao.DataAccessException;
110 import org.springframework.jdbc.core.JdbcTemplate;
111 
112 public class PropagationTaskITCase extends AbstractTaskITCase {
113 
114     @BeforeAll
115     public static void testItemTransformersSetup() {
116         ImplementationTO dateToLong = null;
117         ImplementationTO dateToDate = null;
118         try {
119             dateToLong = IMPLEMENTATION_SERVICE.read(
120                     IdRepoImplementationType.ITEM_TRANSFORMER, DateToLongItemTransformer.class.getSimpleName());
121             dateToDate = IMPLEMENTATION_SERVICE.read(
122                     IdRepoImplementationType.ITEM_TRANSFORMER, DateToDateItemTransformer.class.getSimpleName());
123         } catch (SyncopeClientException e) {
124             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
125                 dateToLong = new ImplementationTO();
126                 dateToLong.setKey(DateToLongItemTransformer.class.getSimpleName());
127                 dateToLong.setEngine(ImplementationEngine.JAVA);
128                 dateToLong.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
129                 dateToLong.setBody(DateToLongItemTransformer.class.getName());
130                 Response response = IMPLEMENTATION_SERVICE.create(dateToLong);
131                 dateToLong = IMPLEMENTATION_SERVICE.read(
132                         dateToLong.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
133                 assertNotNull(dateToLong);
134 
135                 dateToDate = new ImplementationTO();
136                 dateToDate.setKey(DateToDateItemTransformer.class.getSimpleName());
137                 dateToDate.setEngine(ImplementationEngine.JAVA);
138                 dateToDate.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
139                 dateToDate.setBody(DateToDateItemTransformer.class.getName());
140                 response = IMPLEMENTATION_SERVICE.create(dateToDate);
141                 dateToDate = IMPLEMENTATION_SERVICE.read(
142                         dateToDate.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
143                 assertNotNull(dateToDate);
144             }
145         }
146         assertNotNull(dateToLong);
147         assertNotNull(dateToDate);
148     }
149 
150     @Test
151     public void paginatedList() {
152         PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(
153                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(2).build());
154         assertNotNull(tasks);
155         assertEquals(2, tasks.getResult().size());
156 
157         for (TaskTO task : tasks.getResult()) {
158             assertNotNull(task);
159         }
160 
161         tasks = TASK_SERVICE.search(
162                 new TaskQuery.Builder(TaskType.PROPAGATION).page(2).size(2).build());
163         assertNotNull(tasks);
164         assertEquals(2, tasks.getPage());
165         assertEquals(2, tasks.getResult().size());
166 
167         for (TaskTO task : tasks.getResult()) {
168             assertNotNull(task);
169         }
170 
171         tasks = TASK_SERVICE.search(
172                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1000).size(2).build());
173         assertNotNull(tasks);
174         assertTrue(tasks.getResult().isEmpty());
175     }
176 
177     @Test
178     public void read() {
179         PropagationTaskTO taskTO = TASK_SERVICE.read(
180                 TaskType.PROPAGATION, "316285cc-ae52-4ea2-a33b-7355e189ac3f", true);
181         assertNotNull(taskTO);
182         assertNotNull(taskTO.getExecutions());
183         assertTrue(taskTO.getExecutions().isEmpty());
184     }
185 
186     @Test
187     public void batch() throws IOException {
188         // create user with testdb resource
189         UserCR userCR = UserITCase.getUniqueSample("taskBatch@apache.org");
190         userCR.getResources().add(RESOURCE_NAME_TESTDB);
191         UserTO userTO = createUser(userCR).getEntity();
192 
193         List<PropagationTaskTO> tasks = new ArrayList<>(
194                 TASK_SERVICE.<PropagationTaskTO>search(new TaskQuery.Builder(TaskType.PROPAGATION).
195                         anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build()).
196                         getResult());
197         assertFalse(tasks.isEmpty());
198 
199         BatchRequest batchRequest = ADMIN_CLIENT.batch();
200 
201         TaskService batchTaskService = batchRequest.getService(TaskService.class);
202         tasks.forEach(task -> batchTaskService.delete(TaskType.PROPAGATION, task.getKey()));
203 
204         Response response = batchRequest.commit().getResponse();
205         parseBatchResponse(response);
206 
207         assertFalse(TASK_SERVICE.search(
208                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(100).build()).
209                 getResult().containsAll(tasks));
210     }
211 
212     @Test
213     public void propagationJEXLTransformer() {
214         // 0. Set propagation JEXL MappingItemTransformer
215         ResourceTO resource = RESOURCE_SERVICE.read(RESOURCE_NAME_DBSCRIPTED);
216         ResourceTO originalResource = SerializationUtils.clone(resource);
217         Provision provision = resource.getProvision(PRINTER).get();
218         assertNotNull(provision);
219 
220         Optional<Item> mappingItem = provision.getMapping().getItems().stream().
221                 filter(item -> "location".equals(item.getIntAttrName())).findFirst();
222         assertTrue(mappingItem.isPresent());
223         assertTrue(mappingItem.get().getTransformers().isEmpty());
224 
225         String suffix = getUUIDString();
226         mappingItem.get().setPropagationJEXLTransformer("value + '" + suffix + '\'');
227 
228         try {
229             RESOURCE_SERVICE.update(resource);
230 
231             // 1. create printer on external resource
232             AnyObjectCR anyObjectCR = AnyObjectITCase.getSample("propagationJEXLTransformer");
233             String originalLocation = anyObjectCR.getPlainAttr("location").get().getValues().get(0);
234             assertFalse(originalLocation.endsWith(suffix));
235 
236             AnyObjectTO anyObjectTO = createAnyObject(anyObjectCR).getEntity();
237             assertNotNull(anyObjectTO);
238 
239             // 2. verify that JEXL MappingItemTransformer was applied during propagation
240             // (location ends with given suffix on external resource)
241             ConnObject connObjectTO = RESOURCE_SERVICE.
242                     readConnObject(RESOURCE_NAME_DBSCRIPTED, anyObjectTO.getType(), anyObjectTO.getKey());
243             assertFalse(anyObjectTO.getPlainAttr("location").get().getValues().get(0).endsWith(suffix));
244             assertTrue(connObjectTO.getAttr("LOCATION").get().getValues().get(0).endsWith(suffix));
245         } finally {
246             RESOURCE_SERVICE.update(originalResource);
247         }
248     }
249 
250     @Test
251     public void privileges() {
252         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
253         ldap.setKey("ldapWithPrivileges");
254 
255         Provision provision = ldap.getProvision(AnyTypeKind.USER.name()).orElse(null);
256         provision.getMapping().getItems().removeIf(item -> "mail".equals(item.getIntAttrName()));
257         provision.getVirSchemas().clear();
258 
259         ldap.getProvisions().clear();
260         ldap.getProvisions().add(provision);
261 
262         Item item = new Item();
263         item.setIntAttrName("privileges[mightyApp]");
264         item.setExtAttrName("businessCategory");
265         item.setPurpose(MappingPurpose.PROPAGATION);
266 
267         provision.getMapping().add(item);
268 
269         ldap = createResource(ldap);
270 
271         try {
272             UserCR userCR = UserITCase.getUniqueSample("privilege@syncope.apache.org");
273             userCR.getResources().add(ldap.getKey());
274             userCR.getRoles().add("Other");
275 
276             ProvisioningResult<UserTO> result = createUser(userCR);
277             assertEquals(1, result.getPropagationStatuses().size());
278             assertNotNull(result.getPropagationStatuses().get(0).getAfterObj());
279 
280             Attr businessCategory =
281                     result.getPropagationStatuses().get(0).getAfterObj().getAttr("businessCategory").orElse(null);
282             assertNotNull(businessCategory);
283             assertEquals(1, businessCategory.getValues().size());
284             assertEquals("postMighty", businessCategory.getValues().get(0));
285         } finally {
286             RESOURCE_SERVICE.delete(ldap.getKey());
287         }
288     }
289 
290     @Test
291     public void purgePropagations() {
292         try {
293             TASK_SERVICE.purgePropagations(null, null, null);
294             fail();
295         } catch (WebServiceException e) {
296             assertNotNull(e);
297         }
298 
299         OffsetDateTime oneWeekAgo = OffsetDateTime.now().minusWeeks(1);
300         Response response = TASK_SERVICE.purgePropagations(
301                 oneWeekAgo,
302                 List.of(ExecStatus.SUCCESS),
303                 List.of(RESOURCE_NAME_WS1));
304         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
305 
306         List<PropagationTaskTO> deleted = response.readEntity(new GenericType<List<PropagationTaskTO>>() {
307         });
308         assertNotNull(deleted);
309         // only ws-target-resource-1 PROPAGATION tasks should have been deleted
310         assertEquals(1, deleted.size());
311         assertTrue(deleted.stream().allMatch(d -> RESOURCE_NAME_WS1.equals(d.getResource())));
312         // check that other propagation tasks haven't been affected
313         assertFalse(TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION)
314                 .anyTypeKind(AnyTypeKind.USER)
315                 .page(0).size(10)
316                 .build()).getResult().isEmpty());
317         // delete all remaining SUCCESS tasks
318         response = TASK_SERVICE.purgePropagations(oneWeekAgo, List.of(ExecStatus.SUCCESS), List.of());
319         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
320 
321         deleted = response.readEntity(new GenericType<List<PropagationTaskTO>>() {
322         });
323         assertNotNull(deleted);
324     }
325 
326     @Test
327     public void propagationPolicyRetry() throws InterruptedException {
328         SyncopeClient.nullPriorityAsync(ANY_OBJECT_SERVICE, true);
329 
330         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
331         jdbcTemplate.execute("ALTER TABLE TESTPRINTER ADD COLUMN MAND_VALUE VARCHAR(1)");
332         jdbcTemplate.execute("UPDATE TESTPRINTER SET MAND_VALUE='C'");
333         jdbcTemplate.execute("ALTER TABLE TESTPRINTER ALTER COLUMN MAND_VALUE VARCHAR(1) NOT NULL");
334         try {
335             String entityKey = createAnyObject(AnyObjectITCase.getSample("propagationPolicy")).getEntity().getKey();
336 
337             Thread.sleep(1000);
338             jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
339 
340             PagedResult<PropagationTaskTO> propagations = await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).until(
341                     () -> TASK_SERVICE.search(
342                             new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_DBSCRIPTED).
343                                     anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(entityKey).build()),
344                     p -> p.getTotalCount() > 0);
345 
346             propagations.getResult().get(0).getExecutions().stream().
347                     anyMatch(e -> ExecStatus.FAILURE.name().equals(e.getStatus()));
348             propagations.getResult().get(0).getExecutions().stream().
349                     anyMatch(e -> ExecStatus.SUCCESS.name().equals(e.getStatus()));
350         } finally {
351             SyncopeClient.nullPriorityAsync(ANY_OBJECT_SERVICE, false);
352 
353             try {
354                 jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
355             } catch (DataAccessException e) {
356                 // ignore
357             }
358         }
359     }
360 
361     private static String propagationPolicyOptimizeKey() {
362         return POLICY_SERVICE.list(PolicyType.PROPAGATION).stream().
363                 filter(p -> "optimize".equals(p.getName())).
364                 findFirst().
365                 orElseGet(() -> {
366                     PropagationPolicyTO policy = new PropagationPolicyTO();
367                     policy.setName("optimize");
368                     policy.setFetchAroundProvisioning(false);
369                     policy.setUpdateDelta(true);
370                     return createPolicy(PolicyType.PROPAGATION, policy);
371                 }).getKey();
372     }
373 
374     @Test
375     public void propagationPolicyOptimizeToLDAP() {
376         String policyKey = propagationPolicyOptimizeKey();
377 
378         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
379         assertNull(ldap.getPropagationPolicy());
380 
381         ldap.setPropagationPolicy(policyKey);
382         RESOURCE_SERVICE.update(ldap);
383 
384         try {
385             // 0. create groups on LDAP
386             GroupTO group1 = createGroup(GroupITCase.getSample("propagationPolicyOptimizeToLDAP")).getEntity();
387             GroupTO group2 = createGroup(GroupITCase.getSample("propagationPolicyOptimizeToLDAP")).getEntity();
388 
389             // 1a. create user on LDAP and verify success
390             UserCR userCR = UserITCase.getUniqueSample("propagationPolicyOptimizeToLDAP@syncope.apache.org");
391             userCR.getAuxClasses().add("minimal group");
392             userCR.getPlainAttrs().add(attr("title", "title1"));
393             userCR.getMemberships().add(new MembershipTO.Builder(group1.getKey()).build());
394             ProvisioningResult<UserTO> created = createUser(userCR);
395             assertEquals(RESOURCE_NAME_LDAP, created.getPropagationStatuses().get(0).getResource());
396             assertEquals(ExecStatus.SUCCESS, created.getPropagationStatuses().get(0).getStatus());
397 
398             // 1b. read from LDAP the effective object
399             ReconStatus status = RECONCILIATION_SERVICE.status(
400                     new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).
401                             anyKey(created.getEntity().getKey()).moreAttrsToGet("ldapGroups").build());
402             assertEquals(List.of("title1"), status.getOnResource().getAttr("title").get().getValues());
403             assertEquals(
404                     List.of("cn=" + group1.getName() + ",ou=groups,o=isp"),
405                     status.getOnResource().getAttr("ldapGroups").get().getValues());
406 
407             // 1c. check the generated propagation data
408             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
409                     resource(RESOURCE_NAME_LDAP).
410                     anyTypeKind(AnyTypeKind.USER).entityKey(created.getEntity().getKey()).build());
411             assertEquals(1, tasks.getSize());
412 
413             PropagationData data = POJOHelper.deserialize(
414                     tasks.getResult().get(0).getPropagationData(), PropagationData.class);
415             assertNull(data.getAttributeDeltas());
416 
417             TASK_SERVICE.delete(TaskType.PROPAGATION, tasks.getResult().get(0).getKey());
418 
419             // 2a. update user on LDAP and verify success
420             UserUR userUR = new UserUR.Builder(created.getEntity().getKey()).plainAttr(new AttrPatch.Builder(
421                     new Attr.Builder("title").values("title1", "title2").build()).build()).
422                     membership(new MembershipUR.Builder(group2.getKey()).build()).
423                     build();
424             ProvisioningResult<UserTO> updated = updateUser(userUR);
425             assertEquals(RESOURCE_NAME_LDAP, updated.getPropagationStatuses().get(0).getResource());
426             assertEquals(ExecStatus.SUCCESS, updated.getPropagationStatuses().get(0).getStatus());
427 
428             // 2b. read from LDAP the effective object
429             status = RECONCILIATION_SERVICE.status(
430                     new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).
431                             anyKey(created.getEntity().getKey()).moreAttrsToGet("ldapGroups").build());
432             assertEquals(
433                     Set.of("title1", "title2"),
434                     new HashSet<>(status.getOnResource().getAttr("title").get().getValues()));
435             assertEquals(
436                     Set.of("cn=" + group1.getName() + ",ou=groups,o=isp",
437                             "cn=" + group2.getName() + ",ou=groups,o=isp"),
438                     new HashSet<>(status.getOnResource().getAttr("ldapGroups").get().getValues()));
439 
440             // 2c. check the generated propagation data
441             tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
442                     resource(RESOURCE_NAME_LDAP).
443                     anyTypeKind(AnyTypeKind.USER).entityKey(created.getEntity().getKey()).build());
444             assertEquals(1, tasks.getSize());
445 
446             data = POJOHelper.deserialize(tasks.getResult().get(0).getPropagationData(), PropagationData.class);
447             assertNotNull(data.getAttributeDeltas());
448 
449             TASK_SERVICE.delete(TaskType.PROPAGATION, tasks.getResult().get(0).getKey());
450         } finally {
451             ldap.setPropagationPolicy(null);
452             RESOURCE_SERVICE.update(ldap);
453         }
454     }
455 
456     @Test
457     public void propagationPolicyOptimizeToScriptedDB() {
458         String policyKey = propagationPolicyOptimizeKey();
459 
460         ResourceTO db = RESOURCE_SERVICE.read(RESOURCE_NAME_DBSCRIPTED);
461         String beforePolicyKey = db.getPropagationPolicy();
462         assertNotNull(beforePolicyKey);
463 
464         db.setPropagationPolicy(policyKey);
465 
466         // 0. create new schema and change resource mapping to include it
467         PlainSchemaTO paperformat = new PlainSchemaTO();
468         paperformat.setKey("paperformat");
469         paperformat.setMultivalue(true);
470         SCHEMA_SERVICE.create(SchemaType.PLAIN, paperformat);
471 
472         AnyTypeClassTO printer = ANY_TYPE_CLASS_SERVICE.read("minimal printer");
473         printer.getPlainSchemas().add(paperformat.getKey());
474         ANY_TYPE_CLASS_SERVICE.update(printer);
475 
476         Item paperformatItem = new Item();
477         paperformatItem.setPurpose(MappingPurpose.PROPAGATION);
478         paperformatItem.setIntAttrName("paperformat");
479         paperformatItem.setExtAttrName("paperformat");
480         db.getProvision(PRINTER).get().getMapping().add(paperformatItem);
481         RESOURCE_SERVICE.update(db);
482 
483         ProvisioningResult<AnyObjectTO> created = null;
484         try {
485             // 1a. create printer on db and verify success
486             created = createAnyObject(AnyObjectITCase.getSample("ppOptimizeToDB"));
487             assertEquals(RESOURCE_NAME_DBSCRIPTED, created.getPropagationStatuses().get(0).getResource());
488             assertEquals(ExecStatus.SUCCESS, created.getPropagationStatuses().get(0).getStatus());
489 
490             // 1b. check the generated propagation data
491             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
492                     resource(RESOURCE_NAME_DBSCRIPTED).
493                     anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(created.getEntity().getKey()).build());
494             assertEquals(1, tasks.getSize());
495 
496             PropagationData data = POJOHelper.deserialize(
497                     tasks.getResult().get(0).getPropagationData(), PropagationData.class);
498             assertNull(data.getAttributeDeltas());
499 
500             TASK_SERVICE.delete(TaskType.PROPAGATION, tasks.getResult().get(0).getKey());
501 
502             // 2a. update printer on db and verify success
503             AnyObjectUR req = new AnyObjectUR.Builder(created.getEntity().getKey()).plainAttr(new AttrPatch.Builder(
504                     new Attr.Builder("paperformat").values("format1", "format2").build()).build()).
505                     build();
506             ProvisioningResult<AnyObjectTO> updated = updateAnyObject(req);
507             assertEquals(RESOURCE_NAME_DBSCRIPTED, updated.getPropagationStatuses().get(0).getResource());
508             assertEquals(ExecStatus.SUCCESS, updated.getPropagationStatuses().get(0).getStatus());
509 
510             // 2b. read from db the effective object
511             JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
512             List<String> values = queryForList(jdbcTemplate,
513                     MAX_WAIT_SECONDS,
514                     "SELECT paper_format FROM testPRINTER_PAPERFORMAT WHERE printer_id=?",
515                     String.class,
516                     created.getEntity().getKey());
517             assertEquals(Set.of("format1", "format2"), new HashSet<>(values));
518 
519             // 2c. check the generated propagation data
520             tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
521                     resource(RESOURCE_NAME_DBSCRIPTED).
522                     anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(created.getEntity().getKey()).build());
523             assertEquals(1, tasks.getSize());
524 
525             data = POJOHelper.deserialize(tasks.getResult().get(0).getPropagationData(), PropagationData.class);
526             assertNotNull(data.getAttributeDeltas());
527 
528             TASK_SERVICE.delete(TaskType.PROPAGATION, tasks.getResult().get(0).getKey());
529 
530             // 3a. update printer on db and verify success
531             req = new AnyObjectUR.Builder(created.getEntity().getKey()).plainAttr(new AttrPatch.Builder(
532                     new Attr.Builder("paperformat").values("format1", "format3").build()).build()).
533                     build();
534             updated = updateAnyObject(req);
535             assertEquals(RESOURCE_NAME_DBSCRIPTED, updated.getPropagationStatuses().get(0).getResource());
536             assertEquals(ExecStatus.SUCCESS, updated.getPropagationStatuses().get(0).getStatus());
537 
538             // 3b. read from db the effective object
539             values = queryForList(jdbcTemplate,
540                     MAX_WAIT_SECONDS,
541                     "SELECT paper_format FROM testPRINTER_PAPERFORMAT WHERE printer_id=?",
542                     String.class,
543                     created.getEntity().getKey());
544             assertEquals(Set.of("format1", "format3"), new HashSet<>(values));
545 
546             // 3c. check the generated propagation data
547             tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
548                     resource(RESOURCE_NAME_DBSCRIPTED).
549                     anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(created.getEntity().getKey()).build());
550             assertEquals(1, tasks.getSize());
551 
552             data = POJOHelper.deserialize(tasks.getResult().get(0).getPropagationData(), PropagationData.class);
553             assertNotNull(data.getAttributeDeltas());
554 
555             TASK_SERVICE.delete(TaskType.PROPAGATION, tasks.getResult().get(0).getKey());
556         } finally {
557             Optional.ofNullable(created).map(c -> c.getEntity().getKey()).ifPresent(ANY_OBJECT_SERVICE::delete);
558 
559             SCHEMA_SERVICE.delete(SchemaType.PLAIN, "paperformat");
560 
561             db.setPropagationPolicy(beforePolicyKey);
562             db.getProvision(PRINTER).ifPresent(provision -> provision.getMapping().
563                     getItems().removeIf(item -> "paperformat".equals(item.getIntAttrName())));
564             RESOURCE_SERVICE.update(db);
565         }
566     }
567 
568     @Test
569     public void issueSYNCOPE741() {
570         for (int i = 0; i < 3; i++) {
571             TASK_SERVICE.execute(new ExecSpecs.Builder().key("1e697572-b896-484c-ae7f-0c8f63fcbc6c").build());
572             TASK_SERVICE.execute(new ExecSpecs.Builder().key("316285cc-ae52-4ea2-a33b-7355e189ac3f").build());
573         }
574         try {
575             Thread.sleep(3000);
576         } catch (InterruptedException e) {
577             // ignore
578         }
579 
580         // check list
581         PagedResult<TaskTO> tasks = TASK_SERVICE.search(
582                 new TaskQuery.Builder(TaskType.PROPAGATION).
583                         page(1).size(2).orderBy("operation DESC").details(false).build());
584         for (TaskTO item : tasks.getResult()) {
585             assertTrue(item.getExecutions().isEmpty());
586         }
587 
588         tasks = TASK_SERVICE.search(
589                 new TaskQuery.Builder(TaskType.PROPAGATION).
590                         page(1).size(2).orderBy("operation DESC").details(true).build());
591         for (TaskTO item : tasks.getResult()) {
592             assertFalse(item.getExecutions().isEmpty());
593         }
594 
595         // check read
596         PropagationTaskTO task = TASK_SERVICE.read(TaskType.PROPAGATION, "1e697572-b896-484c-ae7f-0c8f63fcbc6c", false);
597         assertNotNull(task);
598         assertEquals("1e697572-b896-484c-ae7f-0c8f63fcbc6c", task.getKey());
599         assertTrue(task.getExecutions().isEmpty());
600 
601         task = TASK_SERVICE.read(TaskType.PROPAGATION, "1e697572-b896-484c-ae7f-0c8f63fcbc6c", true);
602         assertNotNull(task);
603         assertEquals("1e697572-b896-484c-ae7f-0c8f63fcbc6c", task.getKey());
604         assertFalse(task.getExecutions().isEmpty());
605 
606         // check list executions
607         PagedResult<ExecTO> execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().
608                 key("1e697572-b896-484c-ae7f-0c8f63fcbc6c").
609                 before(OffsetDateTime.now().plusSeconds(30)).
610                 page(1).size(2).build());
611         assertTrue(execs.getTotalCount() >= execs.getResult().size());
612     }
613 
614     @Test
615     public void issueSYNCOPE1288() {
616         // create a new user
617         UserCR userCR = UserITCase.getUniqueSample("xxxyyy@xxx.xxx");
618         userCR.getResources().add(RESOURCE_NAME_LDAP);
619 
620         UserTO userTO = createUser(userCR).getEntity();
621         assertNotNull(userTO);
622 
623         // generate some PropagationTasks
624         for (int i = 0; i < 9; i++) {
625             UserUR userUR = new UserUR();
626             userUR.setKey(userTO.getKey());
627             userUR.getPlainAttrs().add(new AttrPatch.Builder(new Attr.Builder("userId").value(
628                     "test" + getUUIDString() + i + "@test.com").build()).
629                     operation(PatchOperation.ADD_REPLACE).
630                     build());
631 
632             USER_SERVICE.update(userUR);
633         }
634 
635         // ASC order
636         PagedResult<TaskTO> unorderedTasks = TASK_SERVICE.search(
637                 new TaskQuery.Builder(TaskType.PROPAGATION).
638                         resource(RESOURCE_NAME_LDAP).
639                         entityKey(userTO.getKey()).
640                         anyTypeKind(AnyTypeKind.USER).
641                         page(1).
642                         size(10).
643                         build());
644         Collections.sort(unorderedTasks.getResult(), (t1, t2) -> t1.getStart().compareTo(t2.getStart()));
645         assertNotNull(unorderedTasks);
646         assertFalse(unorderedTasks.getResult().isEmpty());
647         assertEquals(10, unorderedTasks.getResult().size());
648 
649         PagedResult<TaskTO> orderedTasks = TASK_SERVICE.search(
650                 new TaskQuery.Builder(TaskType.PROPAGATION).
651                         resource(RESOURCE_NAME_LDAP).
652                         entityKey(userTO.getKey()).
653                         anyTypeKind(AnyTypeKind.USER).
654                         page(1).
655                         size(10).
656                         orderBy("start").
657                         build());
658         assertNotNull(orderedTasks);
659         assertFalse(orderedTasks.getResult().isEmpty());
660         assertEquals(10, orderedTasks.getResult().size());
661 
662         assertTrue(orderedTasks.getResult().equals(unorderedTasks.getResult()));
663 
664         // DESC order
665         Collections.reverse(unorderedTasks.getResult());
666         orderedTasks = TASK_SERVICE.search(
667                 new TaskQuery.Builder(TaskType.PROPAGATION).
668                         resource(RESOURCE_NAME_LDAP).
669                         entityKey(userTO.getKey()).
670                         anyTypeKind(AnyTypeKind.USER).
671                         page(1).
672                         size(10).
673                         orderBy("start DESC").
674                         build());
675 
676         assertTrue(orderedTasks.getResult().equals(unorderedTasks.getResult()));
677     }
678 
679     @Test
680     public void issueSYNCOPE1430() throws ParseException {
681         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
682         try {
683             // 1. clone the LDAP resource and add some sensible mappings
684             Provision provision = ldap.getProvision(AnyTypeKind.USER.name()).orElse(null);
685             assertNotNull(provision);
686             provision.getMapping().getItems().removeIf(item -> "mail".equals(item.getExtAttrName()));
687             provision.getVirSchemas().clear();
688 
689             // Date -> long (JEXL expression) -> string
690             Item loginDateForJexlAsLong = new Item();
691             loginDateForJexlAsLong.setPurpose(MappingPurpose.PROPAGATION);
692             loginDateForJexlAsLong.setIntAttrName("loginDate");
693             loginDateForJexlAsLong.setExtAttrName("employeeNumber");
694             loginDateForJexlAsLong.setPropagationJEXLTransformer("value.toInstant().toEpochMilli()");
695             provision.getMapping().add(loginDateForJexlAsLong);
696 
697             // Date -> string (JEXL expression)
698             Item loginDateForJexlAsString = new Item();
699             loginDateForJexlAsString.setPurpose(MappingPurpose.PROPAGATION);
700             loginDateForJexlAsString.setIntAttrName("loginDate");
701             loginDateForJexlAsString.setExtAttrName("street");
702             loginDateForJexlAsString.setPropagationJEXLTransformer(
703                     "value.toInstant().toString().split(\"T\")[0].replace(\"-\", \"\")");
704             provision.getMapping().add(loginDateForJexlAsString);
705 
706             // Date -> long
707             Item loginDateForJavaToLong = new Item();
708             loginDateForJavaToLong.setPurpose(MappingPurpose.PROPAGATION);
709             loginDateForJavaToLong.setIntAttrName("loginDate");
710             loginDateForJavaToLong.setExtAttrName("st");
711             loginDateForJavaToLong.getTransformers().add(DateToLongItemTransformer.class.getSimpleName());
712             provision.getMapping().add(loginDateForJavaToLong);
713 
714             // Date -> date
715             Item loginDateForJavaToDate = new Item();
716             loginDateForJavaToDate.setPurpose(MappingPurpose.PROPAGATION);
717             loginDateForJavaToDate.setIntAttrName("loginDate");
718             loginDateForJavaToDate.setExtAttrName("carLicense");
719             loginDateForJavaToDate.getTransformers().add(DateToDateItemTransformer.class.getSimpleName());
720             provision.getMapping().add(loginDateForJavaToDate);
721 
722             ldap.getProvisions().clear();
723             ldap.getProvisions().add(provision);
724             ldap.setKey(RESOURCE_NAME_LDAP + "1430" + getUUIDString());
725             RESOURCE_SERVICE.create(ldap);
726 
727             // 2. create user with the new resource assigned
728             UserCR createReq = UserITCase.getUniqueSample("syncope1430@syncope.apache.org");
729             createReq.getResources().clear();
730             createReq.getResources().add(ldap.getKey());
731             createReq.getPlainAttrs().removeIf(attr -> "loginDate".equals(attr.getSchema()));
732             createReq.getPlainAttrs().add(attr("loginDate", "2019-01-29"));
733             UserTO user = createUser(createReq).getEntity();
734 
735             // 3. check attributes prepared for propagation
736             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
737                     resource(user.getResources().iterator().next()).
738                     anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
739             assertEquals(1, tasks.getSize());
740 
741             Set<Attribute> propagationAttrs = new HashSet<>();
742             if (StringUtils.isNotBlank(tasks.getResult().get(0).getPropagationData())) {
743                 propagationAttrs.addAll(POJOHelper.deserialize(
744                         tasks.getResult().get(0).getPropagationData(), PropagationData.class).getAttributes());
745             }
746 
747             OffsetDateTime loginDate = LocalDate.parse(user.getPlainAttr("loginDate").get().getValues().get(0)).
748                     atStartOfDay(ZoneOffset.UTC).toOffsetDateTime();
749 
750             Attribute employeeNumber = AttributeUtil.find("employeeNumber", propagationAttrs);
751             assertNotNull(employeeNumber);
752             assertEquals(loginDate.toInstant().toEpochMilli(), employeeNumber.getValue().get(0));
753 
754             Attribute street = AttributeUtil.find("street", propagationAttrs);
755             assertNotNull(street);
756             assertEquals(loginDate.toInstant().toString().split("T")[0].replace("-", ""), street.getValue().get(0));
757 
758             Attribute st = AttributeUtil.find("st", propagationAttrs);
759             assertNotNull(st);
760             assertEquals(loginDate.toInstant().toEpochMilli(), st.getValue().get(0));
761 
762             Attribute carLicense = AttributeUtil.find("carLicense", propagationAttrs);
763             assertNotNull(carLicense);
764             assertEquals(DateTimeFormatter.ISO_LOCAL_DATE.format(loginDate.plusDays(1)), carLicense.getValue().get(0));
765         } finally {
766             try {
767                 RESOURCE_SERVICE.delete(ldap.getKey());
768             } catch (Exception ignore) {
769                 // ignore
770             }
771         }
772     }
773 
774     @Test
775     public void issueSYNCOPE1473() throws ParseException {
776         // create a new group schema
777         PlainSchemaTO schemaTO = new PlainSchemaTO();
778         schemaTO.setKey("ldapGroups" + getUUIDString());
779         schemaTO.setType(AttrSchemaType.String);
780         schemaTO.setMultivalue(true);
781         schemaTO.setReadonly(true);
782         schemaTO.setAnyTypeClass("minimal user");
783 
784         schemaTO = createSchema(SchemaType.PLAIN, schemaTO);
785         assertNotNull(schemaTO);
786 
787         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
788         UserTO userTO = null;
789         try {
790             // 1. clone the LDAP resource and add some sensible mappings
791             Provision provisionGroup =
792                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.GROUP.name()).orElse(null));
793             assertNotNull(provisionGroup);
794             provisionGroup.getVirSchemas().clear();
795 
796             Provision provisionUser =
797                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.USER.name()).orElse(null));
798             assertNotNull(provisionUser);
799             provisionUser.getMapping().getItems().removeIf(item -> "mail".equals(item.getExtAttrName()));
800             provisionUser.getVirSchemas().clear();
801 
802             Item ldapGroups = new Item();
803             ldapGroups.setPurpose(MappingPurpose.PROPAGATION);
804             ldapGroups.setIntAttrName(schemaTO.getKey());
805             ldapGroups.setExtAttrName("ldapGroups");
806             provisionUser.getMapping().add(ldapGroups);
807 
808             ldap.getProvisions().clear();
809             ldap.getProvisions().add(provisionUser);
810             ldap.getProvisions().add(provisionGroup);
811             ldap.setKey(RESOURCE_NAME_LDAP + "1473" + getUUIDString());
812             RESOURCE_SERVICE.create(ldap);
813 
814             // 1. create group with the new resource assigned
815             GroupCR groupCR = new GroupCR();
816             groupCR.setName("SYNCOPEGROUP1473-" + getUUIDString());
817             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
818             groupCR.getResources().add(ldap.getKey());
819 
820             GroupTO groupTO = createGroup(groupCR).getEntity();
821             assertNotNull(groupCR);
822 
823             // 2. create user with the new resource assigned
824             UserCR userCR = UserITCase.getUniqueSample("syncope1473@syncope.apache.org");
825             userCR.getResources().clear();
826             userCR.getResources().add(ldap.getKey());
827             userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
828 
829             userTO = createUser(userCR).getEntity();
830             assertNotNull(userTO);
831 
832             // 3. check attributes prepared for propagation
833             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
834                     resource(userTO.getResources().iterator().next()).
835                     anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
836             assertEquals(1, tasks.getSize());
837 
838             ResourceDR resourceDR = new ResourceDR.Builder().key(groupTO.getKey()).
839                     action(ResourceDeassociationAction.UNLINK).resource(ldap.getKey()).build();
840 
841             GROUP_SERVICE.deassociate(resourceDR);
842             GROUP_SERVICE.delete(groupTO.getKey());
843 
844             GroupCR newGroupCR = new GroupCR();
845             newGroupCR.setName("NEWSYNCOPEGROUP1473-" + getUUIDString());
846             newGroupCR.setRealm(SyncopeConstants.ROOT_REALM);
847             newGroupCR.getResources().add(ldap.getKey());
848 
849             GroupTO newGroupTO = createGroup(newGroupCR).getEntity();
850             assertNotNull(newGroupTO);
851 
852             UserUR userUR = new UserUR();
853             userUR.setKey(userTO.getKey());
854             userUR.getMemberships().add(
855                     new MembershipUR.Builder(newGroupTO.getKey()).operation(PatchOperation.ADD_REPLACE).build());
856             USER_SERVICE.update(userUR);
857 
858             ConnObject connObject =
859                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey());
860             assertNotNull(connObject);
861             assertTrue(connObject.getAttr("ldapGroups").isPresent());
862             assertEquals(2, connObject.getAttr("ldapGroups").get().getValues().size());
863         } finally {
864             try {
865                 RESOURCE_SERVICE.delete(ldap.getKey());
866                 if (userTO != null) {
867                     USER_SERVICE.delete(userTO.getKey());
868                 }
869                 SCHEMA_SERVICE.delete(SchemaType.PLAIN, schemaTO.getKey());
870             } catch (Exception ignore) {
871                 // ignore
872             }
873         }
874     }
875 
876     @Test
877     public void issueSYNCOPE1567() {
878         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
879         try {
880             // 1. clone the LDAP resource and add the relationships mapping
881             Provision provisionUser =
882                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.USER.name()).orElse(null));
883             assertNotNull(provisionUser);
884             provisionUser.getVirSchemas().clear();
885 
886             Item relationships = new Item();
887             relationships.setPurpose(MappingPurpose.PROPAGATION);
888             relationships.setIntAttrName("relationships[neighborhood][PRINTER].model");
889             relationships.setExtAttrName("l");
890             provisionUser.getMapping().add(relationships);
891 
892             ldap.getProvisions().clear();
893             ldap.getProvisions().add(provisionUser);
894             ldap.setKey(RESOURCE_NAME_LDAP + "1567" + getUUIDString());
895             RESOURCE_SERVICE.create(ldap);
896 
897             // 1. create user with relationship and the new resource assigned
898             UserCR userCR = UserITCase.getUniqueSample("syncope1567@syncope.apache.org");
899             userCR.getRelationships().add(new RelationshipTO.Builder("neighborhood").
900                     otherEnd(PRINTER, "fc6dbc3a-6c07-4965-8781-921e7401a4a5").build());
901             userCR.getResources().clear();
902             userCR.getResources().add(ldap.getKey());
903 
904             UserTO userTO = createUser(userCR).getEntity();
905             assertNotNull(userTO);
906             assertFalse(userTO.getRelationships().isEmpty());
907 
908             // 2. check attributes prepared for propagation
909             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
910                     resource(userCR.getResources().iterator().next()).
911                     anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
912             assertEquals(1, tasks.getSize());
913 
914             Set<Attribute> propagationAttrs = POJOHelper.deserialize(
915                     tasks.getResult().get(0).getPropagationData(), PropagationData.class).getAttributes();
916             Attribute attr = AttributeUtil.find("l", propagationAttrs);
917             assertNotNull(attr);
918             assertNotNull(attr.getValue());
919             assertEquals("Canon MFC8030", attr.getValue().get(0).toString());
920 
921             // 3. check propagated value
922             ConnObject connObject =
923                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey());
924             assertNotNull(connObject);
925             assertTrue(connObject.getAttr("l").isPresent());
926             assertEquals("Canon MFC8030", connObject.getAttr("l").get().getValues().get(0));
927         } finally {
928             try {
929                 RESOURCE_SERVICE.delete(ldap.getKey());
930             } catch (Exception ignore) {
931                 // ignore
932             }
933         }
934     }
935 
936     @Test
937     public void issueSYNCOPE1605() throws ParseException {
938         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
939         try {
940             // 1. clone the LDAP resource and add some sensible mappings
941             Provision provisionGroup =
942                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.GROUP.name()).orElse(null));
943             assertNotNull(provisionGroup);
944             provisionGroup.getVirSchemas().clear();
945             provisionGroup.getMapping().getItems().clear();
946 
947             Item item = new Item();
948             item.setConnObjectKey(true);
949             item.setIntAttrName("name");
950             item.setExtAttrName("description");
951             item.setPurpose(MappingPurpose.BOTH);
952 
953             provisionGroup.getMapping().setConnObjectKeyItem(item);
954             provisionGroup.getMapping().setConnObjectLink("'cn=' + originalName + ',ou=groups,o=isp'");
955 
956             ldap.getProvisions().clear();
957             ldap.getProvisions().add(provisionGroup);
958 
959             ldap.setKey(RESOURCE_NAME_LDAP + "1605" + getUUIDString());
960             RESOURCE_SERVICE.create(ldap);
961 
962             // 1. create group with the new resource assigned
963             String originalName = "grp1605-" + getUUIDString();
964 
965             GroupCR groupCR = new GroupCR();
966             groupCR.setName("SYNCOPEGROUP1605-" + getUUIDString());
967             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
968             groupCR.getResources().add(ldap.getKey());
969             groupCR.getPlainAttrs().add(new Attr.Builder("originalName").value(originalName).build());
970 
971             GroupTO groupTO = createGroup(groupCR).getEntity();
972             assertNotNull(groupTO);
973 
974             // 3. check attributes prepared for propagation
975             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
976                     resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).build());
977             assertEquals(1, tasks.getSize());
978             assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
979             assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
980 
981             ConnObject beforeConnObject =
982                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.GROUP.name(), groupTO.getKey());
983 
984             GroupUR groupUR = new GroupUR();
985             groupUR.setKey(groupTO.getKey());
986 
987             groupUR.getPlainAttrs().add(attrAddReplacePatch("originalName", "new" + originalName));
988             groupTO = updateGroup(groupUR).getEntity();
989 
990             tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
991                     resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).
992                     orderBy("start DESC").build());
993             assertEquals(2, tasks.getSize());
994             assertEquals(ResourceOperation.UPDATE, tasks.getResult().get(0).getOperation());
995             assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
996 
997             ConnObject afterConnObject =
998                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.GROUP.name(), groupTO.getKey());
999             assertNotEquals(afterConnObject.getAttr(Name.NAME).get().getValues().get(0),
1000                     beforeConnObject.getAttr(Name.NAME).get().getValues().get(0));
1001             assertTrue(afterConnObject.getAttr(Name.NAME).get().getValues().get(0).contains("new" + originalName));
1002         } finally {
1003             try {
1004                 RESOURCE_SERVICE.delete(ldap.getKey());
1005             } catch (Exception ignore) {
1006                 // ignore
1007             }
1008         }
1009     }
1010 
1011     @Test
1012     public void issueSYNCOPE1751() {
1013         // 1. Create a Group with a resource assigned
1014         GroupTO groupTO = createGroup(
1015                 new GroupCR.Builder(SyncopeConstants.ROOT_REALM, "SYNCOPEGROUP1751-" + getUUIDString()).
1016                         resource(RESOURCE_NAME_LDAP).build()).getEntity();
1017         // 2. Create a user
1018         String username = "SYNCOPEUSER1751" + getUUIDString();
1019         UserTO userTO = createUser(
1020                 new UserCR.Builder(SyncopeConstants.ROOT_REALM, username).plainAttrs(
1021                         new Attr.Builder("userId").value(username + "@syncope.org").build(),
1022                         new Attr.Builder("fullname").value(username).build(),
1023                         new Attr.Builder("surname").value(username).build()).
1024                         build()).getEntity();
1025         // 3. Update the user assigning the group previously created -> group-based provisioning
1026         userTO = updateUser(
1027                 new UserUR.Builder(userTO.getKey()).
1028                         membership(new MembershipUR.Builder(groupTO.getKey()).build()).
1029                         build()).getEntity();
1030         // since the resource is flagged to generate random pwd must populate the password on effective create on the
1031         // resource, even if it is an update on Syncope
1032         PagedResult<TaskTO> propTasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
1033                 resource(RESOURCE_NAME_LDAP).anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
1034         assertFalse(propTasks.getResult().isEmpty());
1035         assertEquals(1, propTasks.getSize());
1036         PropagationData propagationData = POJOHelper.deserialize(
1037                 PropagationTaskTO.class.cast(propTasks.getResult().get(0)).getPropagationData(),
1038                 PropagationData.class);
1039         assertTrue(propagationData.getAttributes().stream().
1040                 anyMatch(a -> OperationalAttributes.PASSWORD_NAME.equals(a.getName())));
1041     }
1042 }