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.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
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.assertTrue;
27  import static org.junit.jupiter.api.Assertions.fail;
28  import static org.junit.jupiter.api.Assumptions.assumeFalse;
29  
30  import java.io.IOException;
31  import java.nio.charset.StandardCharsets;
32  import java.util.Base64;
33  import java.util.Collection;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Optional;
37  import java.util.Set;
38  import java.util.UUID;
39  import javax.naming.NamingException;
40  import javax.ws.rs.HttpMethod;
41  import javax.ws.rs.core.GenericType;
42  import javax.ws.rs.core.MediaType;
43  import javax.ws.rs.core.Response;
44  import org.apache.commons.lang3.tuple.Triple;
45  import org.apache.cxf.helpers.IOUtils;
46  import org.apache.cxf.jaxrs.client.WebClient;
47  import org.apache.syncope.client.lib.SyncopeClient;
48  import org.apache.syncope.common.lib.Attr;
49  import org.apache.syncope.common.lib.SyncopeClientException;
50  import org.apache.syncope.common.lib.SyncopeConstants;
51  import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
52  import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
53  import org.apache.syncope.common.lib.request.AttrPatch;
54  import org.apache.syncope.common.lib.request.GroupCR;
55  import org.apache.syncope.common.lib.request.MembershipUR;
56  import org.apache.syncope.common.lib.request.PasswordPatch;
57  import org.apache.syncope.common.lib.request.ResourceAR;
58  import org.apache.syncope.common.lib.request.ResourceDR;
59  import org.apache.syncope.common.lib.request.StatusR;
60  import org.apache.syncope.common.lib.request.StringPatchItem;
61  import org.apache.syncope.common.lib.request.StringReplacePatchItem;
62  import org.apache.syncope.common.lib.request.UserCR;
63  import org.apache.syncope.common.lib.request.UserUR;
64  import org.apache.syncope.common.lib.to.AnyTypeClassTO;
65  import org.apache.syncope.common.lib.to.ConnObject;
66  import org.apache.syncope.common.lib.to.ExecTO;
67  import org.apache.syncope.common.lib.to.GroupTO;
68  import org.apache.syncope.common.lib.to.ImplementationTO;
69  import org.apache.syncope.common.lib.to.Item;
70  import org.apache.syncope.common.lib.to.Mapping;
71  import org.apache.syncope.common.lib.to.MembershipTO;
72  import org.apache.syncope.common.lib.to.PlainSchemaTO;
73  import org.apache.syncope.common.lib.to.PropagationStatus;
74  import org.apache.syncope.common.lib.to.ProvisioningResult;
75  import org.apache.syncope.common.lib.to.PushTaskTO;
76  import org.apache.syncope.common.lib.to.RealmTO;
77  import org.apache.syncope.common.lib.to.ReconStatus;
78  import org.apache.syncope.common.lib.to.ResourceTO;
79  import org.apache.syncope.common.lib.to.RoleTO;
80  import org.apache.syncope.common.lib.to.UserTO;
81  import org.apache.syncope.common.lib.types.AnyTypeKind;
82  import org.apache.syncope.common.lib.types.AttrSchemaType;
83  import org.apache.syncope.common.lib.types.CipherAlgorithm;
84  import org.apache.syncope.common.lib.types.ClientExceptionType;
85  import org.apache.syncope.common.lib.types.ExecStatus;
86  import org.apache.syncope.common.lib.types.IdMImplementationType;
87  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
88  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
89  import org.apache.syncope.common.lib.types.ImplementationEngine;
90  import org.apache.syncope.common.lib.types.MappingPurpose;
91  import org.apache.syncope.common.lib.types.MatchingRule;
92  import org.apache.syncope.common.lib.types.PatchOperation;
93  import org.apache.syncope.common.lib.types.PolicyType;
94  import org.apache.syncope.common.lib.types.ResourceAssociationAction;
95  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
96  import org.apache.syncope.common.lib.types.SchemaType;
97  import org.apache.syncope.common.lib.types.StatusRType;
98  import org.apache.syncope.common.lib.types.TaskType;
99  import org.apache.syncope.common.lib.types.UnmatchingRule;
100 import org.apache.syncope.common.rest.api.RESTHeaders;
101 import org.apache.syncope.common.rest.api.beans.RealmQuery;
102 import org.apache.syncope.common.rest.api.beans.ReconQuery;
103 import org.apache.syncope.common.rest.api.service.UserService;
104 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
105 import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagationActions;
106 import org.apache.syncope.core.provisioning.java.propagation.GenerateRandomPasswordPropagationActions;
107 import org.apache.syncope.core.provisioning.java.propagation.LDAPPasswordPropagationActions;
108 import org.apache.syncope.core.spring.security.Encryptor;
109 import org.apache.syncope.fit.AbstractITCase;
110 import org.identityconnectors.framework.common.objects.Name;
111 import org.identityconnectors.framework.common.objects.OperationalAttributes;
112 import org.junit.jupiter.api.BeforeAll;
113 import org.junit.jupiter.api.Test;
114 import org.springframework.dao.EmptyResultDataAccessException;
115 import org.springframework.jdbc.core.JdbcTemplate;
116 
117 public class UserIssuesITCase extends AbstractITCase {
118 
119     @BeforeAll
120     public static void testPropagationActionsSetup() {
121         ImplementationTO propagationActions = null;
122         try {
123             propagationActions = IMPLEMENTATION_SERVICE.read(
124                     IdMImplementationType.PROPAGATION_ACTIONS, LDAPPasswordPropagationActions.class.getSimpleName());
125         } catch (SyncopeClientException e) {
126             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
127                 propagationActions = new ImplementationTO();
128                 propagationActions.setKey(LDAPPasswordPropagationActions.class.getSimpleName());
129                 propagationActions.setEngine(ImplementationEngine.JAVA);
130                 propagationActions.setType(IdMImplementationType.PROPAGATION_ACTIONS);
131                 propagationActions.setBody(LDAPPasswordPropagationActions.class.getName());
132                 Response response = IMPLEMENTATION_SERVICE.create(propagationActions);
133                 propagationActions = IMPLEMENTATION_SERVICE.read(
134                         propagationActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
135             }
136         }
137         assertNotNull(propagationActions);
138     }
139 
140     @Test
141     public void issue186() {
142         // 1. create an user with strict mandatory attributes only
143         UserCR userCR = new UserCR();
144         userCR.setRealm(SyncopeConstants.ROOT_REALM);
145         String userId = getUUIDString() + "issue186@syncope.apache.org";
146         userCR.setUsername(userId);
147         userCR.setPassword("password123");
148 
149         userCR.getPlainAttrs().add(attr("userId", userId));
150         userCR.getPlainAttrs().add(attr("fullname", userId));
151         userCR.getPlainAttrs().add(attr("surname", userId));
152 
153         UserTO userTO = createUser(userCR).getEntity();
154         assertNotNull(userTO);
155         assertTrue(userTO.getResources().isEmpty());
156 
157         // 2. update assigning a resource forcing mandatory constraints: must fail with RequiredValuesMissing
158         UserUR userUR = new UserUR.Builder(userTO.getKey()).
159                 password(new PasswordPatch.Builder().value("newPassword123").build()).
160                 resource(new StringPatchItem.Builder().
161                         operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_WS2).build()).
162                 build();
163 
164         try {
165             userTO = updateUser(userUR).getEntity();
166             fail("This should not happen");
167         } catch (SyncopeClientException e) {
168             assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
169         }
170 
171         // 3. update assigning a resource NOT forcing mandatory constraints
172         // AND priority: must fail with PropagationException
173         userUR = new UserUR();
174         userUR.setKey(userTO.getKey());
175         userUR.setPassword(new PasswordPatch.Builder().value("newPassword123").build());
176         userUR.getResources().add(new StringPatchItem.Builder().
177                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_WS1).build());
178 
179         ProvisioningResult<UserTO> result = updateUser(userUR);
180         assertNotNull(result.getPropagationStatuses().get(0).getFailureReason());
181         userTO = result.getEntity();
182 
183         // 4. update assigning a resource NOT forcing mandatory constraints
184         // BUT not priority: must succeed
185         userUR = new UserUR();
186         userUR.setKey(userTO.getKey());
187         userUR.setPassword(new PasswordPatch.Builder().value("newPassword123456").build());
188         userUR.getResources().add(new StringPatchItem.Builder().
189                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_CSV).build());
190 
191         updateUser(userUR);
192     }
193 
194     @Test
195     public void issue213() {
196         UserCR userCR = UserITCase.getUniqueSample("issue213@syncope.apache.org");
197         userCR.getResources().add(RESOURCE_NAME_TESTDB);
198 
199         UserTO userTO = createUser(userCR).getEntity();
200         assertNotNull(userTO);
201         assertEquals(1, userTO.getResources().size());
202 
203         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
204         String username = queryForObject(
205                 jdbcTemplate, MAX_WAIT_SECONDS, "SELECT id FROM test WHERE id=?", String.class, userTO.getUsername());
206         assertEquals(userTO.getUsername(), username);
207 
208         UserUR userUR = new UserUR();
209         userUR.setKey(userTO.getKey());
210         userUR.getResources().add(
211                 new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(RESOURCE_NAME_TESTDB).build());
212 
213         userTO = updateUser(userUR).getEntity();
214         assertTrue(userTO.getResources().isEmpty());
215 
216         Exception exception = null;
217         try {
218             jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", String.class, userTO.getUsername());
219         } catch (EmptyResultDataAccessException e) {
220             exception = e;
221         }
222         assertNotNull(exception);
223     }
224 
225     @Test
226     public void issue234() {
227         UserCR inUserTO = UserITCase.getUniqueSample("issue234@syncope.apache.org");
228         inUserTO.getResources().add(RESOURCE_NAME_LDAP);
229 
230         UserTO userTO = createUser(inUserTO).getEntity();
231         assertNotNull(userTO);
232 
233         UserUR userUR = new UserUR();
234 
235         userUR.setKey(userTO.getKey());
236         userUR.setUsername(new StringReplacePatchItem.Builder().value('1' + userTO.getUsername()).build());
237 
238         userTO = updateUser(userUR).getEntity();
239         assertNotNull(userTO);
240         assertEquals('1' + inUserTO.getUsername(), userTO.getUsername());
241     }
242 
243     @Test
244     public void issue280() {
245         UserCR userCR = UserITCase.getUniqueSample("issue280@syncope.apache.org");
246         userCR.getResources().clear();
247         userCR.getMemberships().clear();
248 
249         UserTO userTO = createUser(userCR).getEntity();
250         assertNotNull(userTO);
251 
252         UserUR userUR = new UserUR();
253         userUR.setKey(userTO.getKey());
254         userUR.setPassword(new PasswordPatch.Builder().onSyncope(false).
255                 resource(RESOURCE_NAME_TESTDB).value("123password").build());
256         userUR.getResources().add(new StringPatchItem.Builder().
257                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_TESTDB).build());
258 
259         ProvisioningResult<UserTO> result = updateUser(userUR);
260         assertNotNull(result);
261 
262         List<PropagationStatus> propagations = result.getPropagationStatuses();
263         assertNotNull(propagations);
264         assertEquals(1, propagations.size());
265 
266         assertEquals(ExecStatus.SUCCESS, propagations.get(0).getStatus());
267 
268         String resource = propagations.get(0).getResource();
269         assertEquals(RESOURCE_NAME_TESTDB, resource);
270     }
271 
272     @Test
273     public void issue281() {
274         UserCR userCR = UserITCase.getUniqueSample("issue281@syncope.apache.org");
275         userCR.getResources().clear();
276         userCR.getMemberships().clear();
277         userCR.getResources().add(RESOURCE_NAME_CSV);
278 
279         ProvisioningResult<UserTO> result = createUser(userCR);
280         assertNotNull(result);
281 
282         List<PropagationStatus> propagations = result.getPropagationStatuses();
283         assertNotNull(propagations);
284         assertEquals(1, propagations.size());
285         assertNotEquals(ExecStatus.SUCCESS, propagations.get(0).getStatus());
286 
287         String resource = propagations.get(0).getResource();
288         assertEquals(RESOURCE_NAME_CSV, resource);
289     }
290 
291     @Test
292     public void issue288() {
293         UserCR userTO = UserITCase.getSample("issue288@syncope.apache.org");
294         userTO.getPlainAttrs().add(attr("aLong", "STRING"));
295 
296         try {
297             createUser(userTO);
298             fail("This should not happen");
299         } catch (SyncopeClientException e) {
300             assertEquals(ClientExceptionType.InvalidValues, e.getType());
301         }
302     }
303 
304     @Test
305     public void issueSYNCOPE108() {
306         UserCR userCR = UserITCase.getUniqueSample("syncope108@syncope.apache.org");
307         userCR.getResources().clear();
308         userCR.getMemberships().clear();
309         userCR.getVirAttrs().clear();
310         userCR.getAuxClasses().add("csv");
311 
312         userCR.getMemberships().add(new MembershipTO.Builder("0626100b-a4ba-4e00-9971-86fad52a6216").build());
313         userCR.getMemberships().add(new MembershipTO.Builder("ba9ed509-b1f5-48ab-a334-c8530a6422dc").build());
314 
315         userCR.getResources().add(RESOURCE_NAME_CSV);
316 
317         UserTO userTO = createUser(userCR).getEntity();
318         assertNotNull(userTO);
319         assertEquals(2, userTO.getMemberships().size());
320         assertEquals(1, userTO.getResources().size());
321 
322         ConnObject connObjectTO =
323                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
324         assertNotNull(connObjectTO);
325 
326         // -----------------------------------
327         // Remove the first membership: de-provisioning shouldn't happen
328         // -----------------------------------
329         UserUR userUR = new UserUR.Builder(userTO.getKey()).
330                 membership(new MembershipUR.Builder(userTO.getMemberships().get(0).getGroupKey()).
331                         operation(PatchOperation.DELETE).build()).
332                 build();
333 
334         userTO = updateUser(userUR).getEntity();
335         assertNotNull(userTO);
336         assertEquals(1, userTO.getMemberships().size());
337 
338         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
339         assertNotNull(connObjectTO);
340         // -----------------------------------
341 
342         // -----------------------------------
343         // Remove the resource assigned directly: de-provisioning shouldn't happen
344         // -----------------------------------
345         userUR = new UserUR();
346         userUR.setKey(userTO.getKey());
347 
348         userUR.getResources().add(new StringPatchItem.Builder().operation(PatchOperation.DELETE).
349                 value(userTO.getResources().iterator().next()).build());
350 
351         userTO = updateUser(userUR).getEntity();
352         assertNotNull(userTO);
353         assertEquals(1, userTO.getMemberships().size());
354         assertFalse(userTO.getResources().isEmpty());
355 
356         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
357         assertNotNull(connObjectTO);
358         // -----------------------------------
359 
360         // -----------------------------------
361         // Remove the first membership: de-provisioning should happen
362         // -----------------------------------
363         userUR = new UserUR.Builder(userTO.getKey()).
364                 membership(new MembershipUR.Builder(userTO.getMemberships().get(0).getGroupKey()).
365                         operation(PatchOperation.DELETE).build()).
366                 build();
367 
368         userTO = updateUser(userUR).getEntity();
369         assertNotNull(userTO);
370         assertTrue(userTO.getMemberships().isEmpty());
371         assertTrue(userTO.getResources().isEmpty());
372 
373         try {
374             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
375             fail("Read should not succeeed");
376         } catch (SyncopeClientException e) {
377             assertEquals(ClientExceptionType.NotFound, e.getType());
378         }
379     }
380 
381     @Test
382     public void issueSYNCOPE185() {
383         // 1. create user with LDAP resource, succesfully propagated
384         UserCR userCR = UserITCase.getSample("syncope185@syncope.apache.org");
385         userCR.getVirAttrs().clear();
386         userCR.getResources().add(RESOURCE_NAME_LDAP);
387 
388         ProvisioningResult<UserTO> result = createUser(userCR);
389         assertNotNull(result);
390         assertFalse(result.getPropagationStatuses().isEmpty());
391         assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
392         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
393         UserTO userTO = result.getEntity();
394 
395         // 2. delete this user
396         USER_SERVICE.delete(userTO.getKey());
397 
398         // 3. try (and fail) to find this user on the external LDAP resource
399         try {
400             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
401             fail("This entry should not be present on this resource");
402         } catch (SyncopeClientException e) {
403             assertEquals(ClientExceptionType.NotFound, e.getType());
404         }
405     }
406 
407     @Test()
408     public void issueSYNCOPE51() {
409         String originalCA = confParamOps.get(SyncopeConstants.MASTER_DOMAIN,
410                 "password.cipher.algorithm", null, String.class);
411         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", "MD5");
412 
413         UserCR userCR = UserITCase.getSample("syncope51@syncope.apache.org");
414         userCR.setPassword("password");
415 
416         try {
417             createUser(userCR);
418             fail("Create user should not succeed");
419         } catch (SyncopeClientException e) {
420             assertEquals(ClientExceptionType.NotFound, e.getType());
421             assertTrue(e.getElements().iterator().next().contains("MD5"));
422         } finally {
423             confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", originalCA);
424         }
425     }
426 
427     @Test
428     public void issueSYNCOPE267() {
429         // ----------------------------------
430         // create user and check virtual attribute value propagation
431         // ----------------------------------
432         UserCR userCR = UserITCase.getUniqueSample("syncope267@apache.org");
433         userCR.getVirAttrs().add(attr("virtualdata", "virtualvalue"));
434         userCR.getResources().clear();
435         userCR.getResources().add(RESOURCE_NAME_DBVIRATTR);
436 
437         ProvisioningResult<UserTO> result = createUser(userCR);
438         assertNotNull(result);
439         assertFalse(result.getPropagationStatuses().isEmpty());
440         assertEquals(RESOURCE_NAME_DBVIRATTR, result.getPropagationStatuses().get(0).getResource());
441         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
442         UserTO userTO = result.getEntity();
443 
444         ConnObject connObjectTO =
445                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_DBVIRATTR, AnyTypeKind.USER.name(), userTO.getKey());
446         assertNotNull(connObjectTO);
447         assertEquals("virtualvalue", connObjectTO.getAttr("USERNAME").get().getValues().get(0));
448         // ----------------------------------
449 
450         userTO = USER_SERVICE.read(userTO.getKey());
451 
452         assertNotNull(userTO);
453         assertEquals(1, userTO.getVirAttrs().size());
454         assertEquals("virtualvalue", userTO.getVirAttrs().iterator().next().getValues().get(0));
455     }
456 
457     @Test
458     public void issueSYNCOPE266() {
459         UserCR userCR = UserITCase.getUniqueSample("syncope266@apache.org");
460         userCR.getResources().clear();
461 
462         UserTO userTO = createUser(userCR).getEntity();
463         assertNotNull(userTO);
464 
465         UserUR userUR = new UserUR();
466         userUR.setKey(userTO.getKey());
467 
468         // this resource has not a mapping for Password
469         userUR.getResources().add(new StringPatchItem.Builder().
470                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_UPDATE).build());
471 
472         userTO = updateUser(userUR).getEntity();
473         assertNotNull(userTO);
474     }
475 
476     @Test
477     public void issueSYNCOPE279() {
478         UserCR userCR = UserITCase.getUniqueSample("syncope279@apache.org");
479         userCR.getResources().clear();
480         userCR.getResources().add(RESOURCE_NAME_TIMEOUT);
481         ProvisioningResult<UserTO> result = createUser(userCR);
482         assertEquals(RESOURCE_NAME_TIMEOUT, result.getPropagationStatuses().get(0).getResource());
483         assertNotNull(result.getPropagationStatuses().get(0).getFailureReason());
484         assertEquals(ExecStatus.FAILURE, result.getPropagationStatuses().get(0).getStatus());
485     }
486 
487     @Test
488     public void issueSYNCOPE122() {
489         // 1. create user on testdb and testdb2
490         UserCR userCR = UserITCase.getUniqueSample("syncope122@apache.org");
491         userCR.getResources().clear();
492 
493         userCR.getResources().add(RESOURCE_NAME_TESTDB);
494         userCR.getResources().add(RESOURCE_NAME_TESTDB2);
495 
496         UserTO userTO = createUser(userCR).getEntity();
497         assertNotNull(userTO);
498         assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB));
499         assertTrue(userTO.getResources().contains(RESOURCE_NAME_TESTDB2));
500 
501         String pwdOnSyncope = userTO.getPassword();
502 
503         ConnObject userOnDb = RESOURCE_SERVICE.readConnObject(
504                 RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey());
505         Attr pwdOnTestDbAttr = userOnDb.getAttr(OperationalAttributes.PASSWORD_NAME).get();
506         assertNotNull(pwdOnTestDbAttr);
507         assertNotNull(pwdOnTestDbAttr.getValues());
508         assertFalse(pwdOnTestDbAttr.getValues().isEmpty());
509         String pwdOnTestDb = pwdOnTestDbAttr.getValues().get(0);
510 
511         ConnObject userOnDb2 = RESOURCE_SERVICE.readConnObject(
512                 RESOURCE_NAME_TESTDB2, AnyTypeKind.USER.name(), userTO.getKey());
513         Attr pwdOnTestDb2Attr = userOnDb2.getAttr(OperationalAttributes.PASSWORD_NAME).get();
514         assertNotNull(pwdOnTestDb2Attr);
515         assertNotNull(pwdOnTestDb2Attr.getValues());
516         assertFalse(pwdOnTestDb2Attr.getValues().isEmpty());
517         String pwdOnTestDb2 = pwdOnTestDb2Attr.getValues().get(0);
518 
519         // 2. request to change password only on testdb (no Syncope, no testdb2)
520         UserUR userUR = new UserUR();
521         userUR.setKey(userTO.getKey());
522         userUR.setPassword(new PasswordPatch.Builder().value(UUID.randomUUID().toString()).onSyncope(false).
523                 resource(RESOURCE_NAME_TESTDB).build());
524 
525         ProvisioningResult<UserTO> result = updateUser(userUR);
526         userTO = result.getEntity();
527 
528         // 3a. Chech that only a single propagation took place
529         assertNotNull(result.getPropagationStatuses());
530         assertEquals(1, result.getPropagationStatuses().size());
531         assertEquals(RESOURCE_NAME_TESTDB, result.getPropagationStatuses().get(0).getResource());
532 
533         // 3b. verify that password hasn't changed on Syncope
534         assertEquals(pwdOnSyncope, userTO.getPassword());
535 
536         // 3c. verify that password *has* changed on testdb
537         userOnDb = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey());
538         Attr pwdOnTestDbAttrAfter = userOnDb.getAttr(OperationalAttributes.PASSWORD_NAME).get();
539         assertNotNull(pwdOnTestDbAttrAfter);
540         assertNotNull(pwdOnTestDbAttrAfter.getValues());
541         assertFalse(pwdOnTestDbAttrAfter.getValues().isEmpty());
542         assertNotEquals(pwdOnTestDb, pwdOnTestDbAttrAfter.getValues().get(0));
543 
544         // 3d. verify that password hasn't changed on testdb2
545         userOnDb2 = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB2, AnyTypeKind.USER.name(), userTO.getKey());
546         Attr pwdOnTestDb2AttrAfter = userOnDb2.getAttr(OperationalAttributes.PASSWORD_NAME).get();
547         assertNotNull(pwdOnTestDb2AttrAfter);
548         assertNotNull(pwdOnTestDb2AttrAfter.getValues());
549         assertFalse(pwdOnTestDb2AttrAfter.getValues().isEmpty());
550         assertEquals(pwdOnTestDb2, pwdOnTestDb2AttrAfter.getValues().get(0));
551     }
552 
553     @Test
554     public void issueSYNCOPE136AES() {
555         // 1. read configured cipher algorithm in order to be able to restore it at the end of test
556         String origpwdCipherAlgo = confParamOps.get(SyncopeConstants.MASTER_DOMAIN,
557                 "password.cipher.algorithm", null, String.class);
558 
559         // 2. set AES password cipher algorithm
560         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", "AES");
561 
562         UserTO userTO = null;
563         try {
564             // 3. create user with no resources
565             UserCR userCR = UserITCase.getUniqueSample("syncope136_AES@apache.org");
566             userCR.getResources().clear();
567 
568             userTO = createUser(userCR).getEntity();
569             assertNotNull(userTO);
570 
571             // 4. update user, assign a propagation priority resource but don't provide any password
572             UserUR userUR = new UserUR();
573             userUR.setKey(userTO.getKey());
574             userUR.getResources().add(new StringPatchItem.Builder().
575                     operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_LDAP).build());
576             userUR.setPassword(new PasswordPatch.Builder().onSyncope(false).resource(RESOURCE_NAME_LDAP).build());
577 
578             ProvisioningResult<UserTO> result = updateUser(userUR);
579             assertNotNull(result);
580             userTO = result.getEntity();
581             assertNotNull(userTO);
582 
583             // 5. verify that propagation was successful
584             List<PropagationStatus> props = result.getPropagationStatuses();
585             assertNotNull(props);
586             assertEquals(1, props.size());
587             PropagationStatus prop = props.get(0);
588             assertNotNull(prop);
589             assertEquals(RESOURCE_NAME_LDAP, prop.getResource());
590             assertEquals(ExecStatus.SUCCESS, prop.getStatus());
591         } finally {
592             // restore initial cipher algorithm
593             confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", origpwdCipherAlgo);
594 
595             if (userTO != null) {
596                 deleteUser(userTO.getKey());
597             }
598         }
599     }
600 
601     @Test
602     public void issueSYNCOPE136Random() {
603         // 1. create user with no resources
604         UserCR userCR = UserITCase.getUniqueSample("syncope136_Random@apache.org");
605         userCR.getResources().clear();
606         UserTO userTO = createUser(userCR).getEntity();
607         assertNotNull(userTO);
608 
609         // 2. update user, assign a propagation priority resource but don't provide any password
610         UserUR userUR = new UserUR();
611         userUR.setKey(userTO.getKey());
612         userUR.getResources().add(new StringPatchItem.Builder().
613                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_LDAP).build());
614         userUR.setPassword(new PasswordPatch.Builder().onSyncope(false).resource(RESOURCE_NAME_LDAP).build());
615 
616         ProvisioningResult<UserTO> result = updateUser(userUR);
617         assertNotNull(result);
618 
619         // 3. verify that propagation was successful
620         List<PropagationStatus> props = result.getPropagationStatuses();
621         assertNotNull(props);
622         assertEquals(1, props.size());
623         PropagationStatus prop = props.get(0);
624         assertNotNull(prop);
625         assertEquals(RESOURCE_NAME_LDAP, prop.getResource());
626         assertEquals(ExecStatus.SUCCESS, prop.getStatus());
627     }
628 
629     @Test
630     public void issueSYNCOPE265() {
631         String[] userKeys = new String[] {
632             "1417acbe-cbf6-4277-9372-e75e04f97000",
633             "74cd8ece-715a-44a4-a736-e17b46c4e7e6",
634             "b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee",
635             "c9b2dec2-00a7-4855-97c0-d854842b4b24",
636             "823074dc-d280-436d-a7dd-07399fae48ec" };
637 
638         for (String userKey : userKeys) {
639             UserUR userUR = new UserUR();
640             userUR.setKey(userKey);
641             userUR.getPlainAttrs().add(attrAddReplacePatch("ctype", "a type"));
642             UserTO userTO = updateUser(userUR).getEntity();
643             assertEquals("a type", userTO.getPlainAttr("ctype").get().getValues().get(0));
644         }
645     }
646 
647     @Test
648     public void issueSYNCOPE354() {
649         // change resource-ldap group mapping for including uniqueMember (need for assertions below)
650         ResourceTO ldap = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
651         ldap.getProvision(AnyTypeKind.GROUP.name()).get().getMapping().getItems().stream().
652                 filter(item -> ("description".equals(item.getExtAttrName()))).
653                 forEach(item -> item.setExtAttrName("uniqueMember"));
654         RESOURCE_SERVICE.update(ldap);
655 
656         // 1. create group with LDAP resource
657         GroupCR groupCR = new GroupCR();
658         groupCR.setName("SYNCOPE354-" + getUUIDString());
659         groupCR.setRealm("/");
660         groupCR.getResources().add(RESOURCE_NAME_LDAP);
661 
662         GroupTO groupTO = createGroup(groupCR).getEntity();
663         assertNotNull(groupTO);
664 
665         // 2. create user with LDAP resource and membership of the above group
666         UserCR userCR = UserITCase.getUniqueSample("syncope354@syncope.apache.org");
667         userCR.getResources().add(RESOURCE_NAME_LDAP);
668         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
669 
670         UserTO userTO = createUser(userCR).getEntity();
671         assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
672         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey()));
673 
674         // 3. read group on resource, check that user DN is included in uniqueMember
675         ConnObject connObj = RESOURCE_SERVICE.readConnObject(
676                 RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
677         assertNotNull(connObj);
678         assertTrue(connObj.getAttr("uniqueMember").get().getValues().
679                 contains("uid=" + userTO.getUsername() + ",ou=people,o=isp"));
680 
681         // 4. remove membership
682         UserUR userUR = new UserUR();
683         userUR.setKey(userTO.getKey());
684         userUR.getMemberships().add(new MembershipUR.Builder(userTO.getMemberships().get(0).getGroupKey()).
685                 operation(PatchOperation.DELETE).build());
686 
687         userTO = updateUser(userUR).getEntity();
688         assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
689 
690         // 5. read group on resource, check that user DN was removed from uniqueMember
691         connObj = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), groupTO.getKey());
692         assertNotNull(connObj);
693         assertFalse(connObj.getAttr("uniqueMember").get().getValues().
694                 contains("uid=" + userTO.getUsername() + ",ou=people,o=isp"));
695 
696         // 6. user has still the LDAP resource assigned - SYNCOPE-1222
697         userTO = USER_SERVICE.read(userTO.getKey());
698         assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
699         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey()));
700 
701         // 7. restore original resource-ldap group mapping
702         ldap.getProvision(AnyTypeKind.GROUP.name()).get().getMapping().getItems().stream().
703                 filter(item -> "uniqueMember".equals(item.getExtAttrName())).
704                 forEach(item -> item.setExtAttrName("description"));
705         RESOURCE_SERVICE.update(ldap);
706     }
707 
708     @Test
709     public void issueSYNCOPE357() throws IOException {
710         // 1. create group with LDAP resource
711         GroupCR groupCR = new GroupCR();
712         groupCR.setName("SYNCOPE357-" + getUUIDString());
713         groupCR.setRealm("/");
714         groupCR.getResources().add(RESOURCE_NAME_LDAP);
715 
716         GroupTO groupTO = createGroup(groupCR).getEntity();
717         assertNotNull(groupTO);
718 
719         // 2. create user with membership of the above group
720         UserCR userCR = UserITCase.getUniqueSample("syncope357@syncope.apache.org");
721         userCR.getPlainAttrs().add(attr("obscure", "valueToBeObscured"));
722         userCR.getPlainAttrs().add(attr("photo", Base64.getEncoder().encodeToString(
723                 IOUtils.readBytesFromStream(getClass().getResourceAsStream("/favicon.jpg")))));
724         userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
725 
726         UserTO userTO = createUser(userCR).getEntity();
727         assertTrue(userTO.getResources().contains(RESOURCE_NAME_LDAP));
728         assertNotNull(userTO.getPlainAttr("obscure"));
729         assertNotNull(userTO.getPlainAttr("photo"));
730 
731         // 3. read user on resource
732         ConnObject connObj = RESOURCE_SERVICE.readConnObject(
733                 RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
734         assertNotNull(connObj);
735         Attr registeredAddress = connObj.getAttr("registeredAddress").get();
736         assertNotNull(registeredAddress);
737         assertEquals(userTO.getPlainAttr("obscure").get().getValues(), registeredAddress.getValues());
738         Optional<Attr> jpegPhoto = connObj.getAttr("jpegPhoto");
739         assertTrue(jpegPhoto.isPresent());
740         assertEquals(userTO.getPlainAttr("photo").get().getValues().get(0), jpegPhoto.get().getValues().get(0));
741 
742         // 4. remove group
743         GROUP_SERVICE.delete(groupTO.getKey());
744 
745         // 5. try to read user on resource: fail
746         try {
747             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
748             fail("This should not happen");
749         } catch (SyncopeClientException e) {
750             assertEquals(ClientExceptionType.NotFound, e.getType());
751         }
752     }
753 
754     @Test
755     public void issueSYNCOPE383() {
756         // 1. create user without resources
757         UserCR userCR = UserITCase.getUniqueSample("syncope383@apache.org");
758         userCR.getResources().clear();
759         UserTO userTO = createUser(userCR).getEntity();
760         assertNotNull(userTO);
761 
762         // 2. assign resource without specifying a new pwd and check propagation failure
763         UserUR userUR = new UserUR();
764         userUR.setKey(userTO.getKey());
765         userUR.getResources().add(new StringPatchItem.Builder().
766                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_TESTDB).build());
767 
768         ProvisioningResult<UserTO> result = updateUser(userUR);
769         assertNotNull(result);
770         userTO = result.getEntity();
771         assertEquals(RESOURCE_NAME_TESTDB, userTO.getResources().iterator().next());
772         assertNotEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
773         assertNotNull(result.getPropagationStatuses().get(0).getFailureReason());
774         userTO = result.getEntity();
775 
776         // 3. request to change password only on testdb
777         userUR = new UserUR();
778         userUR.setKey(userTO.getKey());
779         userUR.setPassword(
780                 new PasswordPatch.Builder().value(getUUIDString() + "abbcbcbddd123").resource(RESOURCE_NAME_TESTDB).
781                         build());
782 
783         result = updateUser(userUR);
784         assertEquals(RESOURCE_NAME_TESTDB, userTO.getResources().iterator().next());
785         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
786     }
787 
788     @Test
789     public void issueSYNCOPE402() {
790         // 1. create an user with strict mandatory attributes only
791         UserCR userCR = new UserCR();
792         userCR.setRealm(SyncopeConstants.ROOT_REALM);
793         String userId = getUUIDString() + "syncope402@syncope.apache.org";
794         userCR.setUsername(userId);
795         userCR.setPassword("password123");
796 
797         userCR.getPlainAttrs().add(attr("userId", userId));
798         userCR.getPlainAttrs().add(attr("fullname", userId));
799         userCR.getPlainAttrs().add(attr("surname", userId));
800 
801         UserTO userTO = createUser(userCR).getEntity();
802         assertNotNull(userTO);
803         assertTrue(userTO.getResources().isEmpty());
804 
805         // 2. update assigning a resource NOT forcing mandatory constraints
806         // AND priority: must fail with PropagationException
807         UserUR userUR = new UserUR();
808         userUR.setKey(userTO.getKey());
809         userUR.setPassword(new PasswordPatch.Builder().value("newPassword123").build());
810         userUR.getResources().add(new StringPatchItem.Builder().
811                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_WS1).build());
812         userUR.getResources().add(new StringPatchItem.Builder().
813                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_TESTDB).build());
814         ProvisioningResult<UserTO> result = updateUser(userUR);
815 
816         PropagationStatus ws1PropagationStatus = result.getPropagationStatuses().stream().
817                 filter(propStatus -> RESOURCE_NAME_WS1.equals(propStatus.getResource())).
818                 findFirst().orElse(null);
819         assertNotNull(ws1PropagationStatus);
820         assertEquals(RESOURCE_NAME_WS1, ws1PropagationStatus.getResource());
821         assertNotNull(ws1PropagationStatus.getFailureReason());
822         assertEquals(ExecStatus.FAILURE, ws1PropagationStatus.getStatus());
823     }
824 
825     @Test
826     public void issueSYNCOPE420() throws IOException {
827         ImplementationTO logicActions;
828         try {
829             logicActions = IMPLEMENTATION_SERVICE.read(
830                     IdRepoImplementationType.LOGIC_ACTIONS, "DoubleValueLogicActions");
831         } catch (SyncopeClientException e) {
832             logicActions = new ImplementationTO();
833             logicActions.setKey("DoubleValueLogicActions");
834             logicActions.setEngine(ImplementationEngine.GROOVY);
835             logicActions.setType(IdRepoImplementationType.LOGIC_ACTIONS);
836             logicActions.setBody(org.apache.commons.io.IOUtils.toString(
837                     getClass().getResourceAsStream("/DoubleValueLogicActions.groovy"), StandardCharsets.UTF_8));
838             Response response = IMPLEMENTATION_SERVICE.create(logicActions);
839             logicActions = IMPLEMENTATION_SERVICE.read(
840                     logicActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
841         }
842         assertNotNull(logicActions);
843 
844         RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().get(0);
845         assertNotNull(realm);
846         realm.getActions().add(logicActions.getKey());
847         REALM_SERVICE.update(realm);
848 
849         UserCR userCR = UserITCase.getUniqueSample("syncope420@syncope.apache.org");
850         userCR.setRealm(realm.getFullPath());
851         userCR.getPlainAttrs().add(attr("makeItDouble", "3"));
852 
853         UserTO userTO = createUser(userCR).getEntity();
854         assertEquals("6", userTO.getPlainAttr("makeItDouble").get().getValues().get(0));
855 
856         UserUR userUR = new UserUR();
857         userUR.setKey(userTO.getKey());
858         userUR.getPlainAttrs().add(attrAddReplacePatch("makeItDouble", "7"));
859 
860         userTO = updateUser(userUR).getEntity();
861         assertEquals("14", userTO.getPlainAttr("makeItDouble").get().getValues().get(0));
862     }
863 
864     @Test
865     public void issueSYNCOPE426() {
866         UserCR userCR = UserITCase.getUniqueSample("syncope426@syncope.apache.org");
867         UserTO userTO = createUser(userCR).getEntity();
868         assertNotNull(userTO);
869 
870         UserUR userUR = new UserUR();
871         userUR.setKey(userTO.getKey());
872         userUR.setPassword(new PasswordPatch.Builder().value("anotherPassword123").build());
873         userTO = USER_SERVICE.update(userUR).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
874         }).getEntity();
875         assertNotNull(userTO);
876     }
877 
878     @Test
879     public void issueSYNCOPE435() {
880         // 1. create user without password
881         UserCR userCR = UserITCase.getUniqueSample("syncope435@syncope.apache.org");
882         userCR.setPassword(null);
883         userCR.setStorePassword(false);
884         UserTO userTO = createUser(userCR).getEntity();
885         assertNotNull(userTO);
886 
887         // 2. try to update user by subscribing a resource - works but propagation is not even attempted
888         UserUR userUR = new UserUR();
889         userUR.setKey(userTO.getKey());
890         userUR.getResources().add(new StringPatchItem.Builder().
891                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_WS1).build());
892 
893         ProvisioningResult<UserTO> result = updateUser(userUR);
894         assertNotNull(result);
895         userTO = result.getEntity();
896         assertEquals(Set.of(RESOURCE_NAME_WS1), userTO.getResources());
897         assertNotEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
898         assertTrue(result.getPropagationStatuses().get(0).getFailureReason().
899                 startsWith("Not attempted because there are mandatory attributes without value(s): [__PASSWORD__]"));
900     }
901 
902     @Test
903     public void issueSYNCOPE454() throws NamingException {
904         // 1. create user with LDAP resource (with 'Generate password if missing' enabled)
905         UserCR userCR = UserITCase.getUniqueSample("syncope454@syncope.apache.org");
906         userCR.getResources().add(RESOURCE_NAME_LDAP);
907         UserTO userTO = createUser(userCR).getEntity();
908         assertNotNull(userTO);
909 
910         // 2. read resource configuration for LDAP binding
911         ConnObject connObject =
912                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), userTO.getKey());
913 
914         // 3. try (and succeed) to perform simple LDAP binding with provided password ('password123')
915         assertNotNull(getLdapRemoteObject(
916                 connObject.getAttr(Name.NAME).get().getValues().get(0),
917                 "password123",
918                 connObject.getAttr(Name.NAME).get().getValues().get(0)));
919 
920         // 4. update user without any password change request
921         UserUR userUR = new UserUR();
922         userUR.setKey(userTO.getKey());
923         userUR.setPassword(new PasswordPatch());
924         userUR.getPlainAttrs().add(attrAddReplacePatch("surname", "surname2"));
925 
926         USER_SERVICE.update(userUR);
927 
928         // 5. try (and succeed again) to perform simple LDAP binding: password has not changed
929         assertNotNull(getLdapRemoteObject(
930                 connObject.getAttr(Name.NAME).get().getValues().get(0),
931                 "password123",
932                 connObject.getAttr(Name.NAME).get().getValues().get(0)));
933     }
934 
935     @Test
936     public void issueSYNCOPE493() {
937         // 1.  create user and check that firstname is not propagated on resource with mapping for firstname set to NONE
938         UserCR userCR = UserITCase.getUniqueSample("493@test.org");
939         userCR.getResources().add(RESOURCE_NAME_WS1);
940         ProvisioningResult<UserTO> result = createUser(userCR);
941         assertNotNull(result);
942         assertEquals(1, result.getPropagationStatuses().size());
943         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
944         UserTO userTO = result.getEntity();
945 
946         ConnObject actual =
947                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_WS1, AnyTypeKind.USER.name(), userTO.getKey());
948         assertNotNull(actual);
949         // check if mapping attribute with purpose NONE really hasn't been propagated
950         assertFalse(actual.getAttr("NAME").isPresent());
951 
952         // 2.  update resource ws-target-resource-1
953         ResourceTO ws1 = RESOURCE_SERVICE.read(RESOURCE_NAME_WS1);
954         assertNotNull(ws1);
955 
956         Mapping ws1NewUMapping = ws1.getProvision(AnyTypeKind.USER.name()).get().getMapping();
957         // change purpose from NONE to BOTH
958         ws1NewUMapping.getItems().stream().
959                 filter(itemTO -> "firstname".equals(itemTO.getIntAttrName())).
960                 forEach(itemTO -> itemTO.setPurpose(MappingPurpose.BOTH));
961 
962         ws1.getProvision(AnyTypeKind.USER.name()).get().setMapping(ws1NewUMapping);
963 
964         RESOURCE_SERVICE.update(ws1);
965         ResourceTO newWs1 = RESOURCE_SERVICE.read(ws1.getKey());
966         assertNotNull(newWs1);
967 
968         // check for existence
969         Collection<Item> mapItems = newWs1.getProvision(AnyTypeKind.USER.name()).get().getMapping().getItems();
970         assertNotNull(mapItems);
971         assertEquals(7, mapItems.size());
972 
973         // 3.  update user and check firstname propagation
974         UserUR userUR = new UserUR();
975         userUR.setKey(userTO.getKey());
976         userUR.setPassword(new PasswordPatch());
977         userUR.getPlainAttrs().add(attrAddReplacePatch("firstname", "firstnameNew"));
978 
979         result = updateUser(userUR);
980         assertNotNull(userTO);
981         assertEquals(1, result.getPropagationStatuses().size());
982         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
983         userTO = result.getEntity();
984 
985         ConnObject newUser =
986                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_WS1, AnyTypeKind.USER.name(), userTO.getKey());
987 
988         assertNotNull(newUser.getAttr("NAME"));
989         assertEquals("firstnameNew", newUser.getAttr("NAME").get().getValues().get(0));
990 
991         // 4.  restore resource ws-target-resource-1 mapping
992         ws1NewUMapping = newWs1.getProvision(AnyTypeKind.USER.name()).get().getMapping();
993         // restore purpose from BOTH to NONE
994         for (Item itemTO : ws1NewUMapping.getItems()) {
995             if ("firstname".equals(itemTO.getIntAttrName())) {
996                 itemTO.setPurpose(MappingPurpose.NONE);
997             }
998         }
999 
1000         newWs1.getProvision(AnyTypeKind.USER.name()).get().setMapping(ws1NewUMapping);
1001 
1002         RESOURCE_SERVICE.update(newWs1);
1003     }
1004 
1005     @Test
1006     public void issueSYNCOPE505DB() throws Exception {
1007         // 1. create user
1008         UserCR userCR = UserITCase.getUniqueSample("syncope505-db@syncope.apache.org");
1009         userCR.setPassword("security123");
1010         UserTO user = createUser(userCR).getEntity();
1011         assertNotNull(user);
1012         assertTrue(user.getResources().isEmpty());
1013 
1014         // 2. Add DBPasswordPropagationActions
1015         ImplementationTO propagationActions = new ImplementationTO();
1016         propagationActions.setKey(DBPasswordPropagationActions.class.getSimpleName());
1017         propagationActions.setEngine(ImplementationEngine.JAVA);
1018         propagationActions.setType(IdMImplementationType.PROPAGATION_ACTIONS);
1019         propagationActions.setBody(DBPasswordPropagationActions.class.getName());
1020         Response response = IMPLEMENTATION_SERVICE.create(propagationActions);
1021         propagationActions = IMPLEMENTATION_SERVICE.read(
1022                 propagationActions.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
1023         assertNotNull(propagationActions);
1024 
1025         ResourceTO resourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_TESTDB);
1026         assertNotNull(resourceTO);
1027         resourceTO.getPropagationActions().add(propagationActions.getKey());
1028         RESOURCE_SERVICE.update(resourceTO);
1029 
1030         // 3. Add a db resource to the User
1031         UserUR userUR = new UserUR();
1032         userUR.setKey(user.getKey());
1033         userUR.getResources().add(new StringPatchItem.Builder().
1034                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_TESTDB).build());
1035 
1036         userUR.setPassword(new PasswordPatch.Builder().onSyncope(false).resource(RESOURCE_NAME_TESTDB).build());
1037 
1038         user = updateUser(userUR).getEntity();
1039         assertNotNull(user);
1040         assertEquals(1, user.getResources().size());
1041 
1042         // 4. Check that the DB resource has the correct password
1043         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
1044         String value = jdbcTemplate.queryForObject(
1045                 "SELECT PASSWORD FROM test WHERE ID=?", String.class, user.getUsername());
1046         assertEquals(Encryptor.getInstance().encode("security123", CipherAlgorithm.SHA1), value.toUpperCase());
1047 
1048         // 5. Remove DBPasswordPropagationActions
1049         resourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_TESTDB);
1050         assertNotNull(resourceTO);
1051         resourceTO.getPropagationActions().remove(propagationActions.getKey());
1052         RESOURCE_SERVICE.update(resourceTO);
1053     }
1054 
1055     @Test
1056     public void issueSYNCOPE505LDAP() throws Exception {
1057         // 1. create user
1058         UserCR userCR = UserITCase.getUniqueSample("syncope505-ldap@syncope.apache.org");
1059         userCR.setPassword("security123");
1060         UserTO user = createUser(userCR).getEntity();
1061         assertNotNull(user);
1062         assertTrue(user.getResources().isEmpty());
1063 
1064         // 2. Add LDAPPasswordPropagationActions
1065         ResourceTO resourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
1066         assertNotNull(resourceTO);
1067         resourceTO.getPropagationActions().add(LDAPPasswordPropagationActions.class.getSimpleName());
1068         resourceTO.getPropagationActions().remove(GenerateRandomPasswordPropagationActions.class.getSimpleName());
1069         RESOURCE_SERVICE.update(resourceTO);
1070 
1071         // 3. Add a resource to the User
1072         UserUR userUR = new UserUR();
1073         userUR.setKey(user.getKey());
1074         userUR.getResources().add(new StringPatchItem.Builder().
1075                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_LDAP).build());
1076 
1077         userUR.setPassword(new PasswordPatch.Builder().onSyncope(false).resource(RESOURCE_NAME_LDAP).build());
1078 
1079         user = updateUser(userUR).getEntity();
1080         assertNotNull(user);
1081         assertEquals(1, user.getResources().size());
1082 
1083         // 4. Check that the LDAP resource has the correct password
1084         ConnObject connObject =
1085                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), user.getKey());
1086 
1087         assertNotNull(getLdapRemoteObject(
1088                 connObject.getAttr(Name.NAME).get().getValues().get(0),
1089                 "security123",
1090                 connObject.getAttr(Name.NAME).get().getValues().get(0)));
1091 
1092         // 5. Remove LDAPPasswordPropagationActions
1093         resourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_LDAP);
1094         assertNotNull(resourceTO);
1095         resourceTO.getPropagationActions().remove(LDAPPasswordPropagationActions.class.getSimpleName());
1096         resourceTO.getPropagationActions().add(0, GenerateRandomPasswordPropagationActions.class.getSimpleName());
1097         RESOURCE_SERVICE.update(resourceTO);
1098     }
1099 
1100     @Test
1101     public void issueSYNCOPE391() {
1102         assumeFalse(IS_EXT_SEARCH_ENABLED);
1103 
1104         // 1. create user on Syncope with null password
1105         UserCR userCR = UserITCase.getUniqueSample("syncope391@syncope.apache.org");
1106         userCR.setPassword(null);
1107         userCR.setStorePassword(false);
1108 
1109         UserTO userTO = createUser(userCR).getEntity();
1110         assertNotNull(userTO);
1111         assertNull(userTO.getPassword());
1112 
1113         // 2. create existing user on csv and check that password on Syncope is null and that password on resource
1114         // doesn't change
1115         userCR = new UserCR();
1116         userCR.setRealm(SyncopeConstants.ROOT_REALM);
1117         userCR.setPassword(null);
1118         userCR.setStorePassword(false);
1119         userCR.setUsername("syncope391@syncope.apache.org");
1120         userCR.getPlainAttrs().add(attr("fullname", "fullname"));
1121         userCR.getPlainAttrs().add(attr("firstname", "nome0"));
1122         userCR.getPlainAttrs().add(attr("surname", "cognome0"));
1123         userCR.getPlainAttrs().add(attr("userId", "syncope391@syncope.apache.org"));
1124         userCR.getPlainAttrs().add(attr("email", "syncope391@syncope.apache.org"));
1125         userCR.getAuxClasses().add("csv");
1126         userCR.getResources().add(RESOURCE_NAME_CSV);
1127 
1128         userTO = createUser(userCR).getEntity();
1129         assertNotNull(userTO);
1130 
1131         ConnObject connObjectTO =
1132                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
1133         assertNotNull(connObjectTO);
1134 
1135         // check if password has not changed
1136         assertEquals("password0", connObjectTO.getAttr(OperationalAttributes.PASSWORD_NAME).get().getValues().get(0));
1137         assertNull(userTO.getPassword());
1138 
1139         // 3. create user with not null password and propagate onto resource-csv, specify not to save password on
1140         // Syncope local storage
1141         userCR = UserITCase.getUniqueSample("syncope391@syncope.apache.org");
1142         userCR.setPassword("passwordTESTNULL1");
1143         userCR.setStorePassword(false);
1144         userCR.getVirAttrs().clear();
1145         userCR.getAuxClasses().add("csv");
1146         userCR.getResources().add(RESOURCE_NAME_CSV);
1147 
1148         userTO = createUser(userCR).getEntity();
1149         assertNotNull(userTO);
1150 
1151         connObjectTO =
1152                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
1153         assertNotNull(connObjectTO);
1154 
1155         // check if password has been propagated and that saved userTO's password is null
1156         assertEquals(
1157                 "passwordTESTNULL1",
1158                 connObjectTO.getAttr(OperationalAttributes.PASSWORD_NAME).get().getValues().get(0));
1159         assertNull(userTO.getPassword());
1160 
1161         // 4. create user and propagate password on resource-csv and on Syncope local storage
1162         userCR = UserITCase.getUniqueSample("syncope391@syncope.apache.org");
1163         userCR.setPassword("passwordTESTNULL1");
1164         userCR.getVirAttrs().clear();
1165         userCR.getAuxClasses().add("csv");
1166         userCR.getResources().add(RESOURCE_NAME_CSV);
1167 
1168         // storePassword true by default
1169         userTO = createUser(userCR).getEntity();
1170         assertNotNull(userTO);
1171 
1172         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_CSV, AnyTypeKind.USER.name(), userTO.getKey());
1173         assertNotNull(connObjectTO);
1174 
1175         // check if password has been correctly propagated on Syncope and resource-csv as usual
1176         assertEquals(
1177                 "passwordTESTNULL1",
1178                 connObjectTO.getAttr(OperationalAttributes.PASSWORD_NAME).get().getValues().get(0));
1179         Triple<Map<String, Set<String>>, List<String>, UserTO> self =
1180                 CLIENT_FACTORY.create(userTO.getUsername(), "passwordTESTNULL1").self();
1181         assertNotNull(self);
1182 
1183         // 4. add password policy to resource with passwordNotStore to false --> must store password
1184         ResourceTO csv = RESOURCE_SERVICE.read(RESOURCE_NAME_CSV);
1185         assertNotNull(csv);
1186         try {
1187             csv.setPasswordPolicy("55e5de0b-c79c-4e66-adda-251b6fb8579a");
1188             RESOURCE_SERVICE.update(csv);
1189             csv = RESOURCE_SERVICE.read(RESOURCE_NAME_CSV);
1190             assertEquals("55e5de0b-c79c-4e66-adda-251b6fb8579a", csv.getPasswordPolicy());
1191 
1192             userCR = UserITCase.getUniqueSample("syncope391@syncope.apache.org");
1193             userCR.setPassword(null);
1194             userCR.setStorePassword(false);
1195             userCR.getVirAttrs().clear();
1196             userCR.getAuxClasses().add("csv");
1197             userCR.getResources().add(RESOURCE_NAME_CSV);
1198 
1199             createUser(userCR);
1200             fail("This should not happen");
1201         } catch (SyncopeClientException e) {
1202             assertEquals(ClientExceptionType.InvalidUser, e.getType());
1203             assertTrue(e.getMessage().contains("Password mandatory"));
1204         } finally {
1205             // resource csv with null password policy
1206             csv.setPasswordPolicy(null);
1207             RESOURCE_SERVICE.update(csv);
1208         }
1209     }
1210 
1211     @Test
1212     public void issueSYNCOPE647() {
1213         UserCR userCR = UserITCase.getUniqueSample("syncope647@syncope.apache.org");
1214         userCR.getResources().clear();
1215         userCR.getMemberships().clear();
1216         userCR.getVirAttrs().clear();
1217         userCR.getAuxClasses().add("csv");
1218 
1219         userCR.getAuxClasses().add("generic membership");
1220         userCR.getPlainAttrs().add(attr("postalAddress", "postalAddress"));
1221 
1222         userCR.getResources().add(RESOURCE_NAME_LDAP);
1223 
1224         UserTO actual = createUser(userCR).getEntity();
1225         assertNotNull(actual);
1226         assertNotNull(actual.getDerAttr("csvuserid"));
1227 
1228         ConnObject connObjectTO =
1229                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), actual.getKey());
1230         assertNotNull(connObjectTO);
1231         assertEquals("postalAddress", connObjectTO.getAttr("postalAddress").get().getValues().get(0));
1232 
1233         UserUR userUR = new UserUR();
1234         userUR.setKey(actual.getKey());
1235         userUR.getPlainAttrs().add(attrAddReplacePatch("postalAddress", "newPostalAddress"));
1236 
1237         actual = updateUser(userUR).getEntity();
1238 
1239         connObjectTO = RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), actual.getKey());
1240         assertNotNull(connObjectTO);
1241         assertEquals("newPostalAddress", connObjectTO.getAttr("postalAddress").get().getValues().get(0));
1242     }
1243 
1244     @Test
1245     public void issueSYNCOPE626() {
1246         DefaultPasswordRuleConf ruleConf = new DefaultPasswordRuleConf();
1247         ruleConf.setUsernameAllowed(false);
1248 
1249         ImplementationTO rule = new ImplementationTO();
1250         rule.setKey("DefaultPasswordRuleConf" + getUUIDString());
1251         rule.setEngine(ImplementationEngine.JAVA);
1252         rule.setType(IdRepoImplementationType.PASSWORD_RULE);
1253         rule.setBody(POJOHelper.serialize(ruleConf));
1254         Response response = IMPLEMENTATION_SERVICE.create(rule);
1255         rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
1256 
1257         PasswordPolicyTO passwordPolicy = new PasswordPolicyTO();
1258         passwordPolicy.setName("Password Policy for SYNCOPE-626");
1259         passwordPolicy.getRules().add(rule.getKey());
1260 
1261         passwordPolicy = createPolicy(PolicyType.PASSWORD, passwordPolicy);
1262         assertNotNull(passwordPolicy);
1263 
1264         RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().get(0);
1265         String oldPasswordPolicy = realm.getPasswordPolicy();
1266         realm.setPasswordPolicy(passwordPolicy.getKey());
1267         REALM_SERVICE.update(realm);
1268 
1269         try {
1270             UserCR userCR = UserITCase.getUniqueSample("syncope626@syncope.apache.org");
1271             userCR.setRealm(realm.getFullPath());
1272             userCR.setPassword(userCR.getUsername());
1273             try {
1274                 createUser(userCR);
1275                 fail("This should not happen");
1276             } catch (SyncopeClientException e) {
1277                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
1278                 assertTrue(e.getElements().iterator().next().startsWith("InvalidPassword"));
1279             }
1280 
1281             userCR.setPassword("password123");
1282             UserTO user = createUser(userCR).getEntity();
1283             assertNotNull(user);
1284         } finally {
1285             realm.setPasswordPolicy(oldPasswordPolicy);
1286             REALM_SERVICE.update(realm);
1287 
1288             POLICY_SERVICE.delete(PolicyType.PASSWORD, passwordPolicy.getKey());
1289         }
1290 
1291     }
1292 
1293     @Test
1294     public void issueSYNCOPE686() {
1295         // 1. read configured cipher algorithm in order to be able to restore it at the end of test
1296         String origpwdCipherAlgo = confParamOps.get(SyncopeConstants.MASTER_DOMAIN,
1297                 "password.cipher.algorithm", null, String.class);
1298 
1299         // 2. set AES password cipher algorithm
1300         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", "AES");
1301 
1302         try {
1303             // 3. create group with LDAP resource assigned
1304             GroupCR groupCR = GroupITCase.getBasicSample("syncope686");
1305             groupCR.getResources().add(RESOURCE_NAME_LDAP);
1306             GroupTO group = createGroup(groupCR).getEntity();
1307             assertNotNull(group);
1308 
1309             // 4. create user with no resources
1310             UserCR userCR = UserITCase.getUniqueSample("syncope686@apache.org");
1311             userCR.getResources().clear();
1312 
1313             UserTO userTO = createUser(userCR).getEntity();
1314             assertNotNull(userTO);
1315 
1316             // 5. update user with the new group, and don't provide any password
1317             UserUR userUR = new UserUR();
1318             userUR.setKey(userTO.getKey());
1319             userUR.getMemberships().add(new MembershipUR.Builder(group.getKey()).
1320                     operation(PatchOperation.ADD_REPLACE).build());
1321 
1322             ProvisioningResult<UserTO> result = updateUser(userUR);
1323             assertNotNull(result);
1324 
1325             // 5. verify that propagation was successful
1326             List<PropagationStatus> props = result.getPropagationStatuses();
1327             assertNotNull(props);
1328             assertEquals(1, props.size());
1329             PropagationStatus prop = props.get(0);
1330             assertNotNull(prop);
1331             assertEquals(RESOURCE_NAME_LDAP, prop.getResource());
1332             assertEquals(ExecStatus.SUCCESS, prop.getStatus());
1333         } finally {
1334             // restore initial cipher algorithm
1335             confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", origpwdCipherAlgo);
1336         }
1337     }
1338 
1339     @Test
1340     public void issueSYNCOPE710() {
1341         // 1. create groups for indirect resource assignment
1342         GroupCR ldapGroupCR = GroupITCase.getBasicSample("syncope710.ldap");
1343         ldapGroupCR.getResources().add(RESOURCE_NAME_LDAP);
1344         GroupTO ldapGroup = createGroup(ldapGroupCR).getEntity();
1345 
1346         GroupCR dbGroupCR = GroupITCase.getBasicSample("syncope710.db");
1347         dbGroupCR.getResources().add(RESOURCE_NAME_TESTDB);
1348         GroupTO dbGroup = createGroup(dbGroupCR).getEntity();
1349 
1350         // 2. create user with memberships for the groups created above
1351         UserCR userCR = UserITCase.getUniqueSample("syncope710@syncope.apache.org");
1352         userCR.getResources().clear();
1353         userCR.getMemberships().clear();
1354         userCR.getMemberships().add(new MembershipTO.Builder(ldapGroup.getKey()).build());
1355         userCR.getMemberships().add(new MembershipTO.Builder(dbGroup.getKey()).build());
1356 
1357         ProvisioningResult<UserTO> result = createUser(userCR);
1358         assertEquals(2, result.getPropagationStatuses().size());
1359         UserTO userTO = result.getEntity();
1360 
1361         // 3. request to propagate password only to db
1362         UserUR userUR = new UserUR();
1363         userUR.setKey(userTO.getKey());
1364         userUR.setPassword(new PasswordPatch.Builder().
1365                 onSyncope(false).resource(RESOURCE_NAME_TESTDB).value("newpassword123").build());
1366 
1367         result = updateUser(userUR);
1368         assertEquals(1, result.getPropagationStatuses().size());
1369         assertEquals(RESOURCE_NAME_TESTDB, result.getPropagationStatuses().get(0).getResource());
1370     }
1371 
1372     @Test
1373     public void issueSYNCOPE881() {
1374         // 1. create group and assign LDAP
1375         GroupCR groupCR = GroupITCase.getSample("syncope881G");
1376         groupCR.getVirAttrs().add(attr("rvirtualdata", "rvirtualvalue"));
1377 
1378         GroupTO group = createGroup(groupCR).getEntity();
1379         assertNotNull(group);
1380         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), group.getKey()));
1381 
1382         // 2. create user and assign such group
1383         UserCR userCR = UserITCase.getUniqueSample("syncope881U@apache.org");
1384         userCR.getMemberships().clear();
1385         userCR.getMemberships().add(new MembershipTO.Builder(group.getKey()).build());
1386 
1387         UserTO user = createUser(userCR).getEntity();
1388         assertNotNull(user);
1389 
1390         // 3. verify that user is in LDAP
1391         ConnObject connObject =
1392                 RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), user.getKey());
1393         assertNotNull(connObject);
1394         Attr userDn = connObject.getAttr(Name.NAME).get();
1395         assertNotNull(userDn);
1396         assertEquals(1, userDn.getValues().size());
1397         assertNotNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, userDn.getValues().get(0)));
1398 
1399         // 4. remove user
1400         USER_SERVICE.delete(user.getKey());
1401 
1402         // 5. verify that user is not in LDAP anymore
1403         assertNull(getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, userDn.getValues().get(0)));
1404     }
1405 
1406     @Test
1407     public void issueSYNCOPE1099() {
1408         // 1. create group with dynamic condition and resource
1409         GroupCR groupCR = GroupITCase.getSample("syncope1099G");
1410         groupCR.getResources().clear();
1411         groupCR.getResources().add(RESOURCE_NAME_TESTDB);
1412         groupCR.setUDynMembershipCond("firstname==issueSYNCOPE1099");
1413 
1414         GroupTO group = createGroup(groupCR).getEntity();
1415         assertNotNull(group);
1416 
1417         // 2. create user matching the condition above
1418         UserCR userCR = UserITCase.getUniqueSample("syncope1099U@apache.org");
1419         userCR.getPlainAttr("firstname").get().getValues().set(0, "issueSYNCOPE1099");
1420 
1421         ProvisioningResult<UserTO> created = createUser(userCR);
1422         assertNotNull(created);
1423 
1424         // 3. verify that dynamic membership is set and that resource is consequently assigned
1425         UserTO user = created.getEntity();
1426         String groupKey = group.getKey();
1427         assertTrue(user.getDynMemberships().stream().anyMatch(m -> m.getGroupKey().equals(groupKey)));
1428         assertTrue(user.getResources().contains(RESOURCE_NAME_TESTDB));
1429 
1430         // 4. verify that propagation happened towards the resource of the dynamic group
1431         assertFalse(created.getPropagationStatuses().isEmpty());
1432         assertEquals(RESOURCE_NAME_TESTDB, created.getPropagationStatuses().get(0).getResource());
1433     }
1434 
1435     @Test
1436     public void issueSYNCOPE1166() {
1437         UserCR userCR = UserITCase.getUniqueSample("syncope1166@apache.org");
1438         UserTO userTO = createUser(userCR).getEntity();
1439         assertNotNull(userTO);
1440 
1441         UserUR userUR = new UserUR();
1442         userUR.setKey(userTO.getKey());
1443         // resource-ldap has password mapped, resource-db-virattr does not
1444         userUR.setPassword(new PasswordPatch.Builder().
1445                 onSyncope(true).
1446                 resource(RESOURCE_NAME_LDAP).
1447                 value("new2Password").build());
1448 
1449         userUR.getResources().add(new StringPatchItem.Builder().
1450                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_LDAP).build());
1451         userUR.getResources().add(new StringPatchItem.Builder().
1452                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_DBVIRATTR).build());
1453 
1454         ProvisioningResult<UserTO> result = updateUser(userUR);
1455         assertNotNull(result);
1456         assertEquals(2, result.getPropagationStatuses().size());
1457         assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
1458         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1459         assertEquals(RESOURCE_NAME_DBVIRATTR, result.getPropagationStatuses().get(1).getResource());
1460         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(1).getStatus());
1461     }
1462 
1463     @Test
1464     public void issueSYNCOPE1206() {
1465         // 1. create group with dynamic user condition 'cool==true'
1466         GroupCR dynGroupCR = GroupITCase.getSample("syncope1206");
1467         dynGroupCR.setUDynMembershipCond(
1468                 SyncopeClient.getUserSearchConditionBuilder().is("cool").equalTo("true").query());
1469         GroupTO dynGroup = createGroup(dynGroupCR).getEntity();
1470         assertNotNull(dynGroup);
1471         assertTrue(dynGroup.getResources().contains(RESOURCE_NAME_LDAP));
1472 
1473         // 2. create user (no value for cool, no dynamic membership, no propagation to LDAP)
1474         UserCR userCR = UserITCase.getUniqueSample("syncope1206@apache.org");
1475         userCR.getResources().clear();
1476 
1477         ProvisioningResult<UserTO> result = createUser(userCR);
1478         assertTrue(result.getPropagationStatuses().isEmpty());
1479 
1480         // 3. update user to match the dynamic condition: expect propagation to LDAP
1481         UserUR userUR = new UserUR();
1482         userUR.setKey(result.getEntity().getKey());
1483         userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("cool", "true")).build());
1484 
1485         result = updateUser(userUR);
1486         assertEquals(1, result.getPropagationStatuses().size());
1487         assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
1488 
1489         // 4. update again user to not match the dynamic condition any more: expect propagation to LDAP
1490         userUR = new UserUR();
1491         userUR.setKey(result.getEntity().getKey());
1492         userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("cool", "false")).build());
1493 
1494         result = updateUser(userUR);
1495         assertEquals(1, result.getPropagationStatuses().size());
1496         assertEquals(RESOURCE_NAME_LDAP, result.getPropagationStatuses().get(0).getResource());
1497     }
1498 
1499     @Test
1500     public void issueSYNCOPE1337() {
1501         // 1. save current cipher algorithm and set it to something salted
1502         String original = confParamOps.get(SyncopeConstants.MASTER_DOMAIN,
1503                 "password.cipher.algorithm", null, String.class);
1504 
1505         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", CipherAlgorithm.SSHA512.name());
1506 
1507         try {
1508             // 2. create user under /even/two to get password policy with history length 1
1509             UserCR userCR = UserITCase.getUniqueSample("syncope1337@apache.org");
1510             userCR.setPassword("Password123");
1511             userCR.setRealm("/even/two");
1512             UserTO userTO = createUser(userCR).getEntity();
1513             assertNotNull(userTO);
1514 
1515             // 3. attempt to set the same password value: fails
1516             UserUR req = new UserUR();
1517             req.setKey(userTO.getKey());
1518             req.setPassword(new PasswordPatch.Builder().onSyncope(true).value("Password123").build());
1519             try {
1520                 updateUser(req);
1521                 fail("Password update should not work");
1522             } catch (SyncopeClientException e) {
1523                 assertEquals(ClientExceptionType.InvalidUser, e.getType());
1524                 assertTrue(e.getMessage().contains("InvalidPassword"));
1525             }
1526 
1527             // 4. set another password value: works
1528             req.setPassword(new PasswordPatch.Builder().onSyncope(true).value("Password124").build());
1529             userTO = updateUser(req).getEntity();
1530             assertNotNull(userTO);
1531 
1532             // 5. set the original password value: works (history length is 1)
1533             req.setPassword(new PasswordPatch.Builder().onSyncope(true).value("Password123").build());
1534             userTO = updateUser(req).getEntity();
1535             assertNotNull(userTO);
1536         } finally {
1537             // finally revert the cipher algorithm
1538             confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", original);
1539         }
1540     }
1541 
1542     @Test
1543     public void issueSYNCOPE1472() {
1544         // 1. update user rossini by assigning twice resource-testdb2 and auxiliary class csv
1545         UserUR userUR = new UserUR();
1546         userUR.setKey("1417acbe-cbf6-4277-9372-e75e04f97000");
1547         userUR.setPassword(new PasswordPatch.Builder()
1548                 .onSyncope(false)
1549                 .resource(RESOURCE_NAME_TESTDB)
1550                 .value("Password123")
1551                 .build());
1552         userUR.getResources().add(new StringPatchItem.Builder()
1553                 .value(RESOURCE_NAME_TESTDB)
1554                 .operation(PatchOperation.ADD_REPLACE)
1555                 .build());
1556         userUR.getAuxClasses().add(new StringPatchItem.Builder()
1557                 .operation(PatchOperation.ADD_REPLACE)
1558                 .value("csv")
1559                 .build());
1560         userUR.getRoles().add(new StringPatchItem.Builder()
1561                 .operation(PatchOperation.ADD_REPLACE)
1562                 .value("Other")
1563                 .build());
1564 
1565         updateUser(userUR);
1566         updateUser(userUR);
1567 
1568         // 2. remove resources, auxiliary classes and roles
1569         userUR.getResources().clear();
1570         userUR.getResources().add(new StringPatchItem.Builder()
1571                 .value(RESOURCE_NAME_TESTDB)
1572                 .operation(PatchOperation.DELETE)
1573                 .build());
1574         userUR.getAuxClasses().clear();
1575         userUR.getAuxClasses().add(new StringPatchItem.Builder()
1576                 .value("csv")
1577                 .operation(PatchOperation.DELETE)
1578                 .build());
1579         userUR.getRoles().clear();
1580         userUR.getRoles().add(new StringPatchItem.Builder()
1581                 .value("Other")
1582                 .operation(PatchOperation.DELETE)
1583                 .build());
1584         updateUser(userUR);
1585 
1586         UserTO userTO = USER_SERVICE.read("1417acbe-cbf6-4277-9372-e75e04f97000");
1587         assertFalse(userTO.getResources().contains(RESOURCE_NAME_TESTDB), "Should not contain removed resources");
1588         assertFalse(userTO.getAuxClasses().contains("csv"), "Should not contain removed auxiliary classes");
1589         assertFalse(userTO.getRoles().contains("Other"), "Should not contain removed roles");
1590     }
1591 
1592     @Test
1593     public void issueSYNCOPE1699() throws Exception {
1594         UserTO userTO = createUser(UserITCase.getUniqueSample("syncope1669@apache.org")).getEntity();
1595 
1596         UserUR req = new UserUR();
1597         req.setUsername(new StringReplacePatchItem.Builder().value("newUsername" + getUUIDString()).build());
1598 
1599         WebClient webClient = WebClient.create(ADDRESS + "/users/" + userTO.getKey(), ADMIN_UNAME, ADMIN_PWD, null).
1600                 accept(MediaType.APPLICATION_JSON_TYPE).
1601                 type(MediaType.APPLICATION_JSON_TYPE);
1602 
1603         Response response = webClient.invoke(HttpMethod.PATCH, JSON_MAPPER.writeValueAsString(req));
1604         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
1605 
1606         // Key is mismatched in the path parameter and the request body.
1607         req.setKey(UUID.randomUUID().toString());
1608         response = webClient.invoke(HttpMethod.PATCH, JSON_MAPPER.writeValueAsString(req));
1609         assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
1610 
1611         // reading user by its username still works
1612         userTO = USER_SERVICE.read(req.getUsername().getValue());
1613         assertNotNull(userTO);
1614     }
1615 
1616     @Test
1617     public void issueSYNCOPE1750() {
1618         UserCR userCR = UserITCase.getUniqueSample("syncope1750@apache.org");
1619         userCR.getResources().add(RESOURCE_NAME_NOPROPAGATION);
1620         UserTO userTO = createUser(userCR).getEntity();
1621 
1622         UserUR req = new UserUR.Builder(userTO.getKey()).password(new PasswordPatch.Builder().
1623                 onSyncope(false).resource(RESOURCE_NAME_NOPROPAGATION).value("short").build()).build();
1624 
1625         try {
1626             USER_SERVICE.update(req);
1627             fail();
1628         } catch (SyncopeClientException e) {
1629             assertEquals(ClientExceptionType.InvalidUser, e.getType());
1630             assertTrue(e.getMessage().contains("InvalidPassword: Password must be 10 or more characters in length."));
1631         }
1632     }
1633 
1634     @Test
1635     public void issueSYNCOPE1793() {
1636         RoleTO role = new RoleTO();
1637         role.setKey("syncope1793" + getUUIDString());
1638         role.getRealms().add(SyncopeConstants.ROOT_REALM);
1639         role.getEntitlements().add(IdRepoEntitlement.USER_UPDATE);
1640         role = createRole(role);
1641 
1642         UserCR userCR = UserITCase.getUniqueSample("syncope1793@apache.org");
1643         userCR.getRoles().add(role.getKey());
1644         UserTO userTO = createUser(userCR).getEntity();
1645 
1646         UserService userService = CLIENT_FACTORY.create(userTO.getUsername(), "password123").
1647                 getService(UserService.class);
1648         Response response = userService.associate(new ResourceAR.Builder().key(userTO.getKey()).
1649                 resource(RESOURCE_NAME_NOPROPAGATION).action(ResourceAssociationAction.ASSIGN).build());
1650         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
1651     }
1652 
1653     @Test
1654     public void issueSYNCOPE1809() throws IOException {
1655         // 1. add a new schema externalKey and update provision accordingly
1656         PlainSchemaTO externalKeySchemaTO = new PlainSchemaTO();
1657         externalKeySchemaTO.setKey("externalKey");
1658         externalKeySchemaTO.setType(AttrSchemaType.String);
1659         externalKeySchemaTO.setReadonly(true);
1660         SCHEMA_SERVICE.create(SchemaType.PLAIN, externalKeySchemaTO);
1661         try {
1662             AnyTypeClassTO minimalUser = ANY_TYPE_CLASS_SERVICE.read("minimal user");
1663             minimalUser.getPlainSchemas().add(externalKeySchemaTO.getKey());
1664             ANY_TYPE_CLASS_SERVICE.update(minimalUser);
1665             ResourceTO restResourceTO = RESOURCE_SERVICE.read(RESOURCE_NAME_REST);
1666             restResourceTO.getProvision(AnyTypeKind.USER.name())
1667                     .ifPresent(p -> p.setUidOnCreate(externalKeySchemaTO.getKey()));
1668             RESOURCE_SERVICE.update(restResourceTO);
1669             UserCR userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
1670             userCR.getResources().clear();
1671             userCR.getResources().add(RESOURCE_NAME_REST);
1672 
1673             // 2. create
1674             ProvisioningResult<UserTO> result = createUser(userCR);
1675             assertEquals(1, result.getPropagationStatuses().size());
1676             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1677             assertEquals(RESOURCE_NAME_REST, result.getPropagationStatuses().get(0).getResource());
1678             assertEquals("surname", result.getEntity().getPlainAttr("surname").get().getValues().get(0));
1679             // externalKey is going to be populated on create
1680             assertTrue(result.getEntity().getPlainAttr("externalKey").isPresent());
1681             assertEquals(result.getEntity().getKey(),
1682                     result.getEntity().getPlainAttr("externalKey").get().getValues().get(0));
1683             // 3. remove resource from the user
1684             result = updateUser(new UserUR.Builder(result.getEntity()
1685                     .getKey()).resource(new StringPatchItem.Builder().value(RESOURCE_NAME_REST)
1686                     .operation(PatchOperation.DELETE)
1687                     .build()).build());
1688             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1689             // externalKey is going to be removed on resource unassignment
1690             assertFalse(result.getEntity().getPlainAttr("externalKey").isPresent());
1691 
1692             // 4. create a new user and deprovision, attribute is cleared
1693             userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
1694             userCR.getResources().clear();
1695             userCR.getResources().add(RESOURCE_NAME_REST);
1696             result = createUser(userCR);
1697             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1698             // this time fire a deprovision
1699             assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new ResourceDR.Builder().key(result.getEntity()
1700                     .getKey()).action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_REST).build())));
1701             UserTO restUserTO = USER_SERVICE.read(result.getEntity().getKey());
1702             assertFalse(restUserTO.getPlainAttr("externalKey").isPresent());
1703 
1704             // 5. create a new user and unlink, attribute is not cleared since provisioning hasn't been fired
1705             userCR = UserITCase.getUniqueSample("rest@syncope.apache.org");
1706             userCR.getResources().clear();
1707             userCR.getResources().add(RESOURCE_NAME_REST);
1708             result = createUser(userCR);
1709             assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
1710             // this time deprovision
1711             assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new ResourceDR.Builder().key(result.getEntity()
1712                     .getKey()).action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_REST).build())));
1713             restUserTO = USER_SERVICE.read(result.getEntity().getKey());
1714             assertTrue(restUserTO.getPlainAttr("externalKey").isPresent());
1715         } finally {
1716             // remove additional externalKey schema
1717             SCHEMA_SERVICE.delete(SchemaType.PLAIN, externalKeySchemaTO.getKey());
1718         }
1719     }
1720 
1721     @Test
1722     void issueSYNCOPE1818() {
1723         UserTO rossini = USER_SERVICE.read("rossini");
1724         try {
1725             // 1. provision rossini on resource-db-pull
1726             updateUser(new UserUR.Builder(rossini.getKey()).
1727                     plainAttr(attrAddReplacePatch("email", "rossini@apache.org")).
1728                     resource(new StringPatchItem.Builder().value(RESOURCE_NAME_DBPULL).build()).
1729                     build());
1730 
1731             // 2. pull users from resource-db-pull
1732             ExecTO execution = AbstractTaskITCase.execProvisioningTask(TASK_SERVICE,
1733                     TaskType.PULL,
1734                     "7c2242f4-14af-4ab5-af31-cdae23783655",
1735                     MAX_WAIT_SECONDS,
1736                     false);
1737             assertEquals("SUCCESS", execution.getStatus());
1738             assertFalse(rossini.isSuspended());
1739             assertEquals("active", rossini.getStatus());
1740 
1741             // 3. push rossini on LDAP
1742             PushTaskTO pushTaskTO = new PushTaskTO();
1743             pushTaskTO.setSourceRealm(SyncopeConstants.ROOT_REALM);
1744             pushTaskTO.setMatchingRule(MatchingRule.UPDATE);
1745             pushTaskTO.setUnmatchingRule(UnmatchingRule.ASSIGN);
1746             pushTaskTO.setPerformCreate(true);
1747             pushTaskTO.setPerformUpdate(true);
1748             pushTaskTO.setSyncStatus(true);
1749             RECONCILIATION_SERVICE.push(new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).anyKey(
1750                     rossini.getKey()).build(), pushTaskTO);
1751 
1752             // 4. disable rossini on resource-db-pull to fire a propagation towards resource-ldap
1753             JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
1754             jdbcTemplate.update("UPDATE TESTPULL SET EMAIL ='gioacchino.rossini@apache.org', STATUS = "
1755                     + "'false' WHERE USERNAME = 'rossini'");
1756 
1757             // 5. pull again rossini from resource-db-pull
1758             execution = AbstractTaskITCase.execProvisioningTask(
1759                     TASK_SERVICE,
1760                     TaskType.PULL,
1761                     "7c2242f4-14af-4ab5-af31-cdae23783655",
1762                     MAX_WAIT_SECONDS,
1763                     false);
1764             assertEquals("SUCCESS", execution.getStatus());
1765 
1766             rossini = USER_SERVICE.read("rossini");
1767             assertTrue(rossini.isSuspended());
1768             assertEquals("suspended", rossini.getStatus());
1769             assertTrue(rossini.getPlainAttr("email").get().getValues().contains("gioacchino.rossini@apache.org"));
1770 
1771             ReconStatus onLDAP = RECONCILIATION_SERVICE.status(new ReconQuery.Builder(AnyTypeKind.USER.name(),
1772                     RESOURCE_NAME_LDAP).anyKey(rossini.getKey()).build());
1773             Attr enableAttr = onLDAP.getOnResource().getAttr(OperationalAttributes.ENABLE_NAME).orElseThrow();
1774             assertFalse(Boolean.parseBoolean(enableAttr.getValues().get(0)));
1775 
1776             // 6. re-enable on resource-db-pull and restore old values to fire a propagation towards resource-ldap
1777             jdbcTemplate.update("UPDATE TESTPULL SET EMAIL = 'rossini.gioacchino@apache.org', STATUS = "
1778                     + "'true' WHERE USERNAME = 'rossini'");
1779 
1780             // 7. pull again rossini from resource-db-pull
1781             execution = AbstractTaskITCase.execProvisioningTask(
1782                     TASK_SERVICE,
1783                     TaskType.PULL,
1784                     "7c2242f4-14af-4ab5-af31-cdae23783655",
1785                     MAX_WAIT_SECONDS,
1786                     false);
1787             assertEquals("SUCCESS", execution.getStatus());
1788 
1789             rossini = USER_SERVICE.read("rossini");
1790             assertFalse(rossini.isSuspended());
1791             assertEquals("active", rossini.getStatus());
1792 
1793             if (!IS_FLOWABLE_ENABLED) {
1794                 // we can check update only if on default workflow since flowable test workflow definition does not 
1795                 // support update of suspended users
1796                 assertTrue(rossini.getPlainAttr("email").get().getValues().contains("rossini.gioacchino@apache.org"));
1797 
1798                 onLDAP = RECONCILIATION_SERVICE.status(new ReconQuery.Builder(
1799                         AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).anyKey(rossini.getKey()).build());
1800                 enableAttr = onLDAP.getOnResource().getAttr(OperationalAttributes.ENABLE_NAME).orElseThrow();
1801                 assertTrue(Boolean.parseBoolean(enableAttr.getValues().get(0)));
1802             }
1803         } finally {
1804             // restore attributes and (if needed) status
1805             updateUser(new UserUR.Builder(rossini.getKey()).
1806                     plainAttrs(
1807                             attrAddReplacePatch("surname", "Rossini"),
1808                             new AttrPatch.Builder(
1809                                     new Attr.Builder("email").build()).operation(PatchOperation.DELETE).build()).
1810                     resource(new StringPatchItem.Builder().
1811                             value(RESOURCE_NAME_DBPULL).operation(PatchOperation.DELETE).build()).
1812                     build());
1813 
1814             if (USER_SERVICE.read("rossini").isSuspended()) {
1815                 USER_SERVICE.status(new StatusR.Builder(rossini.getKey(), StatusRType.REACTIVATE).onSyncope(true)
1816                         .resources(rossini.getResources()).build());
1817             }
1818 
1819             JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
1820             jdbcTemplate.update("DELETE FROM TESTPULL WHERE USERNAME = 'rossini'");
1821         }
1822     }
1823 }