View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.fit.core;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertNotEquals;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertNull;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  import static org.junit.jupiter.api.Assertions.fail;
30  import static org.junit.jupiter.api.Assumptions.assumeFalse;
31  
32  import java.io.IOException;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Optional;
36  import java.util.UUID;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.atomic.AtomicReference;
39  import javax.naming.NamingEnumeration;
40  import javax.naming.NamingException;
41  import javax.naming.directory.DirContext;
42  import javax.naming.directory.SearchControls;
43  import javax.naming.directory.SearchResult;
44  import javax.ws.rs.ForbiddenException;
45  import javax.ws.rs.core.GenericType;
46  import javax.ws.rs.core.Response;
47  import org.apache.commons.lang3.SerializationUtils;
48  import org.apache.syncope.client.lib.SyncopeClient;
49  import org.apache.syncope.common.lib.AnyOperations;
50  import org.apache.syncope.common.lib.Attr;
51  import org.apache.syncope.common.lib.EntityTOUtils;
52  import org.apache.syncope.common.lib.SyncopeClientException;
53  import org.apache.syncope.common.lib.SyncopeConstants;
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.ResourceAR;
60  import org.apache.syncope.common.lib.request.ResourceDR;
61  import org.apache.syncope.common.lib.request.StringPatchItem;
62  import org.apache.syncope.common.lib.request.StringReplacePatchItem;
63  import org.apache.syncope.common.lib.request.UserCR;
64  import org.apache.syncope.common.lib.to.AnyObjectTO;
65  import org.apache.syncope.common.lib.to.AnyTypeClassTO;
66  import org.apache.syncope.common.lib.to.AnyTypeTO;
67  import org.apache.syncope.common.lib.to.ConnInstanceTO;
68  import org.apache.syncope.common.lib.to.ConnObject;
69  import org.apache.syncope.common.lib.to.DerSchemaTO;
70  import org.apache.syncope.common.lib.to.ExecTO;
71  import org.apache.syncope.common.lib.to.GroupTO;
72  import org.apache.syncope.common.lib.to.Item;
73  import org.apache.syncope.common.lib.to.Mapping;
74  import org.apache.syncope.common.lib.to.MembershipTO;
75  import org.apache.syncope.common.lib.to.PagedResult;
76  import org.apache.syncope.common.lib.to.PlainSchemaTO;
77  import org.apache.syncope.common.lib.to.PropagationStatus;
78  import org.apache.syncope.common.lib.to.Provision;
79  import org.apache.syncope.common.lib.to.ProvisioningResult;
80  import org.apache.syncope.common.lib.to.ResourceTO;
81  import org.apache.syncope.common.lib.to.TypeExtensionTO;
82  import org.apache.syncope.common.lib.to.UserTO;
83  import org.apache.syncope.common.lib.types.AnyTypeKind;
84  import org.apache.syncope.common.lib.types.AttrSchemaType;
85  import org.apache.syncope.common.lib.types.CipherAlgorithm;
86  import org.apache.syncope.common.lib.types.ClientExceptionType;
87  import org.apache.syncope.common.lib.types.ConnectorCapability;
88  import org.apache.syncope.common.lib.types.ExecStatus;
89  import org.apache.syncope.common.lib.types.MappingPurpose;
90  import org.apache.syncope.common.lib.types.PatchOperation;
91  import org.apache.syncope.common.lib.types.ProvisionAction;
92  import org.apache.syncope.common.lib.types.ResourceAssociationAction;
93  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
94  import org.apache.syncope.common.lib.types.SchemaType;
95  import org.apache.syncope.common.lib.types.TaskType;
96  import org.apache.syncope.common.rest.api.beans.AnyQuery;
97  import org.apache.syncope.common.rest.api.service.GroupService;
98  import org.apache.syncope.common.rest.api.service.SyncopeService;
99  import org.apache.syncope.core.provisioning.java.job.TaskJob;
100 import org.apache.syncope.core.spring.security.Encryptor;
101 import org.apache.syncope.fit.AbstractITCase;
102 import org.junit.jupiter.api.Assertions;
103 import org.junit.jupiter.api.Test;
104 
105 public class GroupITCase extends AbstractITCase {
106 
107     public static GroupCR getBasicSample(final String name) {
108         return new GroupCR.Builder(SyncopeConstants.ROOT_REALM, name + getUUIDString()).build();
109     }
110 
111     public static GroupCR getSample(final String name) {
112         GroupCR groupCR = getBasicSample(name);
113 
114         groupCR.getPlainAttrs().add(attr("icon", "anIcon"));
115 
116         groupCR.getResources().add(RESOURCE_NAME_LDAP);
117         return groupCR;
118     }
119 
120     @Test
121     public void create() {
122         GroupCR groupCR = getSample("lastGroup");
123         groupCR.getVirAttrs().add(attr("rvirtualdata", "rvirtualvalue"));
124         groupCR.setGroupOwner("f779c0d4-633b-4be5-8f57-32eb478a3ca5");
125 
126         GroupTO groupTO = createGroup(groupCR).getEntity();
127         assertNotNull(groupTO);
128 
129         assertNotNull(groupTO.getVirAttr("rvirtualdata").get().getValues());
130         assertFalse(groupTO.getVirAttr("rvirtualdata").get().getValues().isEmpty());
131         assertEquals("rvirtualvalue", groupTO.getVirAttr("rvirtualdata").get().getValues().get(0));
132 
133         assertTrue(groupTO.getResources().contains(RESOURCE_NAME_LDAP));
134 
135         ConnObject connObjectTO =
136                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
137         assertNotNull(connObjectTO);
138         assertNotNull(connObjectTO.getAttr("owner"));
139 
140         // SYNCOPE-515: remove ownership
141         GroupUR groupUR = new GroupUR();
142         groupUR.setKey(groupTO.getKey());
143         groupUR.setGroupOwner(new StringReplacePatchItem());
144 
145         assertNull(updateGroup(groupUR).getEntity().getGroupOwner());
146     }
147 
148     @Test
149     public void createWithInternationalCharacters() {
150         GroupCR groupCR = getSample("räksmörgås");
151 
152         GroupTO groupTO = createGroup(groupCR).getEntity();
153         assertNotNull(groupTO);
154     }
155 
156     @Test
157     public void delete() {
158         try {
159             GROUP_SERVICE.delete(UUID.randomUUID().toString());
160         } catch (SyncopeClientException e) {
161             assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
162         }
163 
164         GroupCR groupCR = new GroupCR();
165         groupCR.setName("toBeDeleted" + getUUIDString());
166         groupCR.setRealm("/even");
167 
168         groupCR.getResources().add(RESOURCE_NAME_LDAP);
169 
170         GroupTO groupTO = createGroup(groupCR).getEntity();
171         assertNotNull(groupTO);
172 
173         GroupTO deletedGroup = deleteGroup(groupTO.getKey()).getEntity();
174         assertNotNull(deletedGroup);
175 
176         try {
177             GROUP_SERVICE.read(deletedGroup.getKey());
178         } catch (SyncopeClientException e) {
179             assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
180         }
181     }
182 
183     @Test
184     public void list() {
185         PagedResult<GroupTO> groupTOs =
186                 GROUP_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build());
187         assertNotNull(groupTOs);
188         assertTrue(groupTOs.getResult().size() >= 8);
189         groupTOs.getResult().forEach(Assertions::assertNotNull);
190     }
191 
192     @Test
193     public void read() {
194         GroupTO groupTO = GROUP_SERVICE.read("37d15e4c-cdc1-460b-a591-8505c8133806");
195 
196         assertNotNull(groupTO);
197         assertNotNull(groupTO.getPlainAttrs());
198         assertFalse(groupTO.getPlainAttrs().isEmpty());
199         assertEquals(2, groupTO.getStaticUserMembershipCount());
200     }
201 
202     @Test
203     public void selfRead() {
204         UserTO userTO = USER_SERVICE.read("1417acbe-cbf6-4277-9372-e75e04f97000");
205         assertNotNull(userTO);
206 
207         assertTrue(userTO.getMembership("37d15e4c-cdc1-460b-a591-8505c8133806").isPresent());
208         assertFalse(userTO.getMembership("29f96485-729e-4d31-88a1-6fc60e4677f3").isPresent());
209 
210         GroupService groupService2 = CLIENT_FACTORY.create("rossini", ADMIN_PWD).getService(GroupService.class);
211 
212         try {
213             groupService2.read("29f96485-729e-4d31-88a1-6fc60e4677f3");
214             fail("This should not happen");
215         } catch (SyncopeClientException e) {
216             assertEquals(ClientExceptionType.DelegatedAdministration, e.getType());
217         }
218 
219         List<GroupTO> groups = groupService2.own();
220         assertNotNull(groups);
221         assertTrue(groups.stream().anyMatch(group -> "37d15e4c-cdc1-460b-a591-8505c8133806".equals(group.getKey())));
222     }
223 
224     @Test
225     public void update() {
226         GroupCR groupCR = getSample("latestGroup" + getUUIDString());
227         GroupTO groupTO = createGroup(groupCR).getEntity();
228 
229         assertEquals(1, groupTO.getPlainAttrs().size());
230 
231         GroupUR groupUR = new GroupUR();
232         groupUR.setKey(groupTO.getKey());
233         String modName = "finalGroup" + getUUIDString();
234         groupUR.setName(new StringReplacePatchItem.Builder().value(modName).build());
235         groupUR.getPlainAttrs().add(attrAddReplacePatch("show", "FALSE"));
236 
237         groupTO = updateGroup(groupUR).getEntity();
238 
239         assertEquals(modName, groupTO.getName());
240         assertEquals(2, groupTO.getPlainAttrs().size());
241 
242         groupTO.getPlainAttr("show").get().getValues().clear();
243 
244         groupUR = new GroupUR.Builder(groupTO.getKey()).
245                 plainAttr(new AttrPatch.Builder(new Attr.Builder("show").build()).
246                         operation(PatchOperation.DELETE).build()).build();
247 
248         groupTO = updateGroup(groupUR).getEntity();
249 
250         assertFalse(groupTO.getPlainAttr("show").isPresent());
251     }
252 
253     @Test
254     public void patch() {
255         GroupCR createReq = getBasicSample("patch");
256         createReq.setUDynMembershipCond(
257                 "(($groups==ebf97068-aa4b-4a85-9f01-680e8c4cf227;$resources!=ws-target-resource-1);aLong==1)");
258         createReq.getADynMembershipConds().put(
259                 PRINTER,
260                 "(($groups==ece66293-8f31-4a84-8e8d-23da36e70846;cool==ss);$resources==ws-target-resource-2);"
261                 + "$type==PRINTER");
262 
263         GroupTO created = createGroup(createReq).getEntity();
264 
265         created.getPlainAttrs().add(new Attr.Builder("icon").build());
266         created.getPlainAttrs().add(new Attr.Builder("show").build());
267         created.getPlainAttrs().add(new Attr.Builder("rderived_sx").value("sx").build());
268         created.getPlainAttrs().add(new Attr.Builder("rderived_dx").value("dx").build());
269         created.getPlainAttrs().add(new Attr.Builder("title").value("mr").build());
270 
271         GroupTO original = GROUP_SERVICE.read(created.getKey());
272 
273         GroupUR groupUR = AnyOperations.diff(created, original, true);
274         GroupTO updated = updateGroup(groupUR).getEntity();
275 
276         Map<String, Attr> attrs = EntityTOUtils.buildAttrMap(updated.getPlainAttrs());
277         assertFalse(attrs.containsKey("icon"));
278         assertFalse(attrs.containsKey("show"));
279         assertEquals(List.of("sx"), attrs.get("rderived_sx").getValues());
280         assertEquals(List.of("dx"), attrs.get("rderived_dx").getValues());
281         assertEquals(List.of("mr"), attrs.get("title").getValues());
282     }
283 
284     @Test
285     public void updateAsGroupOwner() {
286         // 1. read group as admin
287         GroupTO groupTO = GROUP_SERVICE.read("ebf97068-aa4b-4a85-9f01-680e8c4cf227");
288 
289         // issue SYNCOPE-15
290         assertNotNull(groupTO.getCreationDate());
291         assertNotNull(groupTO.getLastChangeDate());
292         assertEquals("admin", groupTO.getCreator());
293         assertEquals("admin", groupTO.getLastModifier());
294 
295         // 2. prepare update
296         GroupUR groupUR = new GroupUR();
297         groupUR.setKey(groupTO.getKey());
298         groupUR.setName(new StringReplacePatchItem.Builder().value("Director").build());
299 
300         // 3. try to update as verdi, not owner of group 6 - fail
301         GroupService groupService2 = CLIENT_FACTORY.create("verdi", ADMIN_PWD).getService(GroupService.class);
302 
303         try {
304             groupService2.update(groupUR);
305             fail("This should not happen");
306         } catch (Exception e) {
307             assertNotNull(e);
308         }
309 
310         // 4. update as puccini, owner of group 6 - success
311         GroupService groupService3 = CLIENT_FACTORY.create("puccini", ADMIN_PWD).getService(GroupService.class);
312 
313         groupTO = groupService3.update(groupUR).readEntity(new GenericType<ProvisioningResult<GroupTO>>() {
314         }).getEntity();
315         assertEquals("Director", groupTO.getName());
316 
317         // issue SYNCOPE-15
318         assertNotNull(groupTO.getCreationDate());
319         assertNotNull(groupTO.getLastChangeDate());
320         assertEquals("admin", groupTO.getCreator());
321         assertEquals("puccini", groupTO.getLastModifier());
322         assertTrue(groupTO.getCreationDate().isBefore(groupTO.getLastChangeDate()));
323     }
324 
325     @Test
326     public void unlink() throws IOException {
327         GroupTO actual = createGroup(getSample("unlink")).getEntity();
328         assertNotNull(actual);
329 
330         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), actual.getKey()));
331 
332         ResourceDR resourceDR = new ResourceDR.Builder().key(actual.getKey()).
333                 action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_LDAP).build();
334 
335         assertNotNull(parseBatchResponse(GROUP_SERVICE.deassociate(resourceDR)));
336 
337         actual = GROUP_SERVICE.read(actual.getKey());
338         assertNotNull(actual);
339         assertTrue(actual.getResources().isEmpty());
340 
341         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), actual.getKey()));
342     }
343 
344     @Test
345     public void link() throws IOException {
346         GroupCR groupCR = getSample("link");
347         groupCR.getResources().clear();
348 
349         GroupTO actual = createGroup(groupCR).getEntity();
350         assertNotNull(actual);
351 
352         try {
353             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), actual.getKey());
354             fail("This should not happen");
355         } catch (Exception e) {
356             assertNotNull(e);
357         }
358 
359         ResourceAR resourceAR = new ResourceAR.Builder().key(actual.getKey()).
360                 action(ResourceAssociationAction.LINK).resource(RESOURCE_NAME_LDAP).build();
361 
362         assertNotNull(parseBatchResponse(GROUP_SERVICE.associate(resourceAR)));
363 
364         actual = GROUP_SERVICE.read(actual.getKey());
365         assertFalse(actual.getResources().isEmpty());
366 
367         try {
368             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), actual.getKey());
369             fail("This should not happen");
370         } catch (Exception e) {
371             assertNotNull(e);
372         }
373     }
374 
375     @Test
376     public void unassign() throws IOException {
377         GroupTO groupTO = null;
378 
379         try {
380             groupTO = createGroup(getSample("unassign")).getEntity();
381             assertNotNull(groupTO);
382 
383             assertNotNull(RESOURCE_SERVICE.readConnObject(
384                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey()));
385 
386             ResourceDR resourceDR = new ResourceDR();
387             resourceDR.setKey(groupTO.getKey());
388             resourceDR.setAction(ResourceDeassociationAction.UNASSIGN);
389             resourceDR.getResources().add(RESOURCE_NAME_LDAP);
390 
391             assertNotNull(parseBatchResponse(GROUP_SERVICE.deassociate(resourceDR)));
392 
393             groupTO = GROUP_SERVICE.read(groupTO.getKey());
394             assertNotNull(groupTO);
395             assertTrue(groupTO.getResources().isEmpty());
396 
397             try {
398                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
399                 fail("This should not happen");
400             } catch (Exception e) {
401                 assertNotNull(e);
402             }
403         } finally {
404             if (groupTO != null) {
405                 GROUP_SERVICE.delete(groupTO.getKey());
406             }
407         }
408     }
409 
410     @Test
411     public void assign() throws IOException {
412         GroupCR groupCR = getSample("assign");
413         groupCR.getResources().clear();
414 
415         GroupTO groupTO = null;
416         try {
417             groupTO = createGroup(groupCR).getEntity();
418             assertNotNull(groupTO);
419 
420             try {
421                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
422                 fail("This should not happen");
423             } catch (Exception e) {
424                 assertNotNull(e);
425             }
426 
427             ResourceAR resourceAR = new ResourceAR.Builder().key(groupTO.getKey()).
428                     action(ResourceAssociationAction.ASSIGN).resource(RESOURCE_NAME_LDAP).build();
429 
430             assertNotNull(parseBatchResponse(GROUP_SERVICE.associate(resourceAR)));
431 
432             groupTO = GROUP_SERVICE.read(groupTO.getKey());
433             assertFalse(groupTO.getResources().isEmpty());
434             assertNotNull(RESOURCE_SERVICE.readConnObject(
435                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey()));
436         } finally {
437             if (groupTO != null) {
438                 GROUP_SERVICE.delete(groupTO.getKey());
439             }
440         }
441     }
442 
443     @Test
444     public void deprovision() throws IOException {
445         GroupTO groupTO = null;
446 
447         try {
448             groupTO = createGroup(getSample("deprovision")).getEntity();
449             assertNotNull(groupTO);
450             assertNotNull(groupTO.getKey());
451 
452             assertNotNull(
453                     RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey()));
454 
455             ResourceDR resourceDR = new ResourceDR.Builder().key(groupTO.getKey()).
456                     action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_LDAP).build();
457 
458             assertNotNull(parseBatchResponse(GROUP_SERVICE.deassociate(resourceDR)));
459 
460             groupTO = GROUP_SERVICE.read(groupTO.getKey());
461             assertNotNull(groupTO);
462             assertFalse(groupTO.getResources().isEmpty());
463 
464             try {
465                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
466                 fail("This should not happen");
467             } catch (Exception e) {
468                 assertNotNull(e);
469             }
470         } finally {
471             if (groupTO != null) {
472                 GROUP_SERVICE.delete(groupTO.getKey());
473             }
474         }
475     }
476 
477     @Test
478     public void provision() throws IOException {
479         GroupCR groupCR = getSample("provision");
480         groupCR.getResources().clear();
481 
482         GroupTO groupTO = null;
483         try {
484             groupTO = createGroup(groupCR).getEntity();
485             assertNotNull(groupTO);
486 
487             try {
488                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
489                 fail("This should not happen");
490             } catch (Exception e) {
491                 assertNotNull(e);
492             }
493 
494             ResourceAR resourceAR = new ResourceAR.Builder().key(groupTO.getKey()).
495                     action(ResourceAssociationAction.PROVISION).resource(RESOURCE_NAME_LDAP).build();
496 
497             assertNotNull(parseBatchResponse(GROUP_SERVICE.associate(resourceAR)));
498 
499             groupTO = GROUP_SERVICE.read(groupTO.getKey());
500             assertTrue(groupTO.getResources().isEmpty());
501 
502             assertNotNull(RESOURCE_SERVICE.readConnObject(
503                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey()));
504         } finally {
505             if (groupTO != null) {
506                 GROUP_SERVICE.delete(groupTO.getKey());
507             }
508         }
509     }
510 
511     @Test
512     public void deprovisionUnlinked() throws IOException {
513         GroupCR groupCR = getSample("deprovision");
514         groupCR.getResources().clear();
515 
516         GroupTO groupTO = null;
517         try {
518             groupTO = createGroup(groupCR).getEntity();
519             assertNotNull(groupTO);
520 
521             try {
522                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
523                 fail("This should not happen");
524             } catch (Exception e) {
525                 assertNotNull(e);
526             }
527 
528             ResourceAR resourceAR = new ResourceAR.Builder().key(groupTO.getKey()).
529                     action(ResourceAssociationAction.PROVISION).resource(RESOURCE_NAME_LDAP).build();
530 
531             assertNotNull(parseBatchResponse(GROUP_SERVICE.associate(resourceAR)));
532 
533             groupTO = GROUP_SERVICE.read(groupTO.getKey());
534             assertTrue(groupTO.getResources().isEmpty());
535 
536             assertNotNull(RESOURCE_SERVICE.readConnObject(
537                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey()));
538 
539             ResourceDR resourceDR = new ResourceDR.Builder().key(groupTO.getKey()).
540                     action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_LDAP).build();
541 
542             assertNotNull(parseBatchResponse(GROUP_SERVICE.deassociate(resourceDR)));
543 
544             groupTO = GROUP_SERVICE.read(groupTO.getKey());
545             assertNotNull(groupTO);
546             assertTrue(groupTO.getResources().isEmpty());
547 
548             try {
549                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
550                 fail("This should not happen");
551             } catch (Exception e) {
552                 assertNotNull(e);
553             }
554         } finally {
555             if (groupTO != null) {
556                 GROUP_SERVICE.delete(groupTO.getKey());
557             }
558         }
559     }
560 
561     @Test
562     public void createWithMandatorySchema() {
563         // 1. create a mandatory schema
564         PlainSchemaTO badge = new PlainSchemaTO();
565         badge.setKey("badge" + getUUIDString());
566         badge.setMandatoryCondition("true");
567         SCHEMA_SERVICE.create(SchemaType.PLAIN, badge);
568 
569         // 2. create a group *without* an attribute for that schema: it works
570         GroupCR groupCR = getSample("lastGroup");
571         GroupTO groupTO = createGroup(groupCR).getEntity();
572         assertNotNull(groupTO);
573         assertFalse(groupTO.getPlainAttr(badge.getKey()).isPresent());
574 
575         // 3. add the new mandatory schema to the default group type
576         AnyTypeTO type = ANY_TYPE_SERVICE.read(AnyTypeKind.GROUP.name());
577         String typeClassName = type.getClasses().get(0);
578         AnyTypeClassTO typeClass = ANY_TYPE_CLASS_SERVICE.read(typeClassName);
579         typeClass.getPlainSchemas().add(badge.getKey());
580         ANY_TYPE_CLASS_SERVICE.update(typeClass);
581         typeClass = ANY_TYPE_CLASS_SERVICE.read(typeClassName);
582         assertTrue(typeClass.getPlainSchemas().contains(badge.getKey()));
583 
584         try {
585             // 4. update group: failure since no values are provided and it is mandatory
586             GroupUR groupUR = new GroupUR();
587             groupUR.setKey(groupTO.getKey());
588 
589             try {
590                 updateGroup(groupUR);
591                 fail("This should not happen");
592             } catch (SyncopeClientException e) {
593                 assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
594             }
595 
596             // 5. also add an actual attribute for badge - it will work
597             groupUR.getPlainAttrs().add(attrAddReplacePatch(badge.getKey(), "xxxxxxxxxx"));
598 
599             groupTO = updateGroup(groupUR).getEntity();
600             assertNotNull(groupTO);
601             assertNotNull(groupTO.getPlainAttr(badge.getKey()));
602         } finally {
603             // restore the original group class
604             typeClass.getPlainSchemas().remove(badge.getKey());
605             ANY_TYPE_CLASS_SERVICE.update(typeClass);
606             typeClass = ANY_TYPE_CLASS_SERVICE.read(typeClassName);
607             assertFalse(typeClass.getPlainSchemas().contains(badge.getKey()));
608         }
609     }
610 
611     @Test
612     public void encrypted() throws Exception {
613         // 1. create encrypted schema with secret key as system property
614         PlainSchemaTO encrypted = new PlainSchemaTO();
615         encrypted.setKey("encrypted" + getUUIDString());
616         encrypted.setType(AttrSchemaType.Encrypted);
617         encrypted.setCipherAlgorithm(CipherAlgorithm.SHA512);
618         encrypted.setSecretKey("${obscureSecretKey}");
619         SCHEMA_SERVICE.create(SchemaType.PLAIN, encrypted);
620 
621         // 2. add the new schema to the default group type
622         AnyTypeTO type = ANY_TYPE_SERVICE.read(AnyTypeKind.GROUP.name());
623         String typeClassName = type.getClasses().get(0);
624         AnyTypeClassTO typeClass = ANY_TYPE_CLASS_SERVICE.read(typeClassName);
625         typeClass.getPlainSchemas().add(encrypted.getKey());
626         ANY_TYPE_CLASS_SERVICE.update(typeClass);
627         typeClass = ANY_TYPE_CLASS_SERVICE.read(typeClassName);
628         assertTrue(typeClass.getPlainSchemas().contains(encrypted.getKey()));
629 
630         // 3. create group, verify that the correct encrypted value is returned
631         GroupCR groupCR = getSample("encrypted");
632         groupCR.getPlainAttrs().add(new Attr.Builder(encrypted.getKey()).value("testvalue").build());
633         GroupTO group = createGroup(groupCR).getEntity();
634 
635         assertEquals(Encryptor.getInstance(System.getProperty("obscureSecretKey")).
636                 encode("testvalue", encrypted.getCipherAlgorithm()),
637                 group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
638 
639         // 4. update schema to return cleartext values
640         encrypted.setAnyTypeClass(typeClassName);
641         encrypted.setCipherAlgorithm(CipherAlgorithm.AES);
642         encrypted.setConversionPattern(SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN);
643         SCHEMA_SERVICE.update(SchemaType.PLAIN, encrypted);
644 
645         // 5. update group, verify that the cleartext value is returned
646         GroupUR groupUR = new GroupUR();
647         groupUR.setKey(group.getKey());
648         groupUR.getPlainAttrs().add(new AttrPatch.Builder(
649                 new Attr.Builder(encrypted.getKey()).value("testvalue").build()).build());
650         group = updateGroup(groupUR).getEntity();
651 
652         assertEquals("testvalue", group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
653 
654         // 6. update schema again to disallow cleartext values
655         encrypted.setConversionPattern(null);
656         SCHEMA_SERVICE.update(SchemaType.PLAIN, encrypted);
657 
658         group = GROUP_SERVICE.read(group.getKey());
659         assertNotEquals("testvalue", group.getPlainAttr(encrypted.getKey()).get().getValues().get(0));
660     }
661 
662     @Test
663     public void anonymous() {
664         try {
665             ANONYMOUS_CLIENT.getService(GroupService.class).
666                     search(new AnyQuery.Builder().realm("/even").build());
667             fail("This should not happen");
668         } catch (ForbiddenException e) {
669             assertNotNull(e);
670         }
671 
672         assertFalse(ANONYMOUS_CLIENT.getService(SyncopeService.class).
673                 searchAssignableGroups("/even", null, 1, 100).getResult().isEmpty());
674     }
675 
676     @Test
677     public void uDynMembership() {
678         assertTrue(USER_SERVICE.read("c9b2dec2-00a7-4855-97c0-d854842b4b24").getDynMemberships().isEmpty());
679 
680         GroupCR groupCR = getBasicSample("uDynMembership");
681         groupCR.setUDynMembershipCond("cool==true");
682         GroupTO group = null;
683         try {
684             group = createGroup(groupCR).getEntity();
685             assertNotNull(group);
686             String groupKey = group.getKey();
687 
688             List<MembershipTO> memberships =
689                     USER_SERVICE.read("c9b2dec2-00a7-4855-97c0-d854842b4b24").getDynMemberships();
690             assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
691             assertEquals(1, GROUP_SERVICE.read(group.getKey()).getDynamicUserMembershipCount());
692 
693             GROUP_SERVICE.update(new GroupUR.Builder(group.getKey()).udynMembershipCond("cool==false").build());
694 
695             assertTrue(USER_SERVICE.read("c9b2dec2-00a7-4855-97c0-d854842b4b24").getDynMemberships().isEmpty());
696             assertEquals(0, GROUP_SERVICE.read(group.getKey()).getDynamicUserMembershipCount());
697         } finally {
698             if (group != null) {
699                 GROUP_SERVICE.delete(group.getKey());
700             }
701         }
702     }
703 
704     @Test
705     public void aDynMembership() {
706         String fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").notNullValue().query();
707 
708         // 1. create group with a given aDynMembership condition
709         GroupCR groupCR = getBasicSample("aDynMembership");
710         groupCR.getADynMembershipConds().put(PRINTER, fiql);
711         GroupTO group = createGroup(groupCR).getEntity();
712         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
713 
714         if (IS_EXT_SEARCH_ENABLED) {
715             try {
716                 Thread.sleep(2000);
717             } catch (InterruptedException ex) {
718                 // ignore
719             }
720         }
721 
722         group = GROUP_SERVICE.read(group.getKey());
723         String groupKey = group.getKey();
724         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
725 
726         // verify that the condition is dynamically applied
727         AnyObjectCR newAnyCR = AnyObjectITCase.getSample("aDynMembership");
728         newAnyCR.getResources().clear();
729         AnyObjectTO newAny = createAnyObject(newAnyCR).getEntity();
730         assertNotNull(newAny.getPlainAttr("location"));
731         List<MembershipTO> memberships = ANY_OBJECT_SERVICE.read(
732                 "fc6dbc3a-6c07-4965-8781-921e7401a4a5").getDynMemberships();
733         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
734 
735         memberships = ANY_OBJECT_SERVICE.read(
736                 "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").getDynMemberships();
737         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
738 
739         memberships = ANY_OBJECT_SERVICE.read(newAny.getKey()).getDynMemberships();
740         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
741 
742         // 2. update group and change aDynMembership condition
743         fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").nullValue().query();
744 
745         GroupUR groupUR = new GroupUR();
746         groupUR.setKey(group.getKey());
747         groupUR.getADynMembershipConds().put(PRINTER, fiql);
748 
749         group = updateGroup(groupUR).getEntity();
750         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
751 
752         group = GROUP_SERVICE.read(group.getKey());
753         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
754 
755         // verify that the condition is dynamically applied
756         AnyObjectUR anyObjectUR = new AnyObjectUR();
757         anyObjectUR.setKey(newAny.getKey());
758         anyObjectUR.getPlainAttrs().add(new AttrPatch.Builder(new Attr.Builder("location").build()).
759                 operation(PatchOperation.DELETE).
760                 build());
761         newAny = updateAnyObject(anyObjectUR).getEntity();
762         assertFalse(newAny.getPlainAttr("location").isPresent());
763 
764         memberships = ANY_OBJECT_SERVICE.read(
765                 "fc6dbc3a-6c07-4965-8781-921e7401a4a5").getDynMemberships();
766         assertFalse(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
767         memberships = ANY_OBJECT_SERVICE.read(
768                 "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").getDynMemberships();
769         assertFalse(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
770         memberships = ANY_OBJECT_SERVICE.read(newAny.getKey()).getDynMemberships();
771         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
772     }
773 
774     @Test
775     public void aDynMembershipCount() {
776         // Create a new printer as a dynamic member of a new group
777         GroupCR groupCR = getBasicSample("aDynamicMembership");
778         String fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").equalTo("home").query();
779         groupCR.getADynMembershipConds().put(PRINTER, fiql);
780         GroupTO group = createGroup(groupCR).getEntity();
781 
782         AnyObjectCR printerCR = new AnyObjectCR();
783         printerCR.setRealm(SyncopeConstants.ROOT_REALM);
784         printerCR.setName("Printer_" + getUUIDString());
785         printerCR.setType(PRINTER);
786         printerCR.getPlainAttrs().add(new Attr.Builder("location").value("home").build());
787         AnyObjectTO printer = createAnyObject(printerCR).getEntity();
788 
789         group = GROUP_SERVICE.read(group.getKey());
790         assertEquals(0, group.getStaticAnyObjectMembershipCount());
791         assertEquals(1, group.getDynamicAnyObjectMembershipCount());
792 
793         ANY_OBJECT_SERVICE.delete(printer.getKey());
794         GROUP_SERVICE.delete(group.getKey());
795     }
796 
797     @Test
798     public void aStaticMembershipCount() {
799         // Create a new printer as a static member of a new group
800         GroupCR groupCR = getBasicSample("aStaticMembership");
801         GroupTO group = createGroup(groupCR).getEntity();
802 
803         AnyObjectCR printerCR = new AnyObjectCR();
804         printerCR.setRealm(SyncopeConstants.ROOT_REALM);
805         printerCR.setName("Printer_" + getUUIDString());
806         printerCR.setType(PRINTER);
807         printerCR.getMemberships().add(new MembershipTO.Builder(group.getKey()).build());
808         AnyObjectTO printer = createAnyObject(printerCR).getEntity();
809 
810         group = GROUP_SERVICE.read(group.getKey());
811         assertEquals(0, group.getDynamicAnyObjectMembershipCount());
812         assertEquals(1, group.getStaticAnyObjectMembershipCount());
813 
814         ANY_OBJECT_SERVICE.delete(printer.getKey());
815         GROUP_SERVICE.delete(group.getKey());
816     }
817 
818     @Test
819     public void capabilitiesOverride() {
820         // resource with no capability override
821         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
822         assertNotNull(ldap);
823         assertFalse(ldap.isOverrideCapabilities());
824         assertTrue(ldap.getCapabilitiesOverride().isEmpty());
825 
826         // connector with all required for create and update
827         ConnInstanceTO conn = CONNECTOR_SERVICE.read(ldap.getConnector(), null);
828         assertNotNull(conn);
829         assertTrue(conn.getCapabilities().contains(ConnectorCapability.CREATE));
830         assertTrue(conn.getCapabilities().contains(ConnectorCapability.UPDATE));
831 
832         try {
833             // 1. create succeeds
834             GroupCR groupCR = getSample("syncope714");
835             groupCR.getPlainAttrs().add(attr("title", "first"));
836             groupCR.getResources().add(RESOURCE_NAME_LDAP);
837 
838             ProvisioningResult<GroupTO> result = createGroup(groupCR);
839             assertNotNull(result);
840             assertEquals(1, result.getPropagationStatuses().size());
841             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
842             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
843             GroupTO group = result.getEntity();
844 
845             // 2. update succeeds
846             GroupUR groupUR = new GroupUR();
847             groupUR.setKey(group.getKey());
848             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "second")).
849                     operation(PatchOperation.ADD_REPLACE).build());
850 
851             result = updateGroup(groupUR);
852             assertNotNull(result);
853             assertEquals(1, result.getPropagationStatuses().size());
854             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
855             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
856             group = result.getEntity();
857 
858             // 3. set capability override with only search allowed, but not enable
859             ldap.getCapabilitiesOverride().add(ConnectorCapability.SEARCH);
860             RESOURCE_SERVICE.update(ldap);
861             ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
862             assertNotNull(ldap);
863             assertFalse(ldap.isOverrideCapabilities());
864             assertEquals(1, ldap.getCapabilitiesOverride().size());
865             assertTrue(ldap.getCapabilitiesOverride().contains(ConnectorCapability.SEARCH));
866 
867             // 4. update succeeds again
868             groupUR = new GroupUR();
869             groupUR.setKey(group.getKey());
870             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "third")).
871                     operation(PatchOperation.ADD_REPLACE).build());
872 
873             result = updateGroup(groupUR);
874             assertNotNull(result);
875             assertEquals(1, result.getPropagationStatuses().size());
876             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
877             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
878             group = result.getEntity();
879 
880             // 5. enable capability override
881             ldap.setOverrideCapabilities(true);
882             RESOURCE_SERVICE.update(ldap);
883             ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
884             assertNotNull(ldap);
885             assertTrue(ldap.isOverrideCapabilities());
886             assertEquals(1, ldap.getCapabilitiesOverride().size());
887             assertTrue(ldap.getCapabilitiesOverride().contains(ConnectorCapability.SEARCH));
888 
889             // 6. update now fails
890             groupUR = new GroupUR();
891             groupUR.setKey(group.getKey());
892             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "fourth")).
893                     operation(PatchOperation.ADD_REPLACE).build());
894 
895             result = updateGroup(groupUR);
896             assertNotNull(result);
897             assertEquals(1, result.getPropagationStatuses().size());
898             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
899             assertEquals(ExecStatus.NOT_ATTEMPTED, result.getPropagationStatuses().get(0).getStatus());
900         } finally {
901             ldap.getCapabilitiesOverride().clear();
902             ldap.setOverrideCapabilities(false);
903             RESOURCE_SERVICE.update(ldap);
904         }
905     }
906 
907     @Test
908     public void typeExtensions() {
909         TypeExtensionTO typeExtension = new TypeExtensionTO();
910         typeExtension.setAnyType(AnyTypeKind.USER.name());
911         typeExtension.getAuxClasses().add("csv");
912 
913         GroupCR groupCR = getBasicSample("typeExtensions");
914         groupCR.getTypeExtensions().add(typeExtension);
915 
916         GroupTO groupTO = createGroup(groupCR).getEntity();
917         assertNotNull(groupTO);
918         assertEquals(1, groupTO.getTypeExtensions().size());
919         assertEquals(1, groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().size());
920         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("csv"));
921 
922         typeExtension = new TypeExtensionTO();
923         typeExtension.setAnyType(AnyTypeKind.USER.name());
924         typeExtension.getAuxClasses().add("csv");
925         typeExtension.getAuxClasses().add("other");
926 
927         GroupUR groupUR = new GroupUR();
928         groupUR.setKey(groupTO.getKey());
929         groupUR.getTypeExtensions().add(typeExtension);
930 
931         groupTO = updateGroup(groupUR).getEntity();
932         assertNotNull(groupTO);
933         assertEquals(1, groupTO.getTypeExtensions().size());
934         assertEquals(2, groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().size());
935         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("csv"));
936         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("other"));
937     }
938 
939     @Test
940     public void provisionMembers() throws InterruptedException {
941         assumeFalse(IS_EXT_SEARCH_ENABLED);
942 
943         // 1. create group without resources
944         GroupCR groupCR = getBasicSample("forProvision");
945         GroupTO groupTO = createGroup(groupCR).getEntity();
946 
947         // 2. create user with such group assigned
948         UserCR userCR = UserITCase.getUniqueSample("forProvision@syncope.apache.org");
949         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
950         UserTO userTO = createUser(userCR).getEntity();
951 
952         // 3. modify the group by assiging the LDAP resource
953         GroupUR groupUR = new GroupUR();
954         groupUR.setKey(groupTO.getKey());
955         groupUR.getResources().add(new StringPatchItem.Builder().value(RESOURCE_NAME_LDAP).build());
956         ProvisioningResult<GroupTO> groupUpdateResult = updateGroup(groupUR);
957 
958         PropagationStatus propStatus = groupUpdateResult.getPropagationStatuses().get(0);
959         assertEquals(RESOURCE_NAME_LDAP, propStatus.getResource());
960         assertEquals(ExecStatus.SUCCESS, propStatus.getStatus());
961 
962         // 4. verify that the user above is not found on LDAP
963         try {
964             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
965             fail("This should not happen");
966         } catch (SyncopeClientException e) {
967             assertEquals(ClientExceptionType.NotFound, e.getType());
968         }
969 
970         try {
971             // 5. provision group members
972             ExecTO exec = GROUP_SERVICE.provisionMembers(groupTO.getKey(), ProvisionAction.PROVISION);
973             assertNotNull(exec.getRefKey());
974 
975             AtomicReference<List<ExecTO>> execs = new AtomicReference<>();
976             await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
977                 try {
978                     execs.set(TASK_SERVICE.read(TaskType.SCHEDULED, exec.getRefKey(), true).getExecutions());
979                     return !execs.get().isEmpty();
980                 } catch (Exception e) {
981                     return false;
982                 }
983             });
984             assertEquals(TaskJob.Status.SUCCESS.name(), execs.get().get(0).getStatus());
985 
986             // 6. verify that the user above is now fond on LDAP
987             ConnObject userOnLdap =
988                     RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
989             assertNotNull(userOnLdap);
990 
991             // 7. attempt to execute the same task again: no errors
992             assertDoesNotThrow(() -> GROUP_SERVICE.provisionMembers(groupTO.getKey(), ProvisionAction.PROVISION));
993         } finally {
994             GROUP_SERVICE.delete(groupTO.getKey());
995             USER_SERVICE.delete(userTO.getKey());
996         }
997     }
998 
999     @Test
1000     public void unlimitedMembership() {
1001         GroupCR groupCR = new GroupCR();
1002         groupCR.setName("unlimited" + getUUIDString());
1003         groupCR.setRealm("/even/two");
1004         GroupTO groupTO = createGroup(groupCR).getEntity();
1005 
1006         UserCR userCR = UserITCase.getUniqueSample("unlimited@syncope.apache.org");
1007         userCR.setRealm(SyncopeConstants.ROOT_REALM);
1008         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
1009         UserTO userTO = createUser(userCR).getEntity();
1010 
1011         assertFalse(userTO.getMemberships().isEmpty());
1012         assertEquals(groupTO.getKey(), userTO.getMemberships().get(0).getGroupKey());
1013     }
1014 
1015     @Test
1016     public void issue178() {
1017         GroupCR groupCR = new GroupCR();
1018         groupCR.setName("torename" + getUUIDString());
1019         groupCR.setRealm(SyncopeConstants.ROOT_REALM);
1020 
1021         GroupTO actual = createGroup(groupCR).getEntity();
1022 
1023         assertNotNull(actual);
1024         assertEquals(groupCR.getName(), actual.getName());
1025 
1026         GroupUR groupUR = new GroupUR();
1027         groupUR.setKey(actual.getKey());
1028         groupUR.setName(new StringReplacePatchItem.Builder().value("renamed" + getUUIDString()).build());
1029 
1030         actual = updateGroup(groupUR).getEntity();
1031         assertNotNull(actual);
1032         assertEquals(groupUR.getName().getValue(), actual.getName());
1033     }
1034 
1035     @Test
1036     public void issueSYNCOPE632() {
1037         DerSchemaTO orig = SCHEMA_SERVICE.read(SchemaType.DERIVED, "displayProperty");
1038         DerSchemaTO modified = SerializationUtils.clone(orig);
1039         modified.setExpression("icon + '_' + show");
1040 
1041         GroupCR groupCR = GroupITCase.getSample("lastGroup");
1042         GroupTO groupTO = null;
1043         try {
1044             SCHEMA_SERVICE.update(SchemaType.DERIVED, modified);
1045 
1046             // 0. create group
1047             groupCR.getPlainAttrs().add(attr("icon", "anIcon"));
1048             groupCR.getPlainAttrs().add(attr("show", "true"));
1049             groupCR.getResources().clear();
1050 
1051             groupTO = createGroup(groupCR).getEntity();
1052             assertNotNull(groupTO);
1053 
1054             // 1. create new LDAP resource having ConnObjectKey mapped to a derived attribute
1055             ResourceTO newLDAP = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
1056             newLDAP.setKey("new-ldap");
1057             newLDAP.setPropagationPriority(0);
1058 
1059             for (Provision provision : newLDAP.getProvisions()) {
1060                 provision.getVirSchemas().clear();
1061             }
1062 
1063             Mapping mapping = newLDAP.getProvision(AnyTypeKind.GROUP.name()).get().getMapping();
1064 
1065             Item connObjectKey = mapping.getConnObjectKeyItem().get();
1066             connObjectKey.setIntAttrName("displayProperty");
1067             connObjectKey.setPurpose(MappingPurpose.PROPAGATION);
1068             mapping.setConnObjectKeyItem(connObjectKey);
1069             mapping.setConnObjectLink("'cn=' + displayProperty + ',ou=groups,o=isp'");
1070 
1071             Item description = new Item();
1072             description.setIntAttrName("key");
1073             description.setExtAttrName("description");
1074             description.setPurpose(MappingPurpose.PROPAGATION);
1075             mapping.add(description);
1076 
1077             newLDAP = createResource(newLDAP);
1078             assertNotNull(newLDAP);
1079 
1080             // 2. update group and give the resource created above
1081             GroupUR groupUR = new GroupUR();
1082             groupUR.setKey(groupTO.getKey());
1083             groupUR.getResources().add(new StringPatchItem.Builder().
1084                     operation(PatchOperation.ADD_REPLACE).
1085                     value("new-ldap").build());
1086 
1087             groupTO = updateGroup(groupUR).getEntity();
1088             assertNotNull(groupTO);
1089 
1090             // 3. update the group
1091             groupUR = new GroupUR();
1092             groupUR.setKey(groupTO.getKey());
1093             groupUR.getPlainAttrs().add(attrAddReplacePatch("icon", "anotherIcon"));
1094 
1095             groupTO = updateGroup(groupUR).getEntity();
1096             assertNotNull(groupTO);
1097 
1098             // 4. check that a single group exists in LDAP for the group created and updated above
1099             int entries = 0;
1100             DirContext ctx = null;
1101             try {
1102                 ctx = getLdapResourceDirContext(null, null);
1103 
1104                 SearchControls ctls = new SearchControls();
1105                 ctls.setReturningAttributes(new String[] { "*", "+" });
1106                 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1107 
1108                 NamingEnumeration<SearchResult> result =
1109                         ctx.search("ou=groups,o=isp", "(description=" + groupTO.getKey() + ')', ctls);
1110                 while (result.hasMore()) {
1111                     result.next();
1112                     entries++;
1113                 }
1114             } catch (Exception e) {
1115                 // ignore
1116             } finally {
1117                 if (ctx != null) {
1118                     try {
1119                         ctx.close();
1120                     } catch (NamingException e) {
1121                         // ignore
1122                     }
1123                 }
1124             }
1125 
1126             assertEquals(1, entries);
1127         } finally {
1128             SCHEMA_SERVICE.update(SchemaType.DERIVED, orig);
1129             Optional.ofNullable(groupTO).ifPresent(g -> GROUP_SERVICE.delete(g.getKey()));
1130             RESOURCE_SERVICE.delete("new-ldap");
1131         }
1132     }
1133 
1134     @Test
1135     public void issueSYNCOPE717() {
1136         String doubleSchemaName = "double" + getUUIDString();
1137 
1138         // 1. create double schema without conversion pattern
1139         PlainSchemaTO schema = new PlainSchemaTO();
1140         schema.setKey(doubleSchemaName);
1141         schema.setType(AttrSchemaType.Double);
1142 
1143         schema = createSchema(SchemaType.PLAIN, schema);
1144         assertNotNull(schema);
1145         assertNull(schema.getConversionPattern());
1146 
1147         AnyTypeClassTO minimalGroup = ANY_TYPE_CLASS_SERVICE.read("minimal group");
1148         assertNotNull(minimalGroup);
1149         minimalGroup.getPlainSchemas().add(doubleSchemaName);
1150         ANY_TYPE_CLASS_SERVICE.update(minimalGroup);
1151 
1152         // 2. create group, provide valid input value
1153         GroupCR groupCR = GroupITCase.getBasicSample("syncope717");
1154         groupCR.getPlainAttrs().add(attr(doubleSchemaName, "11.23"));
1155 
1156         GroupTO groupTO = createGroup(groupCR).getEntity();
1157         assertNotNull(groupTO);
1158         assertEquals("11.23", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1159 
1160         // 3. update schema, set conversion pattern
1161         schema = SCHEMA_SERVICE.read(SchemaType.PLAIN, schema.getKey());
1162         schema.setConversionPattern("0.000");
1163         SCHEMA_SERVICE.update(SchemaType.PLAIN, schema);
1164 
1165         // 4. re-read group, verify that pattern was applied
1166         groupTO = GROUP_SERVICE.read(groupTO.getKey());
1167         assertNotNull(groupTO);
1168         assertEquals("11.230", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1169 
1170         // 5. modify group with new double value
1171         GroupUR groupUR = new GroupUR();
1172         groupUR.setKey(groupTO.getKey());
1173         groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr(doubleSchemaName, "11.257")).build());
1174 
1175         groupTO = updateGroup(groupUR).getEntity();
1176         assertNotNull(groupTO);
1177         assertEquals("11.257", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1178 
1179         // 6. update schema, unset conversion pattern
1180         schema.setConversionPattern(null);
1181         SCHEMA_SERVICE.update(SchemaType.PLAIN, schema);
1182 
1183         // 7. modify group with new double value, verify that no pattern is applied
1184         groupUR = new GroupUR();
1185         groupUR.setKey(groupTO.getKey());
1186         groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr(doubleSchemaName, "11.23")).build());
1187 
1188         groupTO = updateGroup(groupUR).getEntity();
1189         assertNotNull(groupTO);
1190         assertEquals("11.23", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1191     }
1192 
1193     @Test
1194     public void issueSYNCOPE1467() {
1195         GroupTO groupTO = null;
1196         try {
1197             GroupCR groupCR = new GroupCR();
1198             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
1199             groupCR.setName("issueSYNCOPE1467");
1200             groupCR.getResources().add(RESOURCE_NAME_LDAP);
1201 
1202             groupTO = createGroup(groupCR).getEntity();
1203             assertNotNull(groupTO);
1204             assertTrue(groupTO.getResources().contains(RESOURCE_NAME_LDAP));
1205 
1206             ConnObject connObjectTO =
1207                     RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
1208             assertNotNull(connObjectTO);
1209             assertEquals("issueSYNCOPE1467", connObjectTO.getAttr("cn").get().getValues().get(0));
1210 
1211             GroupUR groupUR = new GroupUR();
1212             groupUR.setKey(groupTO.getKey());
1213             groupUR.setName(new StringReplacePatchItem.Builder().value("fixedSYNCOPE1467").build());
1214 
1215             ProvisioningResult<GroupTO> result = updateGroup(groupUR);
1216             assertEquals(1, result.getPropagationStatuses().size());
1217             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
1218             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1219 
1220             connObjectTO = RESOURCE_SERVICE.readConnObject(
1221                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
1222             assertNotNull(connObjectTO);
1223             assertEquals("fixedSYNCOPE1467", connObjectTO.getAttr("cn").get().getValues().get(0));
1224         } finally {
1225             Optional.ofNullable(groupTO).ifPresent(g -> GROUP_SERVICE.delete(g.getKey()));
1226         }
1227     }
1228 
1229     @Test
1230     public void issueSYNCOPE1472() {
1231         // 1. update group artDirector by assigning twice resource-testdb and auxiliary class csv
1232         GroupUR groupUR = new GroupUR();
1233         groupUR.setKey("ece66293-8f31-4a84-8e8d-23da36e70846");
1234         groupUR.getResources().add(new StringPatchItem.Builder()
1235                 .value(RESOURCE_NAME_TESTDB)
1236                 .operation(PatchOperation.ADD_REPLACE)
1237                 .build());
1238         groupUR.getAuxClasses().add(new StringPatchItem.Builder()
1239                 .operation(PatchOperation.ADD_REPLACE)
1240                 .value("csv")
1241                 .build());
1242         for (int i = 0; i < 2; i++) {
1243             updateGroup(groupUR);
1244         }
1245 
1246         // 2. remove resources and auxiliary classes
1247         groupUR.getResources().clear();
1248         groupUR.getResources().add(new StringPatchItem.Builder()
1249                 .value(RESOURCE_NAME_TESTDB)
1250                 .operation(PatchOperation.DELETE)
1251                 .build());
1252         groupUR.getAuxClasses().clear();
1253         groupUR.getAuxClasses().add(new StringPatchItem.Builder()
1254                 .value("csv")
1255                 .operation(PatchOperation.DELETE)
1256                 .build());
1257 
1258         updateGroup(groupUR);
1259 
1260         GroupTO groupTO = GROUP_SERVICE.read("ece66293-8f31-4a84-8e8d-23da36e70846");
1261         assertFalse(groupTO.getResources().contains(RESOURCE_NAME_TESTDB), "Should not contain removed resources");
1262         assertFalse(groupTO.getAuxClasses().contains("csv"), "Should not contain removed auxiliary classes");
1263     }
1264 }