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 = createGroup(groupCR).getEntity();
683         assertNotNull(group);
684 
685         List<MembershipTO> memberships = USER_SERVICE.read("c9b2dec2-00a7-4855-97c0-d854842b4b24").getDynMemberships();
686         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(group.getKey())));
687         assertEquals(1, GROUP_SERVICE.read(group.getKey()).getDynamicUserMembershipCount());
688 
689         GROUP_SERVICE.update(new GroupUR.Builder(group.getKey()).udynMembershipCond("cool==false").build());
690 
691         assertTrue(USER_SERVICE.read("c9b2dec2-00a7-4855-97c0-d854842b4b24").getDynMemberships().isEmpty());
692         assertEquals(0, GROUP_SERVICE.read(group.getKey()).getDynamicUserMembershipCount());
693     }
694 
695     @Test
696     public void aDynMembership() {
697         String fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").notNullValue().query();
698 
699         // 1. create group with a given aDynMembership condition
700         GroupCR groupCR = getBasicSample("aDynMembership");
701         groupCR.getADynMembershipConds().put(PRINTER, fiql);
702         GroupTO group = createGroup(groupCR).getEntity();
703         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
704 
705         if (IS_EXT_SEARCH_ENABLED) {
706             try {
707                 Thread.sleep(2000);
708             } catch (InterruptedException ex) {
709                 // ignore
710             }
711         }
712 
713         group = GROUP_SERVICE.read(group.getKey());
714         String groupKey = group.getKey();
715         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
716 
717         // verify that the condition is dynamically applied
718         AnyObjectCR newAnyCR = AnyObjectITCase.getSample("aDynMembership");
719         newAnyCR.getResources().clear();
720         AnyObjectTO newAny = createAnyObject(newAnyCR).getEntity();
721         assertNotNull(newAny.getPlainAttr("location"));
722         List<MembershipTO> memberships = ANY_OBJECT_SERVICE.read(
723                 "fc6dbc3a-6c07-4965-8781-921e7401a4a5").getDynMemberships();
724         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
725 
726         memberships = ANY_OBJECT_SERVICE.read(
727                 "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").getDynMemberships();
728         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
729 
730         memberships = ANY_OBJECT_SERVICE.read(newAny.getKey()).getDynMemberships();
731         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
732 
733         // 2. update group and change aDynMembership condition
734         fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").nullValue().query();
735 
736         GroupUR groupUR = new GroupUR();
737         groupUR.setKey(group.getKey());
738         groupUR.getADynMembershipConds().put(PRINTER, fiql);
739 
740         group = updateGroup(groupUR).getEntity();
741         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
742 
743         group = GROUP_SERVICE.read(group.getKey());
744         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
745 
746         // verify that the condition is dynamically applied
747         AnyObjectUR anyObjectUR = new AnyObjectUR();
748         anyObjectUR.setKey(newAny.getKey());
749         anyObjectUR.getPlainAttrs().add(new AttrPatch.Builder(new Attr.Builder("location").build()).
750                 operation(PatchOperation.DELETE).
751                 build());
752         newAny = updateAnyObject(anyObjectUR).getEntity();
753         assertFalse(newAny.getPlainAttr("location").isPresent());
754 
755         memberships = ANY_OBJECT_SERVICE.read(
756                 "fc6dbc3a-6c07-4965-8781-921e7401a4a5").getDynMemberships();
757         assertFalse(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
758         memberships = ANY_OBJECT_SERVICE.read(
759                 "8559d14d-58c2-46eb-a2d4-a7d35161e8f8").getDynMemberships();
760         assertFalse(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
761         memberships = ANY_OBJECT_SERVICE.read(newAny.getKey()).getDynMemberships();
762         assertTrue(memberships.stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
763     }
764 
765     @Test
766     public void aDynMembershipCount() {
767         // Create a new printer as a dynamic member of a new group
768         GroupCR groupCR = getBasicSample("aDynamicMembership");
769         String fiql = SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("location").equalTo("home").query();
770         groupCR.getADynMembershipConds().put(PRINTER, fiql);
771         GroupTO group = createGroup(groupCR).getEntity();
772 
773         AnyObjectCR printerCR = new AnyObjectCR();
774         printerCR.setRealm(SyncopeConstants.ROOT_REALM);
775         printerCR.setName("Printer_" + getUUIDString());
776         printerCR.setType(PRINTER);
777         printerCR.getPlainAttrs().add(new Attr.Builder("location").value("home").build());
778         AnyObjectTO printer = createAnyObject(printerCR).getEntity();
779 
780         group = GROUP_SERVICE.read(group.getKey());
781         assertEquals(0, group.getStaticAnyObjectMembershipCount());
782         assertEquals(1, group.getDynamicAnyObjectMembershipCount());
783 
784         ANY_OBJECT_SERVICE.delete(printer.getKey());
785         GROUP_SERVICE.delete(group.getKey());
786     }
787 
788     @Test
789     public void aStaticMembershipCount() {
790         // Create a new printer as a static member of a new group
791         GroupCR groupCR = getBasicSample("aStaticMembership");
792         GroupTO group = createGroup(groupCR).getEntity();
793 
794         AnyObjectCR printerCR = new AnyObjectCR();
795         printerCR.setRealm(SyncopeConstants.ROOT_REALM);
796         printerCR.setName("Printer_" + getUUIDString());
797         printerCR.setType(PRINTER);
798         printerCR.getMemberships().add(new MembershipTO.Builder(group.getKey()).build());
799         AnyObjectTO printer = createAnyObject(printerCR).getEntity();
800 
801         group = GROUP_SERVICE.read(group.getKey());
802         assertEquals(0, group.getDynamicAnyObjectMembershipCount());
803         assertEquals(1, group.getStaticAnyObjectMembershipCount());
804 
805         ANY_OBJECT_SERVICE.delete(printer.getKey());
806         GROUP_SERVICE.delete(group.getKey());
807     }
808 
809     @Test
810     public void capabilitiesOverride() {
811         // resource with no capability override
812         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
813         assertNotNull(ldap);
814         assertFalse(ldap.isOverrideCapabilities());
815         assertTrue(ldap.getCapabilitiesOverride().isEmpty());
816 
817         // connector with all required for create and update
818         ConnInstanceTO conn = CONNECTOR_SERVICE.read(ldap.getConnector(), null);
819         assertNotNull(conn);
820         assertTrue(conn.getCapabilities().contains(ConnectorCapability.CREATE));
821         assertTrue(conn.getCapabilities().contains(ConnectorCapability.UPDATE));
822 
823         try {
824             // 1. create succeeds
825             GroupCR groupCR = getSample("syncope714");
826             groupCR.getPlainAttrs().add(attr("title", "first"));
827             groupCR.getResources().add(RESOURCE_NAME_LDAP);
828 
829             ProvisioningResult<GroupTO> result = createGroup(groupCR);
830             assertNotNull(result);
831             assertEquals(1, result.getPropagationStatuses().size());
832             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
833             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
834             GroupTO group = result.getEntity();
835 
836             // 2. update succeeds
837             GroupUR groupUR = new GroupUR();
838             groupUR.setKey(group.getKey());
839             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "second")).
840                     operation(PatchOperation.ADD_REPLACE).build());
841 
842             result = updateGroup(groupUR);
843             assertNotNull(result);
844             assertEquals(1, result.getPropagationStatuses().size());
845             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
846             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
847             group = result.getEntity();
848 
849             // 3. set capability override with only search allowed, but not enable
850             ldap.getCapabilitiesOverride().add(ConnectorCapability.SEARCH);
851             RESOURCE_SERVICE.update(ldap);
852             ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
853             assertNotNull(ldap);
854             assertFalse(ldap.isOverrideCapabilities());
855             assertEquals(1, ldap.getCapabilitiesOverride().size());
856             assertTrue(ldap.getCapabilitiesOverride().contains(ConnectorCapability.SEARCH));
857 
858             // 4. update succeeds again
859             groupUR = new GroupUR();
860             groupUR.setKey(group.getKey());
861             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "third")).
862                     operation(PatchOperation.ADD_REPLACE).build());
863 
864             result = updateGroup(groupUR);
865             assertNotNull(result);
866             assertEquals(1, result.getPropagationStatuses().size());
867             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
868             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
869             group = result.getEntity();
870 
871             // 5. enable capability override
872             ldap.setOverrideCapabilities(true);
873             RESOURCE_SERVICE.update(ldap);
874             ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
875             assertNotNull(ldap);
876             assertTrue(ldap.isOverrideCapabilities());
877             assertEquals(1, ldap.getCapabilitiesOverride().size());
878             assertTrue(ldap.getCapabilitiesOverride().contains(ConnectorCapability.SEARCH));
879 
880             // 6. update now fails
881             groupUR = new GroupUR();
882             groupUR.setKey(group.getKey());
883             groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr("title", "fourth")).
884                     operation(PatchOperation.ADD_REPLACE).build());
885 
886             result = updateGroup(groupUR);
887             assertNotNull(result);
888             assertEquals(1, result.getPropagationStatuses().size());
889             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
890             assertEquals(ExecStatus.NOT_ATTEMPTED, result.getPropagationStatuses().get(0).getStatus());
891         } finally {
892             ldap.getCapabilitiesOverride().clear();
893             ldap.setOverrideCapabilities(false);
894             RESOURCE_SERVICE.update(ldap);
895         }
896     }
897 
898     @Test
899     public void typeExtensions() {
900         TypeExtensionTO typeExtension = new TypeExtensionTO();
901         typeExtension.setAnyType(AnyTypeKind.USER.name());
902         typeExtension.getAuxClasses().add("csv");
903 
904         GroupCR groupCR = getBasicSample("typeExtensions");
905         groupCR.getTypeExtensions().add(typeExtension);
906 
907         GroupTO groupTO = createGroup(groupCR).getEntity();
908         assertNotNull(groupTO);
909         assertEquals(1, groupTO.getTypeExtensions().size());
910         assertEquals(1, groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().size());
911         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("csv"));
912 
913         typeExtension = new TypeExtensionTO();
914         typeExtension.setAnyType(AnyTypeKind.USER.name());
915         typeExtension.getAuxClasses().add("csv");
916         typeExtension.getAuxClasses().add("other");
917 
918         GroupUR groupUR = new GroupUR();
919         groupUR.setKey(groupTO.getKey());
920         groupUR.getTypeExtensions().add(typeExtension);
921 
922         groupTO = updateGroup(groupUR).getEntity();
923         assertNotNull(groupTO);
924         assertEquals(1, groupTO.getTypeExtensions().size());
925         assertEquals(2, groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().size());
926         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("csv"));
927         assertTrue(groupTO.getTypeExtension(AnyTypeKind.USER.name()).get().getAuxClasses().contains("other"));
928     }
929 
930     @Test
931     public void provisionMembers() throws InterruptedException {
932         assumeFalse(IS_EXT_SEARCH_ENABLED);
933 
934         // 1. create group without resources
935         GroupCR groupCR = getBasicSample("forProvision");
936         GroupTO groupTO = createGroup(groupCR).getEntity();
937 
938         // 2. create user with such group assigned
939         UserCR userCR = UserITCase.getUniqueSample("forProvision@syncope.apache.org");
940         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
941         UserTO userTO = createUser(userCR).getEntity();
942 
943         // 3. modify the group by assiging the LDAP resource
944         GroupUR groupUR = new GroupUR();
945         groupUR.setKey(groupTO.getKey());
946         groupUR.getResources().add(new StringPatchItem.Builder().value(RESOURCE_NAME_LDAP).build());
947         ProvisioningResult<GroupTO> groupUpdateResult = updateGroup(groupUR);
948 
949         PropagationStatus propStatus = groupUpdateResult.getPropagationStatuses().get(0);
950         assertEquals(RESOURCE_NAME_LDAP, propStatus.getResource());
951         assertEquals(ExecStatus.SUCCESS, propStatus.getStatus());
952 
953         // 4. verify that the user above is not found on LDAP
954         try {
955             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
956             fail("This should not happen");
957         } catch (SyncopeClientException e) {
958             assertEquals(ClientExceptionType.NotFound, e.getType());
959         }
960 
961         try {
962             // 5. provision group members
963             ExecTO exec = GROUP_SERVICE.provisionMembers(groupTO.getKey(), ProvisionAction.PROVISION);
964             assertNotNull(exec.getRefKey());
965 
966             AtomicReference<List<ExecTO>> execs = new AtomicReference<>();
967             await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
968                 try {
969                     execs.set(TASK_SERVICE.read(TaskType.SCHEDULED, exec.getRefKey(), true).getExecutions());
970                     return !execs.get().isEmpty();
971                 } catch (Exception e) {
972                     return false;
973                 }
974             });
975             assertEquals(TaskJob.Status.SUCCESS.name(), execs.get().get(0).getStatus());
976 
977             // 6. verify that the user above is now fond on LDAP
978             ConnObject userOnLdap =
979                     RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
980             assertNotNull(userOnLdap);
981 
982             // 7. attempt to execute the same task again: no errors
983             assertDoesNotThrow(() -> GROUP_SERVICE.provisionMembers(groupTO.getKey(), ProvisionAction.PROVISION));
984         } finally {
985             GROUP_SERVICE.delete(groupTO.getKey());
986             USER_SERVICE.delete(userTO.getKey());
987         }
988     }
989 
990     @Test
991     public void unlimitedMembership() {
992         GroupCR groupCR = new GroupCR();
993         groupCR.setName("unlimited" + getUUIDString());
994         groupCR.setRealm("/even/two");
995         GroupTO groupTO = createGroup(groupCR).getEntity();
996 
997         UserCR userCR = UserITCase.getUniqueSample("unlimited@syncope.apache.org");
998         userCR.setRealm(SyncopeConstants.ROOT_REALM);
999         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
1000         UserTO userTO = createUser(userCR).getEntity();
1001 
1002         assertFalse(userTO.getMemberships().isEmpty());
1003         assertEquals(groupTO.getKey(), userTO.getMemberships().get(0).getGroupKey());
1004     }
1005 
1006     @Test
1007     public void issue178() {
1008         GroupCR groupCR = new GroupCR();
1009         groupCR.setName("torename" + getUUIDString());
1010         groupCR.setRealm(SyncopeConstants.ROOT_REALM);
1011 
1012         GroupTO actual = createGroup(groupCR).getEntity();
1013 
1014         assertNotNull(actual);
1015         assertEquals(groupCR.getName(), actual.getName());
1016 
1017         GroupUR groupUR = new GroupUR();
1018         groupUR.setKey(actual.getKey());
1019         groupUR.setName(new StringReplacePatchItem.Builder().value("renamed" + getUUIDString()).build());
1020 
1021         actual = updateGroup(groupUR).getEntity();
1022         assertNotNull(actual);
1023         assertEquals(groupUR.getName().getValue(), actual.getName());
1024     }
1025 
1026     @Test
1027     public void issueSYNCOPE632() {
1028         DerSchemaTO orig = SCHEMA_SERVICE.read(SchemaType.DERIVED, "displayProperty");
1029         DerSchemaTO modified = SerializationUtils.clone(orig);
1030         modified.setExpression("icon + '_' + show");
1031 
1032         GroupCR groupCR = GroupITCase.getSample("lastGroup");
1033         GroupTO groupTO = null;
1034         try {
1035             SCHEMA_SERVICE.update(SchemaType.DERIVED, modified);
1036 
1037             // 0. create group
1038             groupCR.getPlainAttrs().add(attr("icon", "anIcon"));
1039             groupCR.getPlainAttrs().add(attr("show", "true"));
1040             groupCR.getResources().clear();
1041 
1042             groupTO = createGroup(groupCR).getEntity();
1043             assertNotNull(groupTO);
1044 
1045             // 1. create new LDAP resource having ConnObjectKey mapped to a derived attribute
1046             ResourceTO newLDAP = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
1047             newLDAP.setKey("new-ldap");
1048             newLDAP.setPropagationPriority(0);
1049 
1050             for (Provision provision : newLDAP.getProvisions()) {
1051                 provision.getVirSchemas().clear();
1052             }
1053 
1054             Mapping mapping = newLDAP.getProvision(AnyTypeKind.GROUP.name()).get().getMapping();
1055 
1056             Item connObjectKey = mapping.getConnObjectKeyItem().get();
1057             connObjectKey.setIntAttrName("displayProperty");
1058             connObjectKey.setPurpose(MappingPurpose.PROPAGATION);
1059             mapping.setConnObjectKeyItem(connObjectKey);
1060             mapping.setConnObjectLink("'cn=' + displayProperty + ',ou=groups,o=isp'");
1061 
1062             Item description = new Item();
1063             description.setIntAttrName("key");
1064             description.setExtAttrName("description");
1065             description.setPurpose(MappingPurpose.PROPAGATION);
1066             mapping.add(description);
1067 
1068             newLDAP = createResource(newLDAP);
1069             assertNotNull(newLDAP);
1070 
1071             // 2. update group and give the resource created above
1072             GroupUR groupUR = new GroupUR();
1073             groupUR.setKey(groupTO.getKey());
1074             groupUR.getResources().add(new StringPatchItem.Builder().
1075                     operation(PatchOperation.ADD_REPLACE).
1076                     value("new-ldap").build());
1077 
1078             groupTO = updateGroup(groupUR).getEntity();
1079             assertNotNull(groupTO);
1080 
1081             // 3. update the group
1082             groupUR = new GroupUR();
1083             groupUR.setKey(groupTO.getKey());
1084             groupUR.getPlainAttrs().add(attrAddReplacePatch("icon", "anotherIcon"));
1085 
1086             groupTO = updateGroup(groupUR).getEntity();
1087             assertNotNull(groupTO);
1088 
1089             // 4. check that a single group exists in LDAP for the group created and updated above
1090             int entries = 0;
1091             DirContext ctx = null;
1092             try {
1093                 ctx = getLdapResourceDirContext(null, null);
1094 
1095                 SearchControls ctls = new SearchControls();
1096                 ctls.setReturningAttributes(new String[] { "*", "+" });
1097                 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1098 
1099                 NamingEnumeration<SearchResult> result =
1100                         ctx.search("ou=groups,o=isp", "(description=" + groupTO.getKey() + ')', ctls);
1101                 while (result.hasMore()) {
1102                     result.next();
1103                     entries++;
1104                 }
1105             } catch (Exception e) {
1106                 // ignore
1107             } finally {
1108                 if (ctx != null) {
1109                     try {
1110                         ctx.close();
1111                     } catch (NamingException e) {
1112                         // ignore
1113                     }
1114                 }
1115             }
1116 
1117             assertEquals(1, entries);
1118         } finally {
1119             SCHEMA_SERVICE.update(SchemaType.DERIVED, orig);
1120             Optional.ofNullable(groupTO).ifPresent(g -> GROUP_SERVICE.delete(g.getKey()));
1121             RESOURCE_SERVICE.delete("new-ldap");
1122         }
1123     }
1124 
1125     @Test
1126     public void issueSYNCOPE717() {
1127         String doubleSchemaName = "double" + getUUIDString();
1128 
1129         // 1. create double schema without conversion pattern
1130         PlainSchemaTO schema = new PlainSchemaTO();
1131         schema.setKey(doubleSchemaName);
1132         schema.setType(AttrSchemaType.Double);
1133 
1134         schema = createSchema(SchemaType.PLAIN, schema);
1135         assertNotNull(schema);
1136         assertNull(schema.getConversionPattern());
1137 
1138         AnyTypeClassTO minimalGroup = ANY_TYPE_CLASS_SERVICE.read("minimal group");
1139         assertNotNull(minimalGroup);
1140         minimalGroup.getPlainSchemas().add(doubleSchemaName);
1141         ANY_TYPE_CLASS_SERVICE.update(minimalGroup);
1142 
1143         // 2. create group, provide valid input value
1144         GroupCR groupCR = GroupITCase.getBasicSample("syncope717");
1145         groupCR.getPlainAttrs().add(attr(doubleSchemaName, "11.23"));
1146 
1147         GroupTO groupTO = createGroup(groupCR).getEntity();
1148         assertNotNull(groupTO);
1149         assertEquals("11.23", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1150 
1151         // 3. update schema, set conversion pattern
1152         schema = SCHEMA_SERVICE.read(SchemaType.PLAIN, schema.getKey());
1153         schema.setConversionPattern("0.000");
1154         SCHEMA_SERVICE.update(SchemaType.PLAIN, schema);
1155 
1156         // 4. re-read group, verify that pattern was applied
1157         groupTO = GROUP_SERVICE.read(groupTO.getKey());
1158         assertNotNull(groupTO);
1159         assertEquals("11.230", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1160 
1161         // 5. modify group with new double value
1162         GroupUR groupUR = new GroupUR();
1163         groupUR.setKey(groupTO.getKey());
1164         groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr(doubleSchemaName, "11.257")).build());
1165 
1166         groupTO = updateGroup(groupUR).getEntity();
1167         assertNotNull(groupTO);
1168         assertEquals("11.257", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1169 
1170         // 6. update schema, unset conversion pattern
1171         schema.setConversionPattern(null);
1172         SCHEMA_SERVICE.update(SchemaType.PLAIN, schema);
1173 
1174         // 7. modify group with new double value, verify that no pattern is applied
1175         groupUR = new GroupUR();
1176         groupUR.setKey(groupTO.getKey());
1177         groupUR.getPlainAttrs().add(new AttrPatch.Builder(attr(doubleSchemaName, "11.23")).build());
1178 
1179         groupTO = updateGroup(groupUR).getEntity();
1180         assertNotNull(groupTO);
1181         assertEquals("11.23", groupTO.getPlainAttr(doubleSchemaName).get().getValues().get(0));
1182     }
1183 
1184     @Test
1185     public void issueSYNCOPE1467() {
1186         GroupTO groupTO = null;
1187         try {
1188             GroupCR groupCR = new GroupCR();
1189             groupCR.setRealm(SyncopeConstants.ROOT_REALM);
1190             groupCR.setName("issueSYNCOPE1467");
1191             groupCR.getResources().add(RESOURCE_NAME_LDAP);
1192 
1193             groupTO = createGroup(groupCR).getEntity();
1194             assertNotNull(groupTO);
1195             assertTrue(groupTO.getResources().contains(RESOURCE_NAME_LDAP));
1196 
1197             ConnObject connObjectTO =
1198                     RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
1199             assertNotNull(connObjectTO);
1200             assertEquals("issueSYNCOPE1467", connObjectTO.getAttr("cn").get().getValues().get(0));
1201 
1202             GroupUR groupUR = new GroupUR();
1203             groupUR.setKey(groupTO.getKey());
1204             groupUR.setName(new StringReplacePatchItem.Builder().value("fixedSYNCOPE1467").build());
1205 
1206             ProvisioningResult<GroupTO> result = updateGroup(groupUR);
1207             assertEquals(1, result.getPropagationStatuses().size());
1208             assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
1209             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1210 
1211             connObjectTO = RESOURCE_SERVICE.readConnObject(
1212                     RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
1213             assertNotNull(connObjectTO);
1214             assertEquals("fixedSYNCOPE1467", connObjectTO.getAttr("cn").get().getValues().get(0));
1215         } finally {
1216             Optional.ofNullable(groupTO).ifPresent(g -> GROUP_SERVICE.delete(g.getKey()));
1217         }
1218     }
1219 
1220     @Test
1221     public void issueSYNCOPE1472() {
1222         // 1. update group artDirector by assigning twice resource-testdb and auxiliary class csv
1223         GroupUR groupUR = new GroupUR();
1224         groupUR.setKey("ece66293-8f31-4a84-8e8d-23da36e70846");
1225         groupUR.getResources().add(new StringPatchItem.Builder()
1226                 .value(RESOURCE_NAME_TESTDB)
1227                 .operation(PatchOperation.ADD_REPLACE)
1228                 .build());
1229         groupUR.getAuxClasses().add(new StringPatchItem.Builder()
1230                 .operation(PatchOperation.ADD_REPLACE)
1231                 .value("csv")
1232                 .build());
1233         for (int i = 0; i < 2; i++) {
1234             updateGroup(groupUR);
1235         }
1236 
1237         // 2. remove resources and auxiliary classes
1238         groupUR.getResources().clear();
1239         groupUR.getResources().add(new StringPatchItem.Builder()
1240                 .value(RESOURCE_NAME_TESTDB)
1241                 .operation(PatchOperation.DELETE)
1242                 .build());
1243         groupUR.getAuxClasses().clear();
1244         groupUR.getAuxClasses().add(new StringPatchItem.Builder()
1245                 .value("csv")
1246                 .operation(PatchOperation.DELETE)
1247                 .build());
1248 
1249         updateGroup(groupUR);
1250 
1251         GroupTO groupTO = GROUP_SERVICE.read("ece66293-8f31-4a84-8e8d-23da36e70846");
1252         assertFalse(groupTO.getResources().contains(RESOURCE_NAME_TESTDB), "Should not contain removed resources");
1253         assertFalse(groupTO.getAuxClasses().contains("csv"), "Should not contain removed auxiliary classes");
1254     }
1255 }