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.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  import static org.junit.jupiter.api.Assumptions.assumeTrue;
30  
31  import java.io.IOException;
32  import java.security.AccessControlException;
33  import java.time.OffsetDateTime;
34  import java.time.format.DateTimeFormatter;
35  import java.util.ArrayList;
36  import java.util.Comparator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.UUID;
41  import java.util.function.Function;
42  import java.util.stream.Collectors;
43  import javax.ws.rs.core.GenericType;
44  import javax.ws.rs.core.Response;
45  import org.apache.commons.lang3.RandomStringUtils;
46  import org.apache.commons.lang3.tuple.Triple;
47  import org.apache.cxf.jaxrs.client.WebClient;
48  import org.apache.syncope.client.lib.SyncopeClient;
49  import org.apache.syncope.client.lib.batch.BatchRequest;
50  import org.apache.syncope.common.lib.Attr;
51  import org.apache.syncope.common.lib.SyncopeClientException;
52  import org.apache.syncope.common.lib.SyncopeConstants;
53  import org.apache.syncope.common.lib.policy.AccountPolicyTO;
54  import org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf;
55  import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
56  import org.apache.syncope.common.lib.request.AttrPatch;
57  import org.apache.syncope.common.lib.request.MembershipUR;
58  import org.apache.syncope.common.lib.request.PasswordPatch;
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.StatusR;
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.request.UserUR;
65  import org.apache.syncope.common.lib.to.ConnObject;
66  import org.apache.syncope.common.lib.to.ImplementationTO;
67  import org.apache.syncope.common.lib.to.MembershipTO;
68  import org.apache.syncope.common.lib.to.PagedResult;
69  import org.apache.syncope.common.lib.to.PropagationStatus;
70  import org.apache.syncope.common.lib.to.PropagationTaskTO;
71  import org.apache.syncope.common.lib.to.ProvisioningResult;
72  import org.apache.syncope.common.lib.to.RealmTO;
73  import org.apache.syncope.common.lib.to.ResourceTO;
74  import org.apache.syncope.common.lib.to.UserTO;
75  import org.apache.syncope.common.lib.types.AnyTypeKind;
76  import org.apache.syncope.common.lib.types.ClientExceptionType;
77  import org.apache.syncope.common.lib.types.ExecStatus;
78  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
79  import org.apache.syncope.common.lib.types.ImplementationEngine;
80  import org.apache.syncope.common.lib.types.PatchOperation;
81  import org.apache.syncope.common.lib.types.PolicyType;
82  import org.apache.syncope.common.lib.types.ResourceAssociationAction;
83  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
84  import org.apache.syncope.common.lib.types.StatusRType;
85  import org.apache.syncope.common.lib.types.TaskType;
86  import org.apache.syncope.common.rest.api.RESTHeaders;
87  import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
88  import org.apache.syncope.common.rest.api.beans.AnyQuery;
89  import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
90  import org.apache.syncope.common.rest.api.beans.RealmQuery;
91  import org.apache.syncope.common.rest.api.beans.TaskQuery;
92  import org.apache.syncope.common.rest.api.service.ResourceService;
93  import org.apache.syncope.common.rest.api.service.UserSelfService;
94  import org.apache.syncope.common.rest.api.service.UserService;
95  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
96  import org.apache.syncope.fit.AbstractITCase;
97  import org.apache.syncope.fit.core.reference.TestAccountRuleConf;
98  import org.apache.syncope.fit.core.reference.TestPasswordRuleConf;
99  import org.identityconnectors.framework.common.objects.OperationalAttributes;
100 import org.junit.jupiter.api.Test;
101 
102 public class UserITCase extends AbstractITCase {
103 
104     private static boolean getBooleanAttribute(final ConnObject connObjectTO, final String attrName) {
105         return Boolean.parseBoolean(connObjectTO.getAttr(attrName).get().getValues().get(0));
106     }
107 
108     public static UserCR getUniqueSample(final String email) {
109         return getSample(getUUIDString() + email);
110     }
111 
112     public static UserCR getSample(final String email) {
113         return new UserCR.Builder(SyncopeConstants.ROOT_REALM, email).
114                 password("password123").
115                 plainAttr(attr("fullname", email)).
116                 plainAttr(attr("firstname", email)).
117                 plainAttr(attr("surname", "surname")).
118                 plainAttr(attr("ctype", "a type")).
119                 plainAttr(attr("userId", email)).
120                 plainAttr(attr("email", email)).
121                 plainAttr(attr("loginDate", DateTimeFormatter.ISO_LOCAL_DATE.format(OffsetDateTime.now()))).
122                 build();
123     }
124 
125     @Test
126     public void readPrivileges() {
127         Set<String> privileges = USER_SERVICE.read("rossini").getPrivileges();
128         assertNotNull(privileges);
129         assertEquals(1, privileges.size());
130     }
131 
132     @Test
133     public void createUserWithNoPropagation() {
134         // create a new user
135         UserCR req = getUniqueSample("xxx@xxx.xxx");
136         req.setPassword("password123");
137         req.getResources().add(RESOURCE_NAME_NOPROPAGATION);
138 
139         UserTO userTO = createUser(req).getEntity();
140 
141         // get the propagation task just created
142         PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).
143                 anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).page(1).size(1).build());
144         assertNotNull(tasks);
145         assertFalse(tasks.getResult().isEmpty());
146 
147         PropagationTaskTO taskTO = tasks.getResult().get(0);
148         assertNotNull(taskTO);
149         assertFalse(taskTO.getExecutions().isEmpty());
150         assertEquals(ExecStatus.NOT_ATTEMPTED.name(), taskTO.getExecutions().get(0).getStatus());
151     }
152 
153     @Test
154     public void enforceMandatoryCondition() {
155         UserCR userCR = getUniqueSample("enforce@apache.org");
156         userCR.getResources().add(RESOURCE_NAME_WS2);
157         userCR.setPassword("newPassword12");
158 
159         Attr type = null;
160         for (Attr attr : userCR.getPlainAttrs()) {
161             if ("ctype".equals(attr.getSchema())) {
162                 type = attr;
163             }
164         }
165         assertNotNull(type);
166         userCR.getPlainAttrs().remove(type);
167 
168         try {
169             createUser(userCR).getEntity();
170             fail("This should not happen");
171         } catch (SyncopeClientException e) {
172             assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
173         }
174 
175         userCR.getPlainAttrs().add(type);
176         UserTO userTO = createUser(userCR).getEntity();
177         assertNotNull(userTO);
178     }
179 
180     @Test
181     public void enforceMandatoryConditionOnDerived() {
182         ResourceTO resourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_CSV);
183         assertNotNull(resourceTO);
184         resourceTO.setKey("resource-csv-enforcing");
185         resourceTO.setEnforceMandatoryCondition(true);
186 
187         Response response = RESOURCE_SERVICE.create(resourceTO);
188         resourceTO = getObject(response.getLocation(), ResourceService.class, ResourceTO.class);
189         assertNotNull(resourceTO);
190 
191         try {
192             UserCR userCR = getUniqueSample("syncope222@apache.org");
193             userCR.getResources().add(resourceTO.getKey());
194             userCR.setPassword("newPassword12");
195 
196             try {
197                 createUser(userCR).getEntity();
198                 fail("This should not happen");
199             } catch (SyncopeClientException e) {
200                 assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
201             }
202 
203             userCR.getAuxClasses().add("csv");
204 
205             UserTO userTO = createUser(userCR).getEntity();
206             assertNotNull(userTO);
207             assertEquals(Set.of(resourceTO.getKey()), userTO.getResources());
208         } finally {
209             RESOURCE_SERVICE.delete(resourceTO.getKey());
210         }
211     }
212 
213     @Test
214     public void createUserWithDbPropagation() {
215         UserCR userCR = getUniqueSample("yyy@yyy.yyy");
216         userCR.getResources().add(RESOURCE_NAME_TESTDB);
217         ProvisioningResult<UserTO> result = createUser(userCR);
218         assertNotNull(result);
219         assertEquals(1, result.getPropagationStatuses().size());
220         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
221     }
222 
223     @Test
224     public void createWithInvalidPassword() {
225         assertThrows(SyncopeClientException.class, () -> {
226             UserCR userCR = getSample("invalidpasswd@syncope.apache.org");
227             userCR.setPassword("pass");
228             createUser(userCR);
229         });
230     }
231 
232     @Test
233     public void createWithInvalidUsername() {
234         assertThrows(SyncopeClientException.class, () -> {
235             UserCR userCR = getSample("invalidusername@syncope.apache.org");
236             userCR.setUsername("us");
237             userCR.setRealm("/odd");
238 
239             createUser(userCR);
240         });
241     }
242 
243     @Test
244     public void createWithInvalidPasswordByRes() {
245         assertThrows(SyncopeClientException.class, () -> {
246             UserCR userCR = getSample("invalidPwdByRes@passwd.com");
247 
248             // configured to be minLength=16
249             userCR.setPassword("password1");
250             userCR.getResources().add(RESOURCE_NAME_NOPROPAGATION);
251             createUser(userCR);
252         });
253     }
254 
255     @Test
256     public void createWithInvalidPasswordByGroup() {
257         assertThrows(SyncopeClientException.class, () -> {
258             UserCR userCR = getSample("invalidPwdByGroup@passwd.com");
259 
260             // configured to be minLength=16
261             userCR.setPassword("password1");
262 
263             userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
264 
265             createUser(userCR);
266         });
267     }
268 
269     @Test
270     public void createWithException() {
271         assertThrows(SyncopeClientException.class, () -> {
272             UserCR userCR = new UserCR();
273             userCR.getPlainAttrs().add(attr("userId", "userId@nowhere.org"));
274             createUser(userCR);
275         });
276     }
277 
278     @Test
279     public void create() {
280         // get task list
281         PagedResult<PropagationTaskTO> tasks = TASK_SERVICE.search(
282                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1).build());
283         assertNotNull(tasks);
284         assertFalse(tasks.getResult().isEmpty());
285 
286         String maxKey = tasks.getResult().iterator().next().getKey();
287         PropagationTaskTO taskTO = TASK_SERVICE.read(TaskType.PROPAGATION, maxKey, true);
288 
289         assertNotNull(taskTO);
290         int maxTaskExecutions = taskTO.getExecutions().size();
291 
292         UserCR userCR = getUniqueSample("a.b@c.com");
293 
294         // add a membership
295         userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
296 
297         // add an attribute with a non-existing schema: must be ignored
298         Attr attrWithInvalidSchemaTO = attr("invalid schema", "a value");
299         userCR.getPlainAttrs().add(attrWithInvalidSchemaTO);
300 
301         // add an attribute with null value: must be ignored
302         userCR.getPlainAttrs().add(attr("activationDate", null));
303 
304         // 1. create user
305         UserTO userTO = createUser(userCR).getEntity();
306 
307         assertNotNull(userTO);
308 
309         // issue SYNCOPE-15
310         assertNotNull(userTO.getCreationDate());
311         assertNotNull(userTO.getCreator());
312         assertNotNull(userTO.getLastChangeDate());
313         assertNotNull(userTO.getLastModifier());
314         assertTrue(userTO.getLastChangeDate().toEpochSecond() - userTO.getCreationDate().toEpochSecond() < 3);
315 
316         assertFalse(userTO.getPlainAttrs().contains(attrWithInvalidSchemaTO));
317 
318         // check for changePwdDate
319         assertNotNull(userTO.getCreationDate());
320 
321         // get the new task list
322         tasks = TASK_SERVICE.search(new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1).build());
323         assertNotNull(tasks);
324         assertFalse(tasks.getResult().isEmpty());
325 
326         String newMaxKey = tasks.getResult().iterator().next().getKey();
327 
328         // default configuration for ws-target-resource2:
329         // only failed executions have to be registered
330         // --> no more tasks/executions should be added
331         assertEquals(newMaxKey, maxKey);
332 
333         // get last task
334         taskTO = TASK_SERVICE.read(TaskType.PROPAGATION, newMaxKey, true);
335 
336         assertNotNull(taskTO);
337         assertEquals(maxTaskExecutions, taskTO.getExecutions().size());
338 
339         // 3. verify password
340         try {
341             Triple<Map<String, Set<String>>, List<String>, UserTO> self =
342                     CLIENT_FACTORY.create(userTO.getUsername(), "password123").self();
343             assertNotNull(self);
344         } catch (AccessControlException e) {
345             fail("Credentials should be valid and not cause AccessControlException");
346         }
347 
348         try {
349             CLIENT_FACTORY.create(userTO.getUsername(), "passwordXX").getService(UserSelfService.class);
350             fail("Credentials are invalid, thus request should raise AccessControlException");
351         } catch (AccessControlException e) {
352             assertNotNull(e);
353         }
354 
355         // 4. try (and fail) to create another user with same (unique) values
356         userCR = getSample(userTO.getUsername());
357         Attr userIdAttr = userTO.getPlainAttr("userId").get();
358         userIdAttr.getValues().clear();
359         userIdAttr.getValues().add("a.b@c.com");
360 
361         try {
362             createUser(userCR);
363             fail("This should not happen");
364         } catch (SyncopeClientException e) {
365             assertEquals(ClientExceptionType.EntityExists, e.getType());
366         }
367     }
368 
369     @Test
370     public void createWithRequiredValueMissing() {
371         UserCR userCR = getUniqueSample("a.b@c.it");
372 
373         Attr type = userCR.getPlainAttr("ctype").get();
374         userCR.getPlainAttrs().remove(type);
375 
376         userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
377 
378         // 1. create user without type (mandatory by UserSchema)
379         try {
380             createUser(userCR);
381             fail("This should not happen");
382         } catch (SyncopeClientException e) {
383             assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
384         }
385 
386         userCR.getPlainAttrs().add(attr("ctype", "F"));
387 
388         Attr surname = userCR.getPlainAttr("surname").get();
389         userCR.getPlainAttrs().remove(surname);
390 
391         // 2. create user without surname (mandatory when type == 'F')
392         try {
393             createUser(userCR);
394             fail("This should not happen");
395         } catch (SyncopeClientException e) {
396             assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
397         }
398     }
399 
400     @Test
401     public void delete() {
402         try {
403             USER_SERVICE.delete(UUID.randomUUID().toString());
404         } catch (SyncopeClientException e) {
405             assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
406         }
407 
408         UserCR userCR = getSample("qqgf.z@nn.com");
409 
410         // specify a propagation
411         userCR.getResources().add(RESOURCE_NAME_TESTDB);
412 
413         UserTO userTO = createUser(userCR).getEntity();
414 
415         String key = userTO.getKey();
416 
417         ProvisioningResult<UserTO> result = deleteUser(key);
418         assertNotNull(result);
419         userTO = result.getEntity();
420         assertEquals(key, userTO.getKey());
421         assertTrue(userTO.getPlainAttrs().isEmpty());
422 
423         // check for propagation result
424         assertFalse(result.getPropagationStatuses().isEmpty());
425         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
426 
427         try {
428             USER_SERVICE.delete(userTO.getKey());
429         } catch (SyncopeClientException e) {
430             assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
431         }
432     }
433 
434     @Test
435     public void deleteByUsername() {
436         UserCR userCR = getSample("delete.by.username@apache.org");
437 
438         // specify a propagation
439         userCR.getResources().add(RESOURCE_NAME_TESTDB);
440 
441         UserTO userTO = createUser(userCR).getEntity();
442 
443         String key = userTO.getKey();
444         userTO = USER_SERVICE.read(key);
445 
446         ProvisioningResult<UserTO> result = deleteUser(userTO.getKey());
447         assertNotNull(result);
448         userTO = result.getEntity();
449         assertEquals(key, userTO.getKey());
450         assertTrue(userTO.getPlainAttrs().isEmpty());
451 
452         // check for propagation result
453         assertFalse(result.getPropagationStatuses().isEmpty());
454         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
455 
456         try {
457             USER_SERVICE.read(userTO.getKey());
458         } catch (SyncopeClientException e) {
459             assertEquals(Response.Status.NOT_FOUND, e.getType().getResponseStatus());
460         }
461     }
462 
463     @Test
464     public void list() {
465         PagedResult<UserTO> users = USER_SERVICE.search(
466                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).page(1).size(2).build());
467         assertNotNull(users);
468         assertFalse(users.getResult().isEmpty());
469         assertEquals(2, users.getResult().size());
470 
471         for (UserTO user : users.getResult()) {
472             assertNotNull(user);
473         }
474 
475         users = USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
476                 page(2).size(2).build());
477         assertNotNull(users);
478         assertEquals(2, users.getPage());
479         assertEquals(2, users.getResult().size());
480 
481         users = USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
482                 page(100).size(2).build());
483         assertNotNull(users);
484         assertTrue(users.getResult().isEmpty());
485     }
486 
487     @Test
488     public void read() {
489         UserTO userTO = USER_SERVICE.read("1417acbe-cbf6-4277-9372-e75e04f97000");
490 
491         assertNotNull(userTO);
492         assertNull(userTO.getPassword());
493         assertNotNull(userTO.getPlainAttrs());
494         assertFalse(userTO.getPlainAttrs().isEmpty());
495     }
496 
497     @Test
498     public void updateWithoutPassword() {
499         UserCR userCR = getUniqueSample("updatewithout@password.com");
500 
501         UserTO userTO = createUser(userCR).getEntity();
502         assertNotNull(userTO);
503 
504         UserUR userUR = new UserUR.Builder(userTO.getKey()).
505                 plainAttr(new AttrPatch.Builder(new Attr.Builder("ctype").build()).
506                         operation(PatchOperation.DELETE).
507                         build()).build();
508 
509         userTO = updateUser(userUR).getEntity();
510 
511         assertNotNull(userTO);
512         assertFalse(userTO.getPlainAttr("ctype").isPresent());
513     }
514 
515     @Test
516     public void updateInvalidPassword() {
517         assertThrows(SyncopeClientException.class, () -> {
518             UserCR userCR = getSample("updateinvalid@password.com");
519 
520             UserTO userTO = createUser(userCR).getEntity();
521             assertNotNull(userTO);
522 
523             UserUR userUR = new UserUR();
524             userUR.setKey(userTO.getKey());
525             userUR.setPassword(new PasswordPatch.Builder().value("pass").build());
526 
527             USER_SERVICE.update(userUR);
528         });
529     }
530 
531     @Test
532     public void updateSamePassword() {
533         assertThrows(SyncopeClientException.class, () -> {
534             UserCR userCR = getUniqueSample("updatesame@password.com");
535             userCR.setRealm("/even/two");
536 
537             UserTO userTO = createUser(userCR).getEntity();
538             assertNotNull(userTO);
539 
540             UserUR userUR = new UserUR();
541             userUR.setKey(userTO.getKey());
542             userUR.setPassword(new PasswordPatch.Builder().value("password123").build());
543 
544             USER_SERVICE.update(userUR);
545         });
546     }
547 
548     @Test
549     public void update() {
550         UserCR userCR = getUniqueSample("g.h@t.com");
551 
552         userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
553 
554         UserTO userTO = createUser(userCR).getEntity();
555 
556         assertFalse(userTO.getDerAttrs().isEmpty());
557         assertEquals(1, userTO.getMemberships().size());
558 
559         UserUR userUR = new UserUR();
560         userUR.setKey(userTO.getKey());
561         userUR.setPassword(new PasswordPatch.Builder().value("new2Password").build());
562 
563         String newUserId = getUUIDString() + "t.w@spre.net";
564         userUR.getPlainAttrs().add(attrAddReplacePatch("userId", newUserId));
565 
566         String newFullName = getUUIDString() + "g.h@t.com";
567         userUR.getPlainAttrs().add(attrAddReplacePatch("fullname", newFullName));
568 
569         userUR.getMemberships().add(new MembershipUR.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").
570                 operation(PatchOperation.ADD_REPLACE).build());
571         userUR.getMemberships().add(new MembershipUR.Builder(userTO.getMemberships().get(0).getGroupKey()).
572                 operation(PatchOperation.ADD_REPLACE).build());
573 
574         userTO = updateUser(userUR).getEntity();
575         assertNotNull(userTO);
576 
577         // issue SYNCOPE-15
578         assertNotNull(userTO.getCreationDate());
579         assertNotNull(userTO.getCreator());
580         assertNotNull(userTO.getLastChangeDate());
581         assertNotNull(userTO.getLastModifier());
582         assertTrue(userTO.getCreationDate().isBefore(userTO.getLastChangeDate()));
583 
584         assertEquals(1, userTO.getMemberships().size());
585         assertFalse(userTO.getDerAttrs().isEmpty());
586 
587         Attr userIdAttr = userTO.getPlainAttr("userId").get();
588         assertEquals(List.of(newUserId), userIdAttr.getValues());
589 
590         Attr fullNameAttr = userTO.getPlainAttr("fullname").get();
591         assertEquals(List.of(newFullName), fullNameAttr.getValues());
592 
593         // update by username
594         userUR = new UserUR();
595         userUR.setKey(userTO.getUsername());
596         String newUsername = UUID.randomUUID().toString();
597         userUR.setUsername(new StringReplacePatchItem.Builder().value(newUsername).build());
598 
599         userTO = updateUser(userUR).getEntity();
600         assertNotNull(userTO);
601         assertEquals(newUsername, userTO.getUsername());
602     }
603 
604     @Test
605     public void updatePasswordOnly() {
606         int beforeTasks = TASK_SERVICE.search(
607                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1).build()).getTotalCount();
608         assertFalse(beforeTasks <= 0);
609 
610         UserCR userCR = getUniqueSample("pwdonly@t.com");
611         userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
612 
613         UserTO userTO = createUser(userCR).getEntity();
614 
615         UserUR userUR = new UserUR();
616         userUR.setKey(userTO.getKey());
617         userUR.setPassword(new PasswordPatch.Builder().value("newPassword123").resource(RESOURCE_NAME_WS2).build());
618 
619         userTO = updateUser(userUR).getEntity();
620 
621         // check for changePwdDate
622         assertNotNull(userTO.getChangePwdDate());
623 
624         int afterTasks = TASK_SERVICE.search(
625                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1).build()).getTotalCount();
626         assertFalse(afterTasks <= 0);
627 
628         assertTrue(beforeTasks < afterTasks);
629     }
630 
631     @SuppressWarnings("unchecked")
632     @Test
633     public void verifyTaskRegistration() {
634         // get task list
635         List<PropagationTaskTO> tasks = TASK_SERVICE.<PropagationTaskTO>search(
636                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1000).build()).getResult();
637         assertFalse(tasks.isEmpty());
638 
639         String maxKey = tasks.stream().
640                 max(Comparator.comparing(PropagationTaskTO::getStart, Comparator.nullsLast(Comparator.naturalOrder()))).
641                 map(PropagationTaskTO::getKey).orElse(null);
642         assertNotNull(maxKey);
643 
644         // --------------------------------------
645         // Create operation
646         // --------------------------------------
647         UserCR userCR = getUniqueSample("t@p.mode");
648 
649         // add a membership
650         userCR.getMemberships().add(new MembershipTO.Builder("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
651 
652         // 1. create user
653         UserTO userTO = createUser(userCR).getEntity();
654         assertNotNull(userTO);
655 
656         // get the new task list
657         tasks = TASK_SERVICE.<PropagationTaskTO>search(
658                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1000).build()).getResult();
659         assertFalse(tasks.isEmpty());
660 
661         String newMaxKey = tasks.stream().
662                 max(Comparator.comparing(PropagationTaskTO::getStart, Comparator.nullsLast(Comparator.naturalOrder()))).
663                 map(PropagationTaskTO::getKey).orElse(null);
664         assertNotNull(newMaxKey);
665 
666         // default configuration for ws-target-resource2 during create:
667         // only failed executions have to be registered
668         // --> no more tasks/executions should be added
669         assertEquals(newMaxKey, maxKey);
670 
671         // --------------------------------------
672         // Update operation
673         // --------------------------------------
674         UserUR userUR = new UserUR();
675         userUR.setKey(userTO.getKey());
676 
677         userUR.getPlainAttrs().add(attrAddReplacePatch("surname", "surname2"));
678 
679         userTO = updateUser(userUR).getEntity();
680 
681         assertNotNull(userTO);
682 
683         // get the new task list
684         tasks = TASK_SERVICE.<PropagationTaskTO>search(
685                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1000).build()).getResult();
686         assertFalse(tasks.isEmpty());
687 
688         // default configuration for ws-target-resource2 during update:
689         // all update executions have to be registered
690         newMaxKey = tasks.stream().
691                 max(Comparator.comparing(PropagationTaskTO::getStart, Comparator.nullsLast(Comparator.naturalOrder()))).
692                 map(PropagationTaskTO::getKey).orElse(null);
693         assertNotNull(newMaxKey);
694 
695         assertNotNull(TASK_SERVICE.read(TaskType.PROPAGATION, newMaxKey, false));
696 
697         // --------------------------------------
698         // Delete operation
699         // --------------------------------------
700         USER_SERVICE.delete(userTO.getKey());
701 
702         // get the new task list
703         tasks = TASK_SERVICE.<PropagationTaskTO>search(
704                 new TaskQuery.Builder(TaskType.PROPAGATION).page(1).size(1000).build()).getResult();
705         assertFalse(tasks.isEmpty());
706 
707         maxKey = newMaxKey;
708         newMaxKey = tasks.stream().
709                 max(Comparator.comparing(PropagationTaskTO::getStart, Comparator.nullsLast(Comparator.naturalOrder()))).
710                 map(PropagationTaskTO::getKey).orElse(null);
711         assertNotNull(newMaxKey);
712 
713         // default configuration for ws-target-resource2: no delete executions have to be registered
714         // --> no more tasks/executions should be added
715         assertEquals(newMaxKey, maxKey);
716     }
717 
718     @Test
719     public void createActivate() {
720         assumeTrue(IS_FLOWABLE_ENABLED);
721 
722         UserCR userCR = getUniqueSample("createActivate@syncope.apache.org");
723 
724         userCR.getMemberships().add(new MembershipTO.Builder("268fed79-f440-4390-9435-b273768eb5d6").build());
725 
726         UserTO userTO = createUser(userCR).getEntity();
727 
728         assertNotNull(userTO);
729         assertNotNull(userTO.getToken());
730         assertNotNull(userTO.getTokenExpireTime());
731 
732         assertEquals("created", userTO.getStatus());
733 
734         StatusR statusR = new StatusR.Builder(userTO.getKey(), StatusRType.ACTIVATE).token(userTO.getToken()).build();
735 
736         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
737         }).getEntity();
738 
739         assertNotNull(userTO);
740         assertNull(userTO.getToken());
741         assertNull(userTO.getTokenExpireTime());
742         assertEquals("active", userTO.getStatus());
743     }
744 
745     @Test
746     public void suspendReactivate() {
747         UserCR userCR = getUniqueSample("suspendReactivate@syncope.apache.org");
748 
749         userCR.getMemberships().add(new MembershipTO.Builder("bf825fe1-7320-4a54-bd64-143b5c18ab97").build());
750 
751         UserTO userTO = createUser(userCR).getEntity();
752 
753         assertNotNull(userTO);
754         assertEquals(IS_FLOWABLE_ENABLED
755                 ? "active"
756                 : "created", userTO.getStatus());
757 
758         StatusR statusR = new StatusR.Builder(userTO.getKey(), StatusRType.SUSPEND).token(userTO.getToken()).build();
759 
760         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
761         }).getEntity();
762         assertNotNull(userTO);
763         assertEquals("suspended", userTO.getStatus());
764 
765         statusR = new StatusR.Builder(userTO.getKey(), StatusRType.REACTIVATE).build();
766 
767         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
768         }).getEntity();
769         assertNotNull(userTO);
770         assertEquals("active", userTO.getStatus());
771     }
772 
773     @Test
774     public void suspendReactivateOnResource() {
775         // Assert resources are present
776         ResourceTO dbTable = RESOURCE_SERVICE.read(RESOURCE_NAME_TESTDB);
777         assertNotNull(dbTable);
778         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
779         assertNotNull(ldap);
780 
781         // Create user with reference to resources
782         UserCR userCR = getUniqueSample("suspreactonresource@syncope.apache.org");
783         userCR.getMemberships().clear();
784         userCR.getResources().clear();
785         userCR.getResources().add(RESOURCE_NAME_TESTDB);
786         userCR.getResources().add(RESOURCE_NAME_LDAP);
787         UserTO userTO = createUser(userCR).getEntity();
788         assertNotNull(userTO);
789         assertEquals(IS_FLOWABLE_ENABLED
790                 ? "active"
791                 : "created", userTO.getStatus());
792         String userKey = userTO.getKey();
793 
794         // Suspend with effect on syncope, ldap and db => user should be suspended in syncope and all resources
795         StatusR statusR = new StatusR.Builder(userKey, StatusRType.SUSPEND).
796                 onSyncope(true).
797                 resources(RESOURCE_NAME_TESTDB, RESOURCE_NAME_LDAP).
798                 build();
799         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
800         }).getEntity();
801         assertNotNull(userTO);
802         assertEquals("suspended", userTO.getStatus());
803 
804         ConnObject connObjectTO =
805                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
806         assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
807 
808         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userKey);
809         assertNotNull(connObjectTO);
810 
811         // Suspend and reactivate only on ldap => db and syncope should still show suspended
812         statusR = new StatusR.Builder(userKey, StatusRType.SUSPEND).
813                 onSyncope(false).
814                 resources(RESOURCE_NAME_LDAP).
815                 build();
816         USER_SERVICE.status(statusR);
817         statusR.setType(StatusRType.REACTIVATE);
818         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
819         }).getEntity();
820         assertNotNull(userTO);
821         assertEquals("suspended", userTO.getStatus());
822 
823         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
824         assertFalse(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
825 
826         // Reactivate on syncope and db => syncope and db should show the user as active
827         statusR = new StatusR.Builder(userKey, StatusRType.REACTIVATE).
828                 onSyncope(true).
829                 resources(RESOURCE_NAME_TESTDB).
830                 build();
831         userTO = USER_SERVICE.status(statusR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
832         }).getEntity();
833         assertNotNull(userTO);
834         assertEquals("active", userTO.getStatus());
835 
836         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userKey);
837         assertTrue(getBooleanAttribute(connObjectTO, OperationalAttributes.ENABLE_NAME));
838     }
839 
840     @Test
841     public void updateMultivalueAttribute() {
842         UserCR userCR = getUniqueSample("multivalue@syncope.apache.org");
843         userCR.getResources().clear();
844         userCR.getVirAttrs().clear();
845 
846         UserTO userTO = createUser(userCR).getEntity();
847         assertNotNull(userTO);
848 
849         Attr loginDate = userTO.getPlainAttr("loginDate").get();
850         assertNotNull(loginDate);
851         assertEquals(1, loginDate.getValues().size());
852 
853         UserUR userUR = new UserUR();
854         userUR.setKey(userTO.getKey());
855 
856         loginDate.getValues().add("2000-01-01");
857         userUR.getPlainAttrs().add(new AttrPatch.Builder(loginDate).
858                 operation(PatchOperation.ADD_REPLACE).build());
859 
860         userTO = updateUser(userUR).getEntity();
861         assertNotNull(userTO);
862 
863         loginDate = userTO.getPlainAttr("loginDate").get();
864         assertNotNull(loginDate);
865         assertEquals(2, loginDate.getValues().size());
866     }
867 
868     private static void verifyAsyncResult(final List<PropagationStatus> statuses) {
869         assertEquals(3, statuses.size());
870 
871         Map<String, PropagationStatus> byResource = statuses.stream().collect(
872                 Collectors.toMap(PropagationStatus::getResource, Function.identity()));
873         assertEquals(ExecStatus.SUCCESS, byResource.get(RESOURCE_NAME_LDAP).getStatus());
874         assertTrue(byResource.get(RESOURCE_NAME_TESTDB).getStatus() == ExecStatus.CREATED
875                 || byResource.get(RESOURCE_NAME_TESTDB).getStatus() == ExecStatus.SUCCESS);
876         assertTrue(byResource.get(RESOURCE_NAME_TESTDB2).getStatus() == ExecStatus.CREATED
877                 || byResource.get(RESOURCE_NAME_TESTDB2).getStatus() == ExecStatus.SUCCESS);
878     }
879 
880     @Test
881     public void async() {
882         SyncopeClient asyncClient = CLIENT_FACTORY.create(ADMIN_UNAME, ADMIN_PWD);
883         UserService asyncService = SyncopeClient.nullPriorityAsync(asyncClient.getService(UserService.class), true);
884 
885         UserCR userCR = getUniqueSample("async@syncope.apache.org");
886         userCR.getResources().add(RESOURCE_NAME_TESTDB);
887         userCR.getResources().add(RESOURCE_NAME_TESTDB2);
888         userCR.getResources().add(RESOURCE_NAME_LDAP);
889 
890         ProvisioningResult<UserTO> result = asyncService.create(userCR).readEntity(
891                 new GenericType<>() {
892         });
893         assertNotNull(result);
894         verifyAsyncResult(result.getPropagationStatuses());
895 
896         UserUR userUR = new UserUR();
897         userUR.setKey(result.getEntity().getKey());
898         userUR.setPassword(new PasswordPatch.Builder().
899                 onSyncope(true).resources(RESOURCE_NAME_LDAP, RESOURCE_NAME_TESTDB, RESOURCE_NAME_TESTDB2).
900                 value("password321").build());
901 
902         result = asyncService.update(userUR).readEntity(
903                 new GenericType<>() {
904         });
905         assertNotNull(result);
906         verifyAsyncResult(result.getPropagationStatuses());
907 
908         result = asyncService.delete(result.getEntity().getKey()).readEntity(
909                 new GenericType<>() {
910         });
911         assertNotNull(result);
912         verifyAsyncResult(result.getPropagationStatuses());
913     }
914 
915     @Test
916     public void groupAttrPropagation() {
917         UserCR userCR = getUniqueSample("checkGroupAttrPropagation@syncope.apache.org");
918         userCR.getResources().clear();
919         userCR.getMemberships().clear();
920         userCR.getVirAttrs().clear();
921 
922         userCR.getAuxClasses().add("csv");
923 
924         userCR.getMemberships().add(new MembershipTO.Builder("37d15e4c-cdc1-460b-a591-8505c8133806").build());
925 
926         userCR.getResources().add(RESOURCE_NAME_CSV);
927 
928         UserTO userTO = createUser(userCR).getEntity();
929         assertNotNull(userTO);
930         assertNotNull(userTO.getDerAttr("csvuserid"));
931 
932         ConnObject connObjectTO =
933                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
934         assertNotNull(connObjectTO);
935         assertEquals("sx-dx", connObjectTO.getAttr("THEIRGROUP").get().getValues().get(0));
936     }
937 
938     @Test
939     public void customPolicyRules() {
940         ImplementationTO implementationTO = new ImplementationTO();
941         implementationTO.setKey("TestAccountRuleConf" + UUID.randomUUID().toString());
942         implementationTO.setEngine(ImplementationEngine.JAVA);
943         implementationTO.setType(IdRepoImplementationType.ACCOUNT_RULE);
944         implementationTO.setBody(POJOHelper.serialize(new TestAccountRuleConf()));
945         Response response = IMPLEMENTATION_SERVICE.create(implementationTO);
946         implementationTO.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
947 
948         AccountPolicyTO accountPolicy = new AccountPolicyTO();
949         accountPolicy.setName("Account Policy with custom rules");
950         accountPolicy.getRules().add(implementationTO.getKey());
951         accountPolicy = createPolicy(PolicyType.ACCOUNT, accountPolicy);
952         assertNotNull(accountPolicy);
953 
954         implementationTO = new ImplementationTO();
955         implementationTO.setKey("TestPasswordRuleConf" + UUID.randomUUID().toString());
956         implementationTO.setEngine(ImplementationEngine.JAVA);
957         implementationTO.setType(IdRepoImplementationType.PASSWORD_RULE);
958         implementationTO.setBody(POJOHelper.serialize(new TestPasswordRuleConf()));
959         response = IMPLEMENTATION_SERVICE.create(implementationTO);
960         implementationTO.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
961 
962         PasswordPolicyTO passwordPolicy = new PasswordPolicyTO();
963         passwordPolicy.setName("Password Policy with custom rules");
964         passwordPolicy.getRules().add(implementationTO.getKey());
965         passwordPolicy = createPolicy(PolicyType.PASSWORD, passwordPolicy);
966         assertNotNull(passwordPolicy);
967 
968         RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().get(0);
969         String oldAccountPolicy = realm.getAccountPolicy();
970         realm.setAccountPolicy(accountPolicy.getKey());
971         String oldPasswordPolicy = realm.getPasswordPolicy();
972         realm.setPasswordPolicy(passwordPolicy.getKey());
973         REALM_SERVICE.update(realm);
974 
975         try {
976             UserCR userCR = getUniqueSample("custompolicyrules@syncope.apache.org");
977             userCR.setRealm(realm.getFullPath());
978 
979             try {
980                 ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
981                         new ComplianceQuery.Builder().password(userCR.getPassword()).realm(userCR.getRealm()).build());
982             } catch (SyncopeClientException e) {
983                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
984                 assertTrue(e.getElements().iterator().next().startsWith("InvalidPassword"));
985             }
986 
987             try {
988                 createUser(userCR);
989                 fail("This should not happen");
990             } catch (SyncopeClientException e) {
991                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
992                 assertTrue(e.getElements().iterator().next().startsWith("InvalidPassword"));
993             }
994 
995             try {
996                 ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
997                         new ComplianceQuery.Builder().username(userCR.getUsername()).realm(userCR.getRealm()).build());
998             } catch (SyncopeClientException e) {
999                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
1000                 assertTrue(e.getElements().iterator().next().startsWith("InvalidUsername"));
1001             }
1002 
1003             userCR.setPassword(userCR.getPassword() + "XXX");
1004 
1005             try {
1006                 createUser(userCR);
1007                 fail("This should not happen");
1008             } catch (SyncopeClientException e) {
1009                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
1010                 assertTrue(e.getElements().iterator().next().startsWith("InvalidUsername"));
1011             }
1012 
1013             userCR.setUsername("YYY" + userCR.getUsername());
1014 
1015             assertDoesNotThrow(() -> ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
1016                     new ComplianceQuery.Builder().password(userCR.getPassword()).realm(userCR.getRealm()).build()));
1017             assertDoesNotThrow(() -> ANONYMOUS_CLIENT.getService(UserSelfService.class).compliance(
1018                     new ComplianceQuery.Builder().username(userCR.getUsername()).realm(userCR.getRealm()).build()));
1019 
1020             UserTO userTO = createUser(userCR).getEntity();
1021             assertNotNull(userTO);
1022         } finally {
1023             realm.setAccountPolicy(oldAccountPolicy);
1024             realm.setPasswordPolicy(oldPasswordPolicy);
1025             REALM_SERVICE.update(realm);
1026 
1027             POLICY_SERVICE.delete(PolicyType.PASSWORD, passwordPolicy.getKey());
1028             POLICY_SERVICE.delete(PolicyType.ACCOUNT, accountPolicy.getKey());
1029         }
1030     }
1031 
1032     @Test
1033     public void mappingPurpose() {
1034         UserCR userCR = getUniqueSample("mpurpose@apache.org");
1035         userCR.getAuxClasses().add("csv");
1036 
1037         userCR.getResources().clear();
1038         userCR.getResources().add(RESOURCE_NAME_CSV);
1039 
1040         UserTO userTO = createUser(userCR).getEntity();
1041         assertNotNull(userTO);
1042 
1043         ConnObject connObjectTO =
1044                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
1045         assertFalse(connObjectTO.getAttr("email").isPresent());
1046     }
1047 
1048     @Test
1049     public void batch() throws IOException {
1050         List<String> users = new ArrayList<>();
1051         for (int i = 0; i < 10; i++) {
1052             UserCR userCR = getUniqueSample("batch_" + i + "@apache.org");
1053             users.add(createUser(userCR).getEntity().getKey());
1054         }
1055 
1056         // check for a fail
1057         users.add(UUID.randomUUID().toString());
1058 
1059         assertEquals(11, users.size());
1060 
1061         BatchRequest batchRequest = ADMIN_CLIENT.batch();
1062 
1063         UserService batchUserService = batchRequest.getService(UserService.class);
1064         users.forEach(user -> batchUserService.status(new StatusR.Builder(user, StatusRType.SUSPEND).
1065                 onSyncope(true).
1066                 build()));
1067         List<BatchResponseItem> batchResponseItems = parseBatchResponse(batchRequest.commit().getResponse());
1068         assertEquals(10, batchResponseItems.stream().
1069                 filter(item -> Response.Status.OK.getStatusCode() == item.getStatus()).count());
1070         assertEquals(1, batchResponseItems.stream().
1071                 filter(item -> Response.Status.NOT_FOUND.getStatusCode() == item.getStatus()).count());
1072         assertEquals("suspended", USER_SERVICE.read(users.get(3)).getStatus());
1073 
1074         UserService batchUserService2 = batchRequest.getService(UserService.class);
1075         users.forEach(user -> batchUserService2.status(new StatusR.Builder(user, StatusRType.REACTIVATE).
1076                 onSyncope(true).
1077                 build()));
1078         batchResponseItems = parseBatchResponse(batchRequest.commit().getResponse());
1079         assertEquals(10, batchResponseItems.stream().
1080                 filter(item -> Response.Status.OK.getStatusCode() == item.getStatus()).count());
1081         assertEquals(1, batchResponseItems.stream().
1082                 filter(item -> Response.Status.NOT_FOUND.getStatusCode() == item.getStatus()).count());
1083         assertEquals("active", USER_SERVICE.read(users.get(3)).getStatus());
1084 
1085         UserService batchUserService3 = batchRequest.getService(UserService.class);
1086         users.forEach(batchUserService3::delete);
1087         batchResponseItems = parseBatchResponse(batchRequest.commit().getResponse());
1088         assertEquals(10, batchResponseItems.stream().
1089                 filter(item -> Response.Status.OK.getStatusCode() == item.getStatus()).count());
1090         assertEquals(1, batchResponseItems.stream().
1091                 filter(item -> Response.Status.NOT_FOUND.getStatusCode() == item.getStatus()).count());
1092 
1093         try {
1094             USER_SERVICE.read(users.get(3));
1095             fail("This should not happen");
1096         } catch (SyncopeClientException e) {
1097             assertEquals(ClientExceptionType.NotFound, e.getType());
1098         }
1099     }
1100 
1101     @Test
1102     public void unlink() throws IOException {
1103         UserCR userCR = getUniqueSample("unlink@syncope.apache.org");
1104         userCR.getResources().clear();
1105         userCR.getMemberships().clear();
1106         userCR.getVirAttrs().clear();
1107         userCR.getAuxClasses().add("csv");
1108         userCR.getResources().add(RESOURCE_NAME_CSV);
1109 
1110         UserTO actual = createUser(userCR).getEntity();
1111         assertNotNull(actual);
1112         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1113 
1114         ResourceDR resourceDR = new ResourceDR.Builder().key(actual.getKey()).
1115                 action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_CSV).build();
1116 
1117         assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(resourceDR)));
1118 
1119         actual = USER_SERVICE.read(actual.getKey());
1120         assertNotNull(actual);
1121         assertTrue(actual.getResources().isEmpty());
1122 
1123         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1124     }
1125 
1126     @Test
1127     public void link() throws IOException {
1128         UserCR userCR = getUniqueSample("link@syncope.apache.org");
1129         userCR.getResources().clear();
1130         userCR.getMemberships().clear();
1131         userCR.getVirAttrs().clear();
1132         userCR.getAuxClasses().add("csv");
1133 
1134         UserTO actual = createUser(userCR).getEntity();
1135         assertNotNull(actual);
1136         assertTrue(actual.getResources().isEmpty());
1137 
1138         try {
1139             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1140             fail("This should not happen");
1141         } catch (Exception e) {
1142             assertNotNull(e);
1143         }
1144 
1145         ResourceAR resourceAR = new ResourceAR.Builder().key(actual.getKey()).
1146                 action(ResourceAssociationAction.LINK).resource(RESOURCE_NAME_CSV).build();
1147 
1148         assertNotNull(parseBatchResponse(USER_SERVICE.associate(resourceAR)));
1149 
1150         actual = USER_SERVICE.read(actual.getKey());
1151         assertNotNull(actual);
1152         assertFalse(actual.getResources().isEmpty());
1153 
1154         try {
1155             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1156             fail("This should not happen");
1157         } catch (Exception e) {
1158             assertNotNull(e);
1159         }
1160     }
1161 
1162     @Test
1163     public void unassign() throws IOException {
1164         UserCR userCR = getUniqueSample("unassign@syncope.apache.org");
1165         userCR.getResources().clear();
1166         userCR.getMemberships().clear();
1167         userCR.getVirAttrs().clear();
1168         userCR.getAuxClasses().add("csv");
1169         userCR.getResources().add(RESOURCE_NAME_CSV);
1170 
1171         UserTO actual = createUser(userCR).getEntity();
1172         assertNotNull(actual);
1173         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1174 
1175         ResourceDR resourceDR = new ResourceDR.Builder().key(actual.getKey()).
1176                 action(ResourceDeassociationAction.UNASSIGN).resource(RESOURCE_NAME_CSV).build();
1177 
1178         assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(resourceDR)));
1179 
1180         actual = USER_SERVICE.read(actual.getKey());
1181         assertNotNull(actual);
1182         assertTrue(actual.getResources().isEmpty());
1183 
1184         try {
1185             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1186             fail("This should not happen");
1187         } catch (Exception e) {
1188             assertNotNull(e);
1189         }
1190     }
1191 
1192     @Test
1193     public void assign() throws IOException {
1194         UserCR userCR = getUniqueSample("assign@syncope.apache.org");
1195         userCR.getResources().clear();
1196         userCR.getMemberships().clear();
1197         userCR.getVirAttrs().clear();
1198         userCR.getAuxClasses().add("csv");
1199 
1200         UserTO actual = createUser(userCR).getEntity();
1201         assertNotNull(actual);
1202         assertTrue(actual.getResources().isEmpty());
1203 
1204         try {
1205             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1206             fail("This should not happen");
1207         } catch (Exception e) {
1208             assertNotNull(e);
1209         }
1210 
1211         ResourceAR resourceAR = new ResourceAR.Builder().key(actual.getKey()).
1212                 value("password123").action(ResourceAssociationAction.ASSIGN).resource(RESOURCE_NAME_CSV).build();
1213 
1214         assertNotNull(parseBatchResponse(USER_SERVICE.associate(resourceAR)));
1215 
1216         actual = USER_SERVICE.read(actual.getKey());
1217         assertNotNull(actual);
1218         assertFalse(actual.getResources().isEmpty());
1219         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1220     }
1221 
1222     @Test
1223     public void deprovision() throws IOException {
1224         UserCR userCR = getUniqueSample("deprovision@syncope.apache.org");
1225         userCR.getResources().clear();
1226         userCR.getMemberships().clear();
1227         userCR.getVirAttrs().clear();
1228         userCR.getAuxClasses().add("csv");
1229         userCR.getResources().add(RESOURCE_NAME_CSV);
1230 
1231         UserTO actual = createUser(userCR).getEntity();
1232         assertNotNull(actual);
1233         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1234 
1235         ResourceDR resourceDR = new ResourceDR.Builder().key(actual.getKey()).
1236                 action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_CSV).build();
1237 
1238         assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(resourceDR)));
1239 
1240         actual = USER_SERVICE.read(actual.getKey());
1241         assertNotNull(actual);
1242         assertFalse(actual.getResources().isEmpty());
1243 
1244         try {
1245             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1246             fail("This should not happen");
1247         } catch (SyncopeClientException e) {
1248             assertEquals(ClientExceptionType.NotFound, e.getType());
1249         }
1250     }
1251 
1252     @Test
1253     public void provision() throws IOException {
1254         UserCR userCR = getUniqueSample("provision@syncope.apache.org");
1255         userCR.getResources().clear();
1256         userCR.getMemberships().clear();
1257         userCR.getVirAttrs().clear();
1258         userCR.getAuxClasses().add("csv");
1259 
1260         UserTO actual = createUser(userCR).getEntity();
1261         assertNotNull(actual);
1262         assertTrue(actual.getResources().isEmpty());
1263 
1264         try {
1265             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1266             fail("This should not happen");
1267         } catch (Exception e) {
1268             assertNotNull(e);
1269         }
1270 
1271         ResourceAR resourceAR = new ResourceAR.Builder().key(actual.getKey()).
1272                 value("password").action(ResourceAssociationAction.PROVISION).resource(RESOURCE_NAME_CSV).build();
1273 
1274         assertNotNull(parseBatchResponse(USER_SERVICE.associate(resourceAR)));
1275 
1276         actual = USER_SERVICE.read(actual.getKey());
1277         assertNotNull(actual);
1278         assertTrue(actual.getResources().isEmpty());
1279         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1280     }
1281 
1282     @Test
1283     public void deprovisionUnlinked() throws IOException {
1284         UserCR userCR = getUniqueSample("provision@syncope.apache.org");
1285         userCR.getResources().clear();
1286         userCR.getMemberships().clear();
1287         userCR.getVirAttrs().clear();
1288         userCR.getAuxClasses().add("csv");
1289 
1290         UserTO actual = createUser(userCR).getEntity();
1291         assertNotNull(actual);
1292         assertTrue(actual.getResources().isEmpty());
1293 
1294         try {
1295             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1296             fail("This should not happen");
1297         } catch (Exception e) {
1298             assertNotNull(e);
1299         }
1300 
1301         ResourceAR resourceAR = new ResourceAR.Builder().key(actual.getKey()).
1302                 value("password").action(ResourceAssociationAction.PROVISION).resource(RESOURCE_NAME_CSV).build();
1303 
1304         assertNotNull(parseBatchResponse(USER_SERVICE.associate(resourceAR)));
1305 
1306         actual = USER_SERVICE.read(actual.getKey());
1307         assertNotNull(actual);
1308         assertTrue(actual.getResources().isEmpty());
1309         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey()));
1310 
1311         ResourceDR resourceDR = new ResourceDR.Builder().key(actual.getKey()).
1312                 action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_CSV).build();
1313 
1314         assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(resourceDR)));
1315 
1316         actual = USER_SERVICE.read(actual.getKey());
1317         assertNotNull(actual);
1318         assertTrue(actual.getResources().isEmpty());
1319 
1320         try {
1321             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), actual.getKey());
1322             fail("This should not happen");
1323         } catch (Exception e) {
1324             assertNotNull(e);
1325         }
1326     }
1327 
1328     @Test
1329     public void restResource() {
1330         UserCR userCR = getUniqueSample("rest@syncope.apache.org");
1331         userCR.getResources().clear();
1332         userCR.getResources().add(RESOURCE_NAME_REST);
1333 
1334         // 1. create
1335         ProvisioningResult<UserTO> result = createUser(userCR);
1336         assertEquals(1, result.getPropagationStatuses().size());
1337         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1338         assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
1339         assertEquals("surname", result.getEntity().getPlainAttr("surname").get().getValues().get(0));
1340 
1341         // verify user exists on the backend REST service
1342         WebClient webClient = WebClient.create(BUILD_TOOLS_ADDRESS + "/rest/users/" + result.getEntity().getKey());
1343         Response response = webClient.get();
1344         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
1345         assertNotNull(response.getEntity());
1346 
1347         // 2. update
1348         UserUR userUR = new UserUR.Builder(result.getEntity().getKey()).
1349                 plainAttr(new AttrPatch.Builder(new Attr.Builder("surname").value("surname2").build()).build()).
1350                 build();
1351         result = updateUser(userUR);
1352         assertEquals(1, result.getPropagationStatuses().size());
1353         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1354         assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
1355         assertEquals("surname2", result.getEntity().getPlainAttr("surname").get().getValues().get(0));
1356 
1357         // verify user still exists on the backend REST service
1358         response = webClient.get();
1359         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
1360         assertNotNull(response.getEntity());
1361 
1362         // 3. delete
1363         result = deleteUser(result.getEntity().getKey());
1364         assertEquals(1, result.getPropagationStatuses().size());
1365         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1366         assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
1367 
1368         // verify user was removed by the backend REST service
1369         assertEquals(Response.Status.NOT_FOUND.getStatusCode(), webClient.get().getStatus());
1370     }
1371 
1372     @Test
1373     public void haveIBeenPwned() {
1374         ImplementationTO rule = new ImplementationTO();
1375         rule.setKey("HaveIBeenPwnedPasswordRuleConf" + getUUIDString());
1376         rule.setEngine(ImplementationEngine.JAVA);
1377         rule.setType(IdRepoImplementationType.PASSWORD_RULE);
1378         rule.setBody(POJOHelper.serialize(new HaveIBeenPwnedPasswordRuleConf()));
1379         Response response = IMPLEMENTATION_SERVICE.create(rule);
1380         rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
1381 
1382         PasswordPolicyTO pwdPolicy = new PasswordPolicyTO();
1383         pwdPolicy.setName("Have I Been Pwned?");
1384         pwdPolicy.getRules().add(rule.getKey());
1385         pwdPolicy = createPolicy(PolicyType.PASSWORD, pwdPolicy);
1386         assertNotNull(pwdPolicy.getKey());
1387 
1388         RealmTO realm = new RealmTO();
1389         realm.setName("hibp");
1390         realm.setPasswordPolicy(pwdPolicy.getKey());
1391         REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, realm);
1392 
1393         UserCR userCR = getUniqueSample("hibp@syncope.apache.org");
1394         userCR.setRealm("/hibp");
1395         userCR.setPassword("password");
1396         try {
1397             createUser(userCR);
1398         } catch (SyncopeClientException e) {
1399             assertEquals(ClientExceptionType.InvalidUser, e.getType());
1400             assertEquals("InvalidPassword: Password pwned", e.getElements().iterator().next());
1401         }
1402 
1403         userCR.setPassword('1' + RandomStringUtils.randomAlphanumeric(10));
1404         UserTO userTO = createUser(userCR).getEntity();
1405         assertNotNull(userTO.getKey());
1406     }
1407 }