View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.fit.core;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotEquals;
25  import static org.junit.jupiter.api.Assertions.assertNotNull;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  import static org.junit.jupiter.api.Assumptions.assumeTrue;
30  
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.UUID;
35  import java.util.concurrent.TimeUnit;
36  import javax.ws.rs.ForbiddenException;
37  import javax.ws.rs.core.GenericType;
38  import javax.ws.rs.core.Response;
39  import org.apache.commons.lang3.StringUtils;
40  import org.apache.commons.lang3.tuple.Triple;
41  import org.apache.syncope.client.lib.SyncopeClient;
42  import org.apache.syncope.common.lib.SyncopeClientException;
43  import org.apache.syncope.common.lib.SyncopeConstants;
44  import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
45  import org.apache.syncope.common.lib.request.MembershipUR;
46  import org.apache.syncope.common.lib.request.PasswordPatch;
47  import org.apache.syncope.common.lib.request.StringPatchItem;
48  import org.apache.syncope.common.lib.request.StringReplacePatchItem;
49  import org.apache.syncope.common.lib.request.UserCR;
50  import org.apache.syncope.common.lib.request.UserUR;
51  import org.apache.syncope.common.lib.to.MembershipTO;
52  import org.apache.syncope.common.lib.to.PagedResult;
53  import org.apache.syncope.common.lib.to.ProvisioningResult;
54  import org.apache.syncope.common.lib.to.UserRequestForm;
55  import org.apache.syncope.common.lib.to.UserTO;
56  import org.apache.syncope.common.lib.to.WorkflowTask;
57  import org.apache.syncope.common.lib.types.AnyTypeKind;
58  import org.apache.syncope.common.lib.types.ClientExceptionType;
59  import org.apache.syncope.common.lib.types.PatchOperation;
60  import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
61  import org.apache.syncope.common.rest.api.service.AccessTokenService;
62  import org.apache.syncope.common.rest.api.service.UserRequestService;
63  import org.apache.syncope.common.rest.api.service.UserSelfService;
64  import org.apache.syncope.common.rest.api.service.UserService;
65  import org.apache.syncope.fit.AbstractITCase;
66  import org.junit.jupiter.api.Test;
67  import org.springframework.dao.EmptyResultDataAccessException;
68  import org.springframework.jdbc.core.JdbcTemplate;
69  
70  public class UserSelfITCase extends AbstractITCase {
71  
72      @Test
73      public void selfRegistrationAllowed() {
74          assertTrue(ANONYMOUS_CLIENT.platform().isSelfRegAllowed());
75      }
76  
77      @Test
78      public void create() {
79          assumeTrue(IS_FLOWABLE_ENABLED);
80  
81          // 1. self-registration as admin: failure
82          try {
83              USER_SELF_SERVICE.create(UserITCase.getUniqueSample("anonymous@syncope.apache.org"));
84              fail("This should not happen");
85          } catch (ForbiddenException e) {
86              assertNotNull(e);
87          }
88  
89          // 2. self-registration as anonymous: works
90          UserTO self = ANONYMOUS_CLIENT.getService(UserSelfService.class).
91                  create(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).
92                  readEntity(new GenericType<ProvisioningResult<UserTO>>() {
93                  }).getEntity();
94          assertNotNull(self);
95          assertEquals("createApproval", self.getStatus());
96      }
97  
98      @Test
99      public void createAndApprove() {
100         assumeTrue(IS_FLOWABLE_ENABLED);
101 
102         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
103         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
104         userCR.getMemberships().add(
105                 new MembershipTO.Builder("29f96485-729e-4d31-88a1-6fc60e4677f3").build());
106         userCR.getResources().add(RESOURCE_NAME_TESTDB);
107 
108         UserTO userTO = ANONYMOUS_CLIENT.getService(UserSelfService.class).
109                 create(userCR).
110                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
111                 }).getEntity();
112         assertNotNull(userTO);
113         assertEquals("createApproval", userTO.getStatus());
114         assertFalse(userTO.getMemberships().isEmpty());
115         assertFalse(userTO.getResources().isEmpty());
116 
117         try {
118             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey());
119             fail("This should not happen");
120         } catch (SyncopeClientException e) {
121             assertEquals(ClientExceptionType.NotFound, e.getType());
122         }
123 
124         // 2. now approve and verify that propagation has happened
125         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
126                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
127         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
128         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
129         userTO = USER_REQUEST_SERVICE.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
130         }).getEntity();
131         assertNotNull(userTO);
132         assertEquals("active", userTO.getStatus());
133         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey()));
134     }
135 
136     @Test
137     public void createAndUnclaim() {
138         assumeTrue(IS_FLOWABLE_ENABLED);
139 
140         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
141         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
142         userCR.getMemberships().add(
143                 new MembershipTO.Builder("29f96485-729e-4d31-88a1-6fc60e4677f3").build());
144         userCR.getResources().add(RESOURCE_NAME_TESTDB);
145         UserTO userTO = ANONYMOUS_CLIENT.getService(UserSelfService.class).
146                 create(userCR).
147                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
148                 }).getEntity();
149         assertNotNull(userTO);
150         assertEquals("createApproval", userTO.getStatus());
151         assertFalse(userTO.getMemberships().isEmpty());
152         assertFalse(userTO.getResources().isEmpty());
153         try {
154             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey());
155             fail();
156         } catch (SyncopeClientException e) {
157             assertEquals(ClientExceptionType.NotFound, e.getType());
158         }
159 
160         // 2. unclaim and verify that propagation has NOT happened
161         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
162                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
163         form = USER_REQUEST_SERVICE.unclaimForm(form.getTaskId());
164         assertNull(form.getAssignee());
165         assertNotNull(userTO);
166         assertNotEquals("active", userTO.getStatus());
167         try {
168             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey());
169             fail();
170         } catch (Exception e) {
171             assertNotNull(e);
172         }
173 
174         // 3. approve and verify that propagation has happened
175         form = USER_REQUEST_SERVICE.listForms(
176                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
177         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
178         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
179         userTO = USER_REQUEST_SERVICE.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
180         }).getEntity();
181         assertNotNull(userTO);
182         assertEquals("active", userTO.getStatus());
183         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), userTO.getKey()));
184     }
185 
186     @Test
187     public void read() {
188         UserTO user = createUser(UserITCase.getUniqueSample("selfread@syncope.apache.org")).getEntity();
189         UserService us2 = CLIENT_FACTORY.create(user.getUsername(), "password123").getService(UserService.class);
190         try {
191             us2.read(user.getKey());
192             fail("This should not happen");
193         } catch (ForbiddenException e) {
194             assertNotNull(e);
195         }
196 
197         Triple<Map<String, Set<String>>, List<String>, UserTO> self =
198                 CLIENT_FACTORY.create(user.getUsername(), "password123").self();
199         assertEquals(user.getUsername(), self.getRight().getUsername());
200     }
201 
202     @Test
203     public void authenticateByPlainAttribute() {
204         UserTO rossini = USER_SERVICE.read("rossini");
205         assertNotNull(rossini);
206         String userId = rossini.getPlainAttr("userId").get().getValues().get(0);
207         assertNotNull(userId);
208 
209         Triple<Map<String, Set<String>>, List<String>, UserTO> self = CLIENT_FACTORY.create(userId, ADMIN_PWD).self();
210         assertEquals(rossini.getUsername(), self.getRight().getUsername());
211     }
212 
213     @Test
214     public void updateWithoutApproval() {
215         // 1. create user as admin
216         UserTO created = createUser(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).getEntity();
217         assertNotNull(created);
218         assertFalse(created.getUsername().endsWith("XX"));
219 
220         // 2. self-update (username) - works
221         UserUR userUR = new UserUR();
222         userUR.setKey(created.getKey());
223         userUR.setUsername(new StringReplacePatchItem.Builder().value(created.getUsername() + "XX").build());
224 
225         SyncopeClient authClient = CLIENT_FACTORY.create(created.getUsername(), "password123");
226         UserTO updated = authClient.getService(UserSelfService.class).update(userUR).
227                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
228                 }).getEntity();
229         assertNotNull(updated);
230         assertEquals(IS_FLOWABLE_ENABLED
231                 ? "active" : "created", updated.getStatus());
232         assertTrue(updated.getUsername().endsWith("XX"));
233     }
234 
235     @Test
236     public void updateWithApproval() {
237         assumeTrue(IS_FLOWABLE_ENABLED);
238 
239         // 1. create user as admin
240         UserTO created = createUser(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).getEntity();
241         assertNotNull(created);
242         assertFalse(created.getUsername().endsWith("XX"));
243 
244         // 2. self-update (username + memberships + resource) - works but needs approval
245         UserUR userUR = new UserUR();
246         userUR.setKey(created.getKey());
247         userUR.setUsername(new StringReplacePatchItem.Builder().value(created.getUsername() + "XX").build());
248         userUR.getMemberships().add(new MembershipUR.Builder("bf825fe1-7320-4a54-bd64-143b5c18ab97").
249                 operation(PatchOperation.ADD_REPLACE).
250                 build());
251         userUR.getResources().add(new StringPatchItem.Builder().
252                 operation(PatchOperation.ADD_REPLACE).value(RESOURCE_NAME_TESTDB).build());
253         userUR.setPassword(new PasswordPatch.Builder().
254                 value("newPassword123").onSyncope(false).resource(RESOURCE_NAME_TESTDB).build());
255 
256         SyncopeClient authClient = CLIENT_FACTORY.create(created.getUsername(), "password123");
257         UserTO updated = authClient.getService(UserSelfService.class).update(userUR).
258                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
259                 }).getEntity();
260         assertNotNull(updated);
261         assertEquals("updateApproval", updated.getStatus());
262         assertFalse(updated.getUsername().endsWith("XX"));
263         assertTrue(updated.getMemberships().isEmpty());
264 
265         // no propagation happened
266         assertTrue(updated.getResources().isEmpty());
267         try {
268             RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), updated.getKey());
269             fail("This should not happen");
270         } catch (SyncopeClientException e) {
271             assertEquals(ClientExceptionType.NotFound, e.getType());
272         }
273 
274         // 3. approve self-update as admin
275         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
276                 new UserRequestQuery.Builder().user(updated.getKey()).build()).getResult().get(0);
277         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
278         form.getProperty("approveUpdate").get().setValue(Boolean.TRUE.toString());
279         updated = USER_REQUEST_SERVICE.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
280         }).getEntity();
281         assertNotNull(updated);
282         assertEquals("active", updated.getStatus());
283         assertTrue(updated.getUsername().endsWith("XX"));
284         assertEquals(1, updated.getMemberships().size());
285 
286         // check that propagation also happened
287         assertTrue(updated.getResources().contains(RESOURCE_NAME_TESTDB));
288         assertNotNull(RESOURCE_SERVICE.readConnObject(RESOURCE_NAME_TESTDB, AnyTypeKind.USER.name(), updated.getKey()));
289     }
290 
291     @Test
292     public void delete() {
293         UserTO created = createUser(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).getEntity();
294         assertNotNull(created);
295 
296         SyncopeClient authClient = CLIENT_FACTORY.create(created.getUsername(), "password123");
297         UserTO deleted = authClient.getService(UserSelfService.class).delete().readEntity(
298                 new GenericType<ProvisioningResult<UserTO>>() {
299         }).getEntity();
300         assertNotNull(deleted);
301         assertEquals(IS_FLOWABLE_ENABLED
302                 ? "deleteApproval" : null, deleted.getStatus());
303     }
304 
305     @Test
306     public void passwordReset() {
307         // 0. ensure that password request DOES require security question
308         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "passwordReset.securityQuestion", true);
309 
310         // 1. create an user with security question and answer
311         UserCR user = UserITCase.getUniqueSample("pwdReset@syncope.apache.org");
312         user.setSecurityQuestion("887028ea-66fc-41e7-b397-620d7ea6dfbb");
313         user.setSecurityAnswer("Rossi");
314         user.getResources().add(RESOURCE_NAME_TESTDB);
315         createUser(user);
316 
317         // verify propagation (including password) on external db
318         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
319         String pwdOnResource = queryForObject(jdbcTemplate,
320                 MAX_WAIT_SECONDS, "SELECT password FROM test WHERE id=?", String.class, user.getUsername());
321         assertTrue(StringUtils.isNotBlank(pwdOnResource));
322 
323         // 2. verify that new user is able to authenticate
324         SyncopeClient authClient = CLIENT_FACTORY.create(user.getUsername(), "password123");
325         UserTO read = authClient.self().getRight();
326         assertNotNull(read);
327 
328         // 3. request password reset (as anonymous) providing the expected security answer
329         try {
330             ANONYMOUS_CLIENT.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "WRONG");
331             fail("This should not happen");
332         } catch (SyncopeClientException e) {
333             assertEquals(ClientExceptionType.InvalidSecurityAnswer, e.getType());
334         }
335         ANONYMOUS_CLIENT.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "Rossi");
336 
337         if (IS_EXT_SEARCH_ENABLED) {
338             try {
339                 Thread.sleep(2000);
340             } catch (InterruptedException ex) {
341                 // ignore
342             }
343         }
344 
345         // 4. get token (normally sent via e-mail, now reading as admin)
346         String token = await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(
347                 () -> USER_SERVICE.read(read.getKey()).getToken(),
348                 StringUtils::isNotBlank);
349 
350         // 5. confirm password reset
351         try {
352             ANONYMOUS_CLIENT.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword");
353             fail("This should not happen");
354         } catch (SyncopeClientException e) {
355             assertEquals(ClientExceptionType.NotFound, e.getType());
356             assertTrue(e.getMessage().contains("WRONG TOKEN"));
357         }
358         ANONYMOUS_CLIENT.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword123");
359 
360         // 6. verify that password was reset and token removed
361         authClient = CLIENT_FACTORY.create(user.getUsername(), "newPassword123");
362         assertNull(authClient.self().getRight().getToken());
363 
364         // 7. verify that password was changed on external resource
365         String newPwdOnResource = queryForObject(jdbcTemplate,
366                 MAX_WAIT_SECONDS, "SELECT password FROM test WHERE id=?", String.class, user.getUsername());
367         assertTrue(StringUtils.isNotBlank(newPwdOnResource));
368         assertNotEquals(pwdOnResource, newPwdOnResource);
369     }
370 
371     @Test
372     public void passwordResetWithoutSecurityQuestion() {
373         // 0. disable security question for password reset
374         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "passwordReset.securityQuestion", false);
375 
376         // 1. create an user with security question and answer
377         UserCR user = UserITCase.getUniqueSample("pwdResetNoSecurityQuestion@syncope.apache.org");
378         createUser(user);
379 
380         // 2. verify that new user is able to authenticate
381         SyncopeClient authClient = CLIENT_FACTORY.create(user.getUsername(), "password123");
382         UserTO read = authClient.self().getRight();
383         assertNotNull(read);
384 
385         // 3. request password reset (as anonymous) with no security answer
386         ANONYMOUS_CLIENT.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), null);
387 
388         // 4. get token (normally sent via e-mail, now reading as admin)
389         String token = USER_SERVICE.read(read.getKey()).getToken();
390         assertNotNull(token);
391 
392         // 5. confirm password reset
393         try {
394             ANONYMOUS_CLIENT.getService(UserSelfService.class).confirmPasswordReset("WRONG TOKEN", "newPassword");
395             fail("This should not happen");
396         } catch (SyncopeClientException e) {
397             assertEquals(ClientExceptionType.NotFound, e.getType());
398             assertTrue(e.getMessage().contains("WRONG TOKEN"));
399         }
400         ANONYMOUS_CLIENT.getService(UserSelfService.class).confirmPasswordReset(token, "newPassword123");
401 
402         // 6. verify that password was reset and token removed
403         authClient = CLIENT_FACTORY.create(user.getUsername(), "newPassword123");
404         read = authClient.self().getRight();
405         assertNotNull(read);
406         assertNull(read.getToken());
407 
408         // 7. re-enable security question for password reset
409         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, "passwordReset.securityQuestion", true);
410     }
411 
412     @Test
413     public void mustChangePassword() {
414         // PRE: reset vivaldi's password
415         UserUR userUR = new UserUR();
416         userUR.setKey("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee");
417         userUR.setPassword(new PasswordPatch.Builder().value("password321").build());
418         USER_SERVICE.update(userUR);
419 
420         // 0. access as vivaldi -> succeed
421         SyncopeClient vivaldiClient = CLIENT_FACTORY.create("vivaldi", "password321");
422         Response response = vivaldiClient.getService(AccessTokenService.class).refresh();
423         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
424 
425         // 1. update user vivaldi requiring password update
426         userUR = new UserUR();
427         userUR.setKey("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee");
428         userUR.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(true).build());
429         UserTO vivaldi = updateUser(userUR).getEntity();
430         assertTrue(vivaldi.isMustChangePassword());
431 
432         // 2. attempt to access -> fail
433         try {
434             vivaldiClient.self();
435             fail("This should not happen");
436         } catch (ForbiddenException e) {
437             assertNotNull(e);
438             assertEquals("Please change your password first", e.getMessage());
439         }
440 
441         // 3. change password
442         vivaldiClient.getService(UserSelfService.class).
443                 mustChangePassword(new PasswordPatch.Builder().value("password123").build());
444 
445         // 4. verify it worked
446         Triple<Map<String, Set<String>>, List<String>, UserTO> self =
447                 CLIENT_FACTORY.create("vivaldi", "password123").self();
448         assertFalse(self.getRight().isMustChangePassword());
449     }
450 
451     @Test
452     public void createWithReject() {
453         assumeTrue(IS_FLOWABLE_ENABLED);
454 
455         UserCR userCR = UserITCase.getUniqueSample("createWithReject@syncope.apache.org");
456         userCR.getResources().add(RESOURCE_NAME_TESTDB);
457 
458         // User with group 0cbcabd2-4410-4b6b-8f05-a052b451d18f are defined in workflow as subject to approval
459         userCR.getMemberships().add(new MembershipTO.Builder("0cbcabd2-4410-4b6b-8f05-a052b451d18f").build());
460 
461         // 1. create user with group 0cbcabd2-4410-4b6b-8f05-a052b451d18f
462         UserTO userTO = createUser(userCR).getEntity();
463         assertNotNull(userTO);
464         assertEquals(1, userTO.getMemberships().size());
465         assertEquals("0cbcabd2-4410-4b6b-8f05-a052b451d18f", userTO.getMemberships().get(0).getGroupKey());
466         assertEquals("createApproval", userTO.getStatus());
467 
468         // 2. request if there is any pending task for user just created
469         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
470                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
471         assertNotNull(form);
472         assertNotNull(form.getUsername());
473         assertEquals(userTO.getUsername(), form.getUsername());
474         assertNotNull(form.getTaskId());
475         assertNull(form.getAssignee());
476 
477         // 3. claim task as rossini, with role "User manager" granting entitlement to claim forms but not in
478         // groupForWorkflowApproval, designated for approval in workflow definition: fail
479         UserTO rossini = USER_SERVICE.read("1417acbe-cbf6-4277-9372-e75e04f97000");
480         if (!rossini.getRoles().contains("User manager")) {
481             UserUR userUR = new UserUR();
482             userUR.setKey("1417acbe-cbf6-4277-9372-e75e04f97000");
483             userUR.getRoles().add(new StringPatchItem.Builder().
484                     operation(PatchOperation.ADD_REPLACE).value("User manager").build());
485             rossini = updateUser(userUR).getEntity();
486         }
487         assertTrue(rossini.getRoles().contains("User manager"));
488 
489         UserRequestService userService2 = CLIENT_FACTORY.create("rossini", ADMIN_PWD).
490                 getService(UserRequestService.class);
491         try {
492             userService2.claimForm(form.getTaskId());
493             fail("This should not happen");
494         } catch (SyncopeClientException e) {
495             assertEquals(ClientExceptionType.Workflow, e.getType());
496         }
497 
498         // 4. claim task from bellini, with role "User manager" and in groupForWorkflowApproval
499         UserRequestService userService3 = CLIENT_FACTORY.create("bellini", ADMIN_PWD).
500                 getService(UserRequestService.class);
501         assertEquals(1, userService3.listForms(
502                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getTotalCount());
503         form = userService3.claimForm(form.getTaskId());
504         assertNotNull(form);
505         assertNotNull(form.getTaskId());
506         assertNotNull(form.getAssignee());
507 
508         // 5. reject user
509         form.getProperty("approveCreate").get().setValue(Boolean.FALSE.toString());
510         form.getProperty("rejectReason").get().setValue("I don't like him.");
511         userTO = userService3.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
512         }).getEntity();
513         assertNotNull(userTO);
514         assertEquals("rejected", userTO.getStatus());
515 
516         // 6. check that rejected user was not propagated to external resource (SYNCOPE-364)
517         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
518         Exception exception = null;
519         try {
520             jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", Integer.class, userTO.getUsername());
521         } catch (EmptyResultDataAccessException e) {
522             exception = e;
523         }
524         assertNotNull(exception);
525     }
526 
527     @Test
528     public void createWithApproval() {
529         assumeTrue(IS_FLOWABLE_ENABLED);
530 
531         // read forms *before* any operation
532         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
533         int preForms = forms.getTotalCount();
534 
535         UserCR userCR = UserITCase.getUniqueSample("createWithApproval@syncope.apache.org");
536         userCR.getResources().add(RESOURCE_NAME_TESTDB);
537 
538         // User with group 0cbcabd2-4410-4b6b-8f05-a052b451d18f are defined in workflow as subject to approval
539         userCR.getMemberships().add(
540                 new MembershipTO.Builder("0cbcabd2-4410-4b6b-8f05-a052b451d18f").build());
541 
542         // 1. create user and verify that no propagation occurred)
543         ProvisioningResult<UserTO> result = createUser(userCR);
544         assertNotNull(result);
545         UserTO userTO = result.getEntity();
546         assertEquals(1, userTO.getMemberships().size());
547         assertEquals("0cbcabd2-4410-4b6b-8f05-a052b451d18f", userTO.getMemberships().get(0).getGroupKey());
548         assertEquals("createApproval", userTO.getStatus());
549         assertEquals(Set.of(RESOURCE_NAME_TESTDB), userTO.getResources());
550 
551         assertTrue(result.getPropagationStatuses().isEmpty());
552 
553         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
554 
555         Exception exception = null;
556         try {
557             jdbcTemplate.queryForObject("SELECT id FROM test WHERE id=?", Integer.class, userTO.getUsername());
558         } catch (EmptyResultDataAccessException e) {
559             exception = e;
560         }
561         assertNotNull(exception);
562 
563         // 2. request if there is any pending form for user just created
564         forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
565         assertEquals(preForms + 1, forms.getTotalCount());
566 
567         // 3. as admin, update user: still pending approval
568         String updatedUsername = "changed-" + UUID.randomUUID().toString();
569         UserUR userUR = new UserUR();
570         userUR.setKey(userTO.getKey());
571         userUR.setUsername(new StringReplacePatchItem.Builder().value(updatedUsername).build());
572         updateUser(userUR);
573 
574         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
575                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
576         assertNotNull(form);
577         assertNotNull(form.getTaskId());
578         assertNotNull(form.getUserTO());
579         assertEquals(updatedUsername, form.getUserTO().getUsername());
580         assertNull(form.getUserUR());
581         assertNull(form.getAssignee());
582 
583         // 4. claim task (as admin)
584         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
585         assertNotNull(form);
586         assertNotNull(form.getTaskId());
587         assertNotNull(form.getUserTO());
588         assertEquals(updatedUsername, form.getUserTO().getUsername());
589         assertNull(form.getUserUR());
590         assertNotNull(form.getAssignee());
591 
592         // 5. approve user (and verify that propagation occurred)
593         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
594         userTO = USER_REQUEST_SERVICE.submitForm(form).readEntity(new GenericType<ProvisioningResult<UserTO>>() {
595         }).getEntity();
596         assertNotNull(userTO);
597         assertEquals(updatedUsername, userTO.getUsername());
598         assertEquals("active", userTO.getStatus());
599         assertEquals(Set.of(RESOURCE_NAME_TESTDB), userTO.getResources());
600 
601         String username = queryForObject(jdbcTemplate,
602                 MAX_WAIT_SECONDS, "SELECT id FROM test WHERE id=?", String.class, userTO.getUsername());
603         assertEquals(userTO.getUsername(), username);
604 
605         // 6. update user
606         userUR = new UserUR();
607         userUR.setKey(userTO.getKey());
608         userUR.setPassword(new PasswordPatch.Builder().value("anotherPassword123").build());
609 
610         userTO = updateUser(userUR).getEntity();
611         assertNotNull(userTO);
612     }
613 
614     @Test
615     public void updateApproval() {
616         assumeTrue(IS_FLOWABLE_ENABLED);
617 
618         // read forms *before* any operation
619         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(
620                 new UserRequestQuery.Builder().build());
621         int preForms = forms.getTotalCount();
622 
623         UserTO created = createUser(UserITCase.getUniqueSample("updateApproval@syncope.apache.org")).getEntity();
624         assertNotNull(created);
625         assertEquals("/", created.getRealm());
626         assertEquals(0, created.getMemberships().size());
627 
628         UserUR req = new UserUR();
629         req.setKey(created.getKey());
630         req.getMemberships().add(new MembershipUR.Builder("b1f7c12d-ec83-441f-a50e-1691daaedf3b").build());
631 
632         SyncopeClient client = CLIENT_FACTORY.create(created.getUsername(), "password123");
633         Response response = client.getService(UserSelfService.class).update(req);
634         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
635         assertEquals("updateApproval", USER_SERVICE.read(created.getKey()).getStatus());
636 
637         forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
638         assertEquals(preForms + 1, forms.getTotalCount());
639 
640         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
641                 new UserRequestQuery.Builder().user(created.getKey()).build()).getResult().get(0);
642         assertNotNull(form);
643         assertNotNull(form.getTaskId());
644         assertNull(form.getAssignee());
645         assertNotNull(form.getUserTO());
646         assertNotNull(form.getUserUR());
647         assertEquals(req, form.getUserUR());
648 
649         // as admin, update user: still pending approval
650         UserUR adminPatch = new UserUR();
651         adminPatch.setKey(created.getKey());
652         adminPatch.setRealm(new StringReplacePatchItem.Builder().value("/even/two").build());
653 
654         UserTO updated = updateUser(adminPatch).getEntity();
655         assertEquals("updateApproval", updated.getStatus());
656         assertEquals("/even/two", updated.getRealm());
657         assertEquals(0, updated.getMemberships().size());
658 
659         // the patch is not updated in the approval form
660         form = USER_REQUEST_SERVICE.listForms(
661                 new UserRequestQuery.Builder().user(created.getKey()).build()).getResult().get(0);
662         assertEquals(req, form.getUserUR());
663 
664         // approve the user
665         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
666         form.getProperty("approveUpdate").get().setValue(Boolean.TRUE.toString());
667         USER_REQUEST_SERVICE.submitForm(form);
668 
669         // verify that the approved user bears both original and further changes
670         UserTO approved = USER_SERVICE.read(created.getKey());
671         assertEquals("active", approved.getStatus());
672         assertEquals("/even/two", approved.getRealm());
673         assertEquals(1, approved.getMemberships().size());
674         assertTrue(approved.getMembership("b1f7c12d-ec83-441f-a50e-1691daaedf3b").isPresent());
675     }
676 
677     @Test
678     public void availableTasks() {
679         assumeTrue(IS_FLOWABLE_ENABLED);
680 
681         UserTO user = createUser(UserITCase.getUniqueSample("availableTasks@apache.org")).getEntity();
682         assertEquals("active", user.getStatus());
683 
684         List<WorkflowTask> tasks = USER_WORKFLOW_TASK_SERVICE.getAvailableTasks(user.getKey());
685         assertNotNull(tasks);
686         assertTrue(tasks.stream().anyMatch(task -> "update".equals(task.getName())));
687         assertTrue(tasks.stream().anyMatch(task -> "suspend".equals(task.getName())));
688         assertTrue(tasks.stream().anyMatch(task -> "delete".equals(task.getName())));
689     }
690 
691     @Test
692     public void issueSYNCOPE15() {
693         assumeTrue(IS_FLOWABLE_ENABLED);
694 
695         // read forms *before* any operation
696         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
697         int preForms = forms.getTotalCount();
698 
699         UserCR userCR = UserITCase.getUniqueSample("issueSYNCOPE15@syncope.apache.org");
700         userCR.getResources().clear();
701         userCR.getVirAttrs().clear();
702         userCR.getMemberships().clear();
703 
704         // Users with group 0cbcabd2-4410-4b6b-8f05-a052b451d18f are defined in workflow as subject to approval
705         userCR.getMemberships().add(new MembershipTO.Builder("0cbcabd2-4410-4b6b-8f05-a052b451d18f").build());
706 
707         // 1. create user with group 9 (and verify that no propagation occurred)
708         UserTO userTO = createUser(userCR).getEntity();
709         assertNotNull(userTO);
710         assertNotEquals(0L, userTO.getKey());
711         assertNotNull(userTO.getCreationDate());
712         assertNotNull(userTO.getCreator());
713         assertNotNull(userTO.getLastChangeDate());
714         assertNotNull(userTO.getLastModifier());
715         assertEquals(userTO.getCreationDate(), userTO.getLastChangeDate());
716 
717         // 2. request if there is any pending form for user just created
718         forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
719         assertEquals(preForms + 1, forms.getTotalCount());
720 
721         UserRequestForm form = USER_REQUEST_SERVICE.listForms(
722                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
723         assertNotNull(form);
724 
725         // 3. first claim by bellini ....
726         UserRequestService userService3 = CLIENT_FACTORY.create("bellini", ADMIN_PWD).
727                 getService(UserRequestService.class);
728         form = userService3.claimForm(form.getTaskId());
729         assertNotNull(form);
730         assertNotNull(form.getTaskId());
731         assertNotNull(form.getAssignee());
732 
733         // 4. second claim task by admin
734         form = USER_REQUEST_SERVICE.claimForm(form.getTaskId());
735         assertNotNull(form);
736 
737         // 5. approve user
738         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
739 
740         // 6. submit approve
741         USER_REQUEST_SERVICE.submitForm(form);
742         assertEquals(preForms,
743                 USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build()).getTotalCount());
744         assertTrue(USER_REQUEST_SERVICE.listForms(
745                 new UserRequestQuery.Builder().user(userTO.getKey()).build()).getResult().isEmpty());
746 
747         // 7.check that no more forms are still to be processed
748         forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
749         assertEquals(preForms, forms.getTotalCount());
750     }
751 
752     @Test
753     public void issueSYNCOPE373() {
754         UserTO userTO = ADMIN_CLIENT.self().getRight();
755         assertEquals(ADMIN_UNAME, userTO.getUsername());
756     }
757 }