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().
572                     key("1e697572-b896-484c-ae7f-0c8f63fcbc6c").build());
573             TASK_SERVICE.execute(new ExecSpecs.Builder().
574                     key("316285cc-ae52-4ea2-a33b-7355e189ac3f").build());
575         }
576         try {
577             Thread.sleep(3000);
578         } catch (InterruptedException e) {
579             // ignore
580         }
581 
582         // check list
583         PagedResult<TaskTO> tasks = TASK_SERVICE.search(
584                 new TaskQuery.Builder(TaskType.PROPAGATION).
585                         page(1).size(2).orderBy("operation DESC").details(false).build());
586         for (TaskTO item : tasks.getResult()) {
587             assertTrue(item.getExecutions().isEmpty());
588         }
589 
590         tasks = TASK_SERVICE.search(
591                 new TaskQuery.Builder(TaskType.PROPAGATION).
592                         page(1).size(2).orderBy("operation DESC").details(true).build());
593         for (TaskTO item : tasks.getResult()) {
594             assertFalse(item.getExecutions().isEmpty());
595         }
596 
597         // check read
598         PropagationTaskTO task = TASK_SERVICE.read(TaskType.PROPAGATION, "1e697572-b896-484c-ae7f-0c8f63fcbc6c", false);
599         assertNotNull(task);
600         assertEquals("1e697572-b896-484c-ae7f-0c8f63fcbc6c", task.getKey());
601         assertTrue(task.getExecutions().isEmpty());
602 
603         task = TASK_SERVICE.read(TaskType.PROPAGATION, "1e697572-b896-484c-ae7f-0c8f63fcbc6c", true);
604         assertNotNull(task);
605         assertEquals("1e697572-b896-484c-ae7f-0c8f63fcbc6c", task.getKey());
606         assertFalse(task.getExecutions().isEmpty());
607 
608         // check list executions
609         PagedResult<ExecTO> execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().
610                 key("1e697572-b896-484c-ae7f-0c8f63fcbc6c").
611                 before(OffsetDateTime.now().plusSeconds(30)).
612                 page(1).size(2).build());
613         assertTrue(execs.getTotalCount() >= execs.getResult().size());
614     }
615 
616     @Test
617     public void issueSYNCOPE1288() {
618         // create a new user
619         UserCR userCR = UserITCase.getUniqueSample("xxxyyy@xxx.xxx");
620         userCR.getResources().add(RESOURCE_NAME_LDAP);
621 
622         UserTO userTO = createUser(userCR).getEntity();
623         assertNotNull(userTO);
624 
625         // generate some PropagationTasks
626         for (int i = 0; i < 9; i++) {
627             UserUR userUR = new UserUR();
628             userUR.setKey(userTO.getKey());
629             userUR.getPlainAttrs().add(new AttrPatch.Builder(new Attr.Builder("userId").value(
630                     "test" + getUUIDString() + i + "@test.com").build()).
631                     operation(PatchOperation.ADD_REPLACE).
632                     build());
633 
634             USER_SERVICE.update(userUR);
635         }
636 
637         // ASC order
638         PagedResult<TaskTO> unorderedTasks = TASK_SERVICE.search(
639                 new TaskQuery.Builder(TaskType.PROPAGATION).
640                         resource(RESOURCE_NAME_LDAP).
641                         entityKey(userTO.getKey()).
642                         anyTypeKind(AnyTypeKind.USER).
643                         page(1).
644                         size(10).
645                         build());
646         Collections.sort(unorderedTasks.getResult(), (t1, t2) -> t1.getStart().compareTo(t2.getStart()));
647         assertNotNull(unorderedTasks);
648         assertFalse(unorderedTasks.getResult().isEmpty());
649         assertEquals(10, unorderedTasks.getResult().size());
650 
651         PagedResult<TaskTO> orderedTasks = TASK_SERVICE.search(
652                 new TaskQuery.Builder(TaskType.PROPAGATION).
653                         resource(RESOURCE_NAME_LDAP).
654                         entityKey(userTO.getKey()).
655                         anyTypeKind(AnyTypeKind.USER).
656                         page(1).
657                         size(10).
658                         orderBy("start").
659                         build());
660         assertNotNull(orderedTasks);
661         assertFalse(orderedTasks.getResult().isEmpty());
662         assertEquals(10, orderedTasks.getResult().size());
663 
664         assertTrue(orderedTasks.getResult().equals(unorderedTasks.getResult()));
665 
666         // DESC order
667         Collections.reverse(unorderedTasks.getResult());
668         orderedTasks = TASK_SERVICE.search(
669                 new TaskQuery.Builder(TaskType.PROPAGATION).
670                         resource(RESOURCE_NAME_LDAP).
671                         entityKey(userTO.getKey()).
672                         anyTypeKind(AnyTypeKind.USER).
673                         page(1).
674                         size(10).
675                         orderBy("start DESC").
676                         build());
677 
678         assertTrue(orderedTasks.getResult().equals(unorderedTasks.getResult()));
679     }
680 
681     @Test
682     public void issueSYNCOPE1430() throws ParseException {
683         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
684         try {
685             // 1. clone the LDAP resource and add some sensible mappings
686             Provision provision = ldap.getProvision(AnyTypeKind.USER.name()).orElse(null);
687             assertNotNull(provision);
688             provision.getMapping().getItems().removeIf(item -> "mail".equals(item.getExtAttrName()));
689             provision.getVirSchemas().clear();
690 
691             // Date -> long (JEXL expression) -> string
692             Item loginDateForJexlAsLong = new Item();
693             loginDateForJexlAsLong.setPurpose(MappingPurpose.PROPAGATION);
694             loginDateForJexlAsLong.setIntAttrName("loginDate");
695             loginDateForJexlAsLong.setExtAttrName("employeeNumber");
696             loginDateForJexlAsLong.setPropagationJEXLTransformer("value.toInstant().toEpochMilli()");
697             provision.getMapping().add(loginDateForJexlAsLong);
698 
699             // Date -> string (JEXL expression)
700             Item loginDateForJexlAsString = new Item();
701             loginDateForJexlAsString.setPurpose(MappingPurpose.PROPAGATION);
702             loginDateForJexlAsString.setIntAttrName("loginDate");
703             loginDateForJexlAsString.setExtAttrName("street");
704             loginDateForJexlAsString.setPropagationJEXLTransformer(
705                     "value.toInstant().toString().split(\"T\")[0].replace(\"-\", \"\")");
706             provision.getMapping().add(loginDateForJexlAsString);
707 
708             // Date -> long
709             Item loginDateForJavaToLong = new Item();
710             loginDateForJavaToLong.setPurpose(MappingPurpose.PROPAGATION);
711             loginDateForJavaToLong.setIntAttrName("loginDate");
712             loginDateForJavaToLong.setExtAttrName("st");
713             loginDateForJavaToLong.getTransformers().add(DateToLongItemTransformer.class.getSimpleName());
714             provision.getMapping().add(loginDateForJavaToLong);
715 
716             // Date -> date
717             Item loginDateForJavaToDate = new Item();
718             loginDateForJavaToDate.setPurpose(MappingPurpose.PROPAGATION);
719             loginDateForJavaToDate.setIntAttrName("loginDate");
720             loginDateForJavaToDate.setExtAttrName("carLicense");
721             loginDateForJavaToDate.getTransformers().add(DateToDateItemTransformer.class.getSimpleName());
722             provision.getMapping().add(loginDateForJavaToDate);
723 
724             ldap.getProvisions().clear();
725             ldap.getProvisions().add(provision);
726             ldap.setKey(RESOURCE_NAME_LDAP + "1430" + getUUIDString());
727             RESOURCE_SERVICE.create(ldap);
728 
729             // 2. create user with the new resource assigned
730             UserCR createReq = UserITCase.getUniqueSample("syncope1430@syncope.apache.org");
731             createReq.getResources().clear();
732             createReq.getResources().add(ldap.getKey());
733             createReq.getPlainAttrs().removeIf(attr -> "loginDate".equals(attr.getSchema()));
734             createReq.getPlainAttrs().add(attr("loginDate", "2019-01-29"));
735             UserTO user = createUser(createReq).getEntity();
736 
737             // 3. check attributes prepared for propagation
738             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
739                     resource(user.getResources().iterator().next()).
740                     anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
741             assertEquals(1, tasks.getSize());
742 
743             Set<Attribute> propagationAttrs = new HashSet<>();
744             if (StringUtils.isNotBlank(tasks.getResult().get(0).getPropagationData())) {
745                 propagationAttrs.addAll(POJOHelper.deserialize(
746                         tasks.getResult().get(0).getPropagationData(), PropagationData.class).getAttributes());
747             }
748 
749             OffsetDateTime loginDate = LocalDate.parse(user.getPlainAttr("loginDate").get().getValues().get(0)).
750                     atStartOfDay(ZoneOffset.UTC).toOffsetDateTime();
751 
752             Attribute employeeNumber = AttributeUtil.find("employeeNumber", propagationAttrs);
753             assertNotNull(employeeNumber);
754             assertEquals(loginDate.toInstant().toEpochMilli(), employeeNumber.getValue().get(0));
755 
756             Attribute street = AttributeUtil.find("street", propagationAttrs);
757             assertNotNull(street);
758             assertEquals(loginDate.toInstant().toString().split("T")[0].replace("-", ""), street.getValue().get(0));
759 
760             Attribute st = AttributeUtil.find("st", propagationAttrs);
761             assertNotNull(st);
762             assertEquals(loginDate.toInstant().toEpochMilli(), st.getValue().get(0));
763 
764             Attribute carLicense = AttributeUtil.find("carLicense", propagationAttrs);
765             assertNotNull(carLicense);
766             assertEquals(DateTimeFormatter.ISO_LOCAL_DATE.format(loginDate.plusDays(1)), carLicense.getValue().get(0));
767         } finally {
768             try {
769                 RESOURCE_SERVICE.delete(ldap.getKey());
770             } catch (Exception ignore) {
771                 // ignore
772             }
773         }
774     }
775 
776     @Test
777     public void issueSYNCOPE1473() throws ParseException {
778         // create a new group schema
779         PlainSchemaTO schemaTO = new PlainSchemaTO();
780         schemaTO.setKey("ldapGroups" + getUUIDString());
781         schemaTO.setType(AttrSchemaType.String);
782         schemaTO.setMultivalue(true);
783         schemaTO.setReadonly(true);
784         schemaTO.setAnyTypeClass("minimal user");
785 
786         schemaTO = createSchema(SchemaType.PLAIN, schemaTO);
787         assertNotNull(schemaTO);
788 
789         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
790         UserTO userTO = null;
791         try {
792             // 1. clone the LDAP resource and add some sensible mappings
793             Provision provisionGroup =
794                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.GROUP.name()).orElse(null));
795             assertNotNull(provisionGroup);
796             provisionGroup.getVirSchemas().clear();
797 
798             Provision provisionUser =
799                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.USER.name()).orElse(null));
800             assertNotNull(provisionUser);
801             provisionUser.getMapping().getItems().removeIf(item -> "mail".equals(item.getExtAttrName()));
802             provisionUser.getVirSchemas().clear();
803 
804             Item ldapGroups = new Item();
805             ldapGroups.setPurpose(MappingPurpose.PROPAGATION);
806             ldapGroups.setIntAttrName(schemaTO.getKey());
807             ldapGroups.setExtAttrName("ldapGroups");
808             provisionUser.getMapping().add(ldapGroups);
809 
810             ldap.getProvisions().clear();
811             ldap.getProvisions().add(provisionUser);
812             ldap.getProvisions().add(provisionGroup);
813             ldap.setKey(RESOURCE_NAME_LDAP + "1473" + getUUIDString());
814             RESOURCE_SERVICE.create(ldap);
815 
816             // 1. create group with the new resource assigned
817             GroupCR groupCR = new GroupCR();
818             groupCR.setName("SYNCOPEGROUP1473-" + getUUIDString());
819             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
820             groupCR.getResources().add(ldap.getKey());
821 
822             GroupTO groupTO = createGroup(groupCR).getEntity();
823             assertNotNull(groupCR);
824 
825             // 2. create user with the new resource assigned
826             UserCR userCR = UserITCase.getUniqueSample("syncope1473@syncope.apache.org");
827             userCR.getResources().clear();
828             userCR.getResources().add(ldap.getKey());
829             userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
830 
831             userTO = createUser(userCR).getEntity();
832             assertNotNull(userTO);
833 
834             // 3. check attributes prepared for propagation
835             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
836                     resource(userTO.getResources().iterator().next()).
837                     anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
838             assertEquals(1, tasks.getSize());
839 
840             ResourceDR resourceDR = new ResourceDR.Builder().key(groupTO.getKey()).
841                     action(ResourceDeassociationAction.UNLINK).resource(ldap.getKey()).build();
842 
843             GROUP_SERVICE.deassociate(resourceDR);
844             GROUP_SERVICE.delete(groupTO.getKey());
845 
846             GroupCR newGroupCR = new GroupCR();
847             newGroupCR.setName("NEWSYNCOPEGROUP1473-" + getUUIDString());
848             newGroupCR.setRealm(SyncopeConstants.ROOT_REALM);
849             newGroupCR.getResources().add(ldap.getKey());
850 
851             GroupTO newGroupTO = createGroup(newGroupCR).getEntity();
852             assertNotNull(newGroupTO);
853 
854             UserUR userUR = new UserUR();
855             userUR.setKey(userTO.getKey());
856             userUR.getMemberships().add(
857                     new MembershipUR.Builder(newGroupTO.getKey()).operation(PatchOperation.ADD_REPLACE).build());
858             USER_SERVICE.update(userUR);
859 
860             ConnObject connObject =
861                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey());
862             assertNotNull(connObject);
863             assertTrue(connObject.getAttr("ldapGroups").isPresent());
864             assertEquals(2, connObject.getAttr("ldapGroups").get().getValues().size());
865         } finally {
866             try {
867                 RESOURCE_SERVICE.delete(ldap.getKey());
868                 if (userTO != null) {
869                     USER_SERVICE.delete(userTO.getKey());
870                 }
871                 SCHEMA_SERVICE.delete(SchemaType.PLAIN, schemaTO.getKey());
872             } catch (Exception ignore) {
873                 // ignore
874             }
875         }
876     }
877 
878     @Test
879     public void issueSYNCOPE1567() {
880         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
881         try {
882             // 1. clone the LDAP resource and add the relationships mapping
883             Provision provisionUser =
884                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.USER.name()).orElse(null));
885             assertNotNull(provisionUser);
886             provisionUser.getVirSchemas().clear();
887 
888             Item relationships = new Item();
889             relationships.setPurpose(MappingPurpose.PROPAGATION);
890             relationships.setIntAttrName("relationships[neighborhood][PRINTER].model");
891             relationships.setExtAttrName("l");
892             provisionUser.getMapping().add(relationships);
893 
894             ldap.getProvisions().clear();
895             ldap.getProvisions().add(provisionUser);
896             ldap.setKey(RESOURCE_NAME_LDAP + "1567" + getUUIDString());
897             RESOURCE_SERVICE.create(ldap);
898 
899             // 1. create user with relationship and the new resource assigned
900             UserCR userCR = UserITCase.getUniqueSample("syncope1567@syncope.apache.org");
901             userCR.getRelationships().add(new RelationshipTO.Builder("neighborhood").
902                     otherEnd(PRINTER, "fc6dbc3a-6c07-4965-8781-921e7401a4a5").build());
903             userCR.getResources().clear();
904             userCR.getResources().add(ldap.getKey());
905 
906             UserTO userTO = createUser(userCR).getEntity();
907             assertNotNull(userTO);
908             assertFalse(userTO.getRelationships().isEmpty());
909 
910             // 2. check attributes prepared for propagation
911             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
912                     resource(userCR.getResources().iterator().next()).
913                     anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
914             assertEquals(1, tasks.getSize());
915 
916             Set<Attribute> propagationAttrs = POJOHelper.deserialize(
917                     tasks.getResult().get(0).getPropagationData(), PropagationData.class).getAttributes();
918             Attribute attr = AttributeUtil.find("l", propagationAttrs);
919             assertNotNull(attr);
920             assertNotNull(attr.getValue());
921             assertEquals("Canon MFC8030", attr.getValue().get(0).toString());
922 
923             // 3. check propagated value
924             ConnObject connObject =
925                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.USER.name(), userTO.getKey());
926             assertNotNull(connObject);
927             assertTrue(connObject.getAttr("l").isPresent());
928             assertEquals("Canon MFC8030", connObject.getAttr("l").get().getValues().get(0));
929         } finally {
930             try {
931                 RESOURCE_SERVICE.delete(ldap.getKey());
932             } catch (Exception ignore) {
933                 // ignore
934             }
935         }
936     }
937 
938     @Test
939     public void issueSYNCOPE1605() throws ParseException {
940         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
941         try {
942             // 1. clone the LDAP resource and add some sensible mappings
943             Provision provisionGroup =
944                     SerializationUtils.clone(ldap.getProvision(AnyTypeKind.GROUP.name()).orElse(null));
945             assertNotNull(provisionGroup);
946             provisionGroup.getVirSchemas().clear();
947             provisionGroup.getMapping().getItems().clear();
948 
949             Item item = new Item();
950             item.setConnObjectKey(true);
951             item.setIntAttrName("name");
952             item.setExtAttrName("description");
953             item.setPurpose(MappingPurpose.BOTH);
954 
955             provisionGroup.getMapping().setConnObjectKeyItem(item);
956             provisionGroup.getMapping().setConnObjectLink("'cn=' + originalName + ',ou=groups,o=isp'");
957 
958             ldap.getProvisions().clear();
959             ldap.getProvisions().add(provisionGroup);
960 
961             ldap.setKey(RESOURCE_NAME_LDAP + "1605" + getUUIDString());
962             RESOURCE_SERVICE.create(ldap);
963 
964             // 1. create group with the new resource assigned
965             String originalName = "grp1605-" + getUUIDString();
966 
967             GroupCR groupCR = new GroupCR();
968             groupCR.setName("SYNCOPEGROUP1605-" + getUUIDString());
969             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
970             groupCR.getResources().add(ldap.getKey());
971             groupCR.getPlainAttrs().add(new Attr.Builder("originalName").value(originalName).build());
972 
973             GroupTO groupTO = createGroup(groupCR).getEntity();
974             assertNotNull(groupTO);
975 
976             // 3. check attributes prepared for propagation
977             PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
978                     resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).build());
979             assertEquals(1, tasks.getSize());
980             assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
981             assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
982 
983             ConnObject beforeConnObject =
984                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.GROUP.name(), groupTO.getKey());
985 
986             GroupUR groupUR = new GroupUR();
987             groupUR.setKey(groupTO.getKey());
988 
989             groupUR.getPlainAttrs().add(attrAddReplacePatch("originalName", "new" + originalName));
990             groupTO = updateGroup(groupUR).getEntity();
991 
992             tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
993                     resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).
994                     orderBy("start DESC").build());
995             assertEquals(2, tasks.getSize());
996             assertEquals(ResourceOperation.UPDATE, tasks.getResult().get(0).getOperation());
997             assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
998 
999             ConnObject afterConnObject =
1000                     RESOURCE_SERVICE.readConnObject(ldap.getKey(), AnyTypeKind.GROUP.name(), groupTO.getKey());
1001             assertNotEquals(afterConnObject.getAttr(Name.NAME).get().getValues().get(0),
1002                     beforeConnObject.getAttr(Name.NAME).get().getValues().get(0));
1003             assertTrue(afterConnObject.getAttr(Name.NAME).get().getValues().get(0).contains("new" + originalName));
1004         } finally {
1005             try {
1006                 RESOURCE_SERVICE.delete(ldap.getKey());
1007             } catch (Exception ignore) {
1008                 // ignore
1009             }
1010         }
1011     }
1012 
1013     @Test
1014     public void issueSYNCOPE1751() {
1015         // 1. Create a Group with a resource assigned
1016         GroupTO groupTO = createGroup(
1017                 new GroupCR.Builder(SyncopeConstants.ROOT_REALM, "SYNCOPEGROUP1751-" + getUUIDString()).
1018                         resource(RESOURCE_NAME_LDAP).build()).getEntity();
1019         // 2. Create a user
1020         String username = "SYNCOPEUSER1751" + getUUIDString();
1021         UserTO userTO = createUser(
1022                 new UserCR.Builder(SyncopeConstants.ROOT_REALM, username).plainAttrs(
1023                         new Attr.Builder("userId").value(username + "@syncope.org").build(),
1024                         new Attr.Builder("fullname").value(username).build(),
1025                         new Attr.Builder("surname").value(username).build()).
1026                         build()).getEntity();
1027         // 3. Update the user assigning the group previously created -> group-based provisioning
1028         userTO = updateUser(
1029                 new UserUR.Builder(userTO.getKey()).
1030                         membership(new MembershipUR.Builder(groupTO.getKey()).build()).
1031                         build()).getEntity();
1032         // since the resource is flagged to generate random pwd must populate the password on effective create on the
1033         // resource, even if it is an update on Syncope
1034         PagedResult<TaskTO> propTasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
1035                 resource(RESOURCE_NAME_LDAP).anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).build());
1036         assertFalse(propTasks.getResult().isEmpty());
1037         assertEquals(1, propTasks.getSize());
1038         PropagationData propagationData = POJOHelper.deserialize(
1039                 PropagationTaskTO.class.cast(propTasks.getResult().get(0)).getPropagationData(),
1040                 PropagationData.class);
1041         assertTrue(propagationData.getAttributes().stream().
1042                 anyMatch(a -> OperationalAttributes.PASSWORD_NAME.equals(a.getName())));
1043     }
1044 }