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.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import com.fasterxml.jackson.core.JsonProcessingException;
28  import javax.ws.rs.core.Response;
29  import org.apache.syncope.client.lib.SyncopeClient;
30  import org.apache.syncope.common.lib.Attr;
31  import org.apache.syncope.common.lib.SyncopeClientException;
32  import org.apache.syncope.common.lib.request.AnyObjectCR;
33  import org.apache.syncope.common.lib.request.AnyObjectUR;
34  import org.apache.syncope.common.lib.request.AttrPatch;
35  import org.apache.syncope.common.lib.request.GroupCR;
36  import org.apache.syncope.common.lib.request.MembershipUR;
37  import org.apache.syncope.common.lib.request.ResourceDR;
38  import org.apache.syncope.common.lib.request.UserCR;
39  import org.apache.syncope.common.lib.request.UserUR;
40  import org.apache.syncope.common.lib.to.AnyObjectTO;
41  import org.apache.syncope.common.lib.to.ExecTO;
42  import org.apache.syncope.common.lib.to.GroupTO;
43  import org.apache.syncope.common.lib.to.Item;
44  import org.apache.syncope.common.lib.to.MembershipTO;
45  import org.apache.syncope.common.lib.to.PagedResult;
46  import org.apache.syncope.common.lib.to.PullTaskTO;
47  import org.apache.syncope.common.lib.to.ResourceTO;
48  import org.apache.syncope.common.lib.to.TypeExtensionTO;
49  import org.apache.syncope.common.lib.to.UserTO;
50  import org.apache.syncope.common.lib.types.AnyTypeKind;
51  import org.apache.syncope.common.lib.types.ClientExceptionType;
52  import org.apache.syncope.common.lib.types.ExecStatus;
53  import org.apache.syncope.common.lib.types.MappingPurpose;
54  import org.apache.syncope.common.lib.types.PatchOperation;
55  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
56  import org.apache.syncope.common.lib.types.TaskType;
57  import org.apache.syncope.common.rest.api.beans.AnyQuery;
58  import org.apache.syncope.common.rest.api.service.TaskService;
59  import org.apache.syncope.fit.AbstractITCase;
60  import org.junit.jupiter.api.Test;
61  import org.springframework.jdbc.core.JdbcTemplate;
62  
63  public class MembershipITCase extends AbstractITCase {
64  
65      @Test
66      public void misc() throws JsonProcessingException {
67          UserCR userCR = UserITCase.getUniqueSample("memb@apache.org");
68          userCR.setRealm("/even/two");
69          userCR.getPlainAttrs().add(new Attr.Builder("aLong").value("1976").build());
70          userCR.getPlainAttrs().removeIf(attr -> "ctype".equals(attr.getSchema()));
71  
72          // the group 034740a9-fa10-453b-af37-dc7897e98fb1 has USER type extensions for 'csv' and 'other' 
73          // any type classes
74          MembershipTO membership = new MembershipTO.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build();
75          membership.getPlainAttrs().add(new Attr.Builder("aLong").value("1977").build());
76  
77          // 'fullname' is in 'minimal user', so it is not allowed for this membership
78          membership.getPlainAttrs().add(new Attr.Builder("fullname").value("discarded").build());
79  
80          userCR.getMemberships().add(membership);
81  
82          // user creation fails because of fullname
83          try {
84              createUser(userCR);
85              fail("This should not happen");
86          } catch (SyncopeClientException e) {
87              assertEquals(ClientExceptionType.InvalidEntity, e.getType());
88              assertTrue(e.getMessage().contains("InvalidPlainAttr: fullname not allowed for membership of group"));
89          }
90  
91          // remove fullname and try again
92          membership.getPlainAttrs().remove(membership.getPlainAttr("fullname").get());
93          UserTO userTO = null;
94          try {
95              userTO = createUser(userCR).getEntity();
96  
97              // 1. verify that 'aLong' is correctly populated for user
98              assertEquals(1, userTO.getPlainAttr("aLong").get().getValues().size());
99              assertEquals("1976", userTO.getPlainAttr("aLong").get().getValues().get(0));
100 
101             // 2. verify that 'aLong' is correctly populated for user's membership
102             assertEquals(1, userCR.getMemberships().size());
103             membership = userTO.getMembership("034740a9-fa10-453b-af37-dc7897e98fb1").get();
104             assertNotNull(membership);
105             assertEquals(1, membership.getPlainAttr("aLong").get().getValues().size());
106             assertEquals("1977", membership.getPlainAttr("aLong").get().getValues().get(0));
107 
108             // 3. verify that derived attrbutes from 'csv' and 'other' are also populated for user's membership
109             assertFalse(membership.getDerAttr("csvuserid").get().getValues().isEmpty());
110             assertFalse(membership.getDerAttr("noschema").get().getValues().isEmpty());
111 
112             // update user - change some values and add new membership attribute
113             UserUR userUR = new UserUR();
114             userUR.setKey(userTO.getKey());
115 
116             userUR.getPlainAttrs().
117                     add(new AttrPatch.Builder(new Attr.Builder("aLong").value("1977").build()).build());
118 
119             MembershipUR membershipPatch = new MembershipUR.Builder(membership.getGroupKey()).build();
120             membershipPatch.getPlainAttrs().add(new Attr.Builder("aLong").value("1976").build());
121             membershipPatch.getPlainAttrs().add(new Attr.Builder("ctype").value("membership type").build());
122             userUR.getMemberships().add(membershipPatch);
123 
124             userTO = updateUser(userUR).getEntity();
125 
126             // 4. verify that 'aLong' is correctly populated for user
127             assertEquals(1, userTO.getPlainAttr("aLong").get().getValues().size());
128             assertEquals("1977", userTO.getPlainAttr("aLong").get().getValues().get(0));
129             assertFalse(userTO.getPlainAttr("ctype").isPresent());
130 
131             // 5. verify that 'aLong' is correctly populated for user's membership
132             assertEquals(1, userCR.getMemberships().size());
133             membership = userTO.getMembership("034740a9-fa10-453b-af37-dc7897e98fb1").get();
134             assertNotNull(membership);
135             assertEquals(1, membership.getPlainAttr("aLong").get().getValues().size());
136             assertEquals("1976", membership.getPlainAttr("aLong").get().getValues().get(0));
137 
138             // 6. verify that 'ctype' is correctly populated for user's membership
139             assertEquals("membership type", membership.getPlainAttr("ctype").get().getValues().get(0));
140 
141             // finally remove membership
142             userUR = new UserUR();
143             userUR.setKey(userTO.getKey());
144 
145             membershipPatch = new MembershipUR.Builder(membership.getGroupKey()).
146                     operation(PatchOperation.DELETE).build();
147             userUR.getMemberships().add(membershipPatch);
148 
149             userTO = updateUser(userUR).getEntity();
150 
151             assertTrue(userTO.getMemberships().isEmpty());
152         } finally {
153             if (userTO != null) {
154                 USER_SERVICE.delete(userTO.getKey());
155             }
156         }
157     }
158 
159     @Test
160     public void deleteUserWithMembership() {
161         UserCR userCR = UserITCase.getUniqueSample("memb@apache.org");
162         userCR.setRealm("/even/two");
163         userCR.getPlainAttrs().add(new Attr.Builder("aLong").value("1976").build());
164 
165         MembershipTO membership = new MembershipTO.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build();
166         membership.getPlainAttrs().add(new Attr.Builder("aLong").value("1977").build());
167         userCR.getMemberships().add(membership);
168 
169         UserTO user = createUser(userCR).getEntity();
170         assertNotNull(user.getKey());
171 
172         USER_SERVICE.delete(user.getKey());
173     }
174 
175     @Test
176     public void onGroupDelete() {
177         // pre: create group with type extension
178         TypeExtensionTO typeExtension = new TypeExtensionTO();
179         typeExtension.setAnyType(AnyTypeKind.USER.name());
180         typeExtension.getAuxClasses().add("csv");
181         typeExtension.getAuxClasses().add("other");
182 
183         GroupCR groupCR = GroupITCase.getBasicSample("typeExt");
184         groupCR.getTypeExtensions().add(typeExtension);
185         GroupTO groupTO = createGroup(groupCR).getEntity();
186         assertNotNull(groupTO);
187 
188         // pre: create user with membership to such group
189         UserCR userCR = UserITCase.getUniqueSample("typeExt@apache.org");
190 
191         MembershipTO membership = new MembershipTO.Builder(groupTO.getKey()).build();
192         membership.getPlainAttrs().add(new Attr.Builder("aLong").value("1454").build());
193         userCR.getMemberships().add(membership);
194 
195         UserTO user = createUser(userCR).getEntity();
196 
197         // verify that 'aLong' is correctly populated for user's membership
198         assertEquals(1, user.getMemberships().size());
199         membership = user.getMembership(groupTO.getKey()).get();
200         assertNotNull(membership);
201         assertEquals(1, membership.getPlainAttr("aLong").get().getValues().size());
202         assertEquals("1454", membership.getPlainAttr("aLong").get().getValues().get(0));
203 
204         // verify that derived attrbutes from 'csv' and 'other' are also populated for user's membership
205         assertFalse(membership.getDerAttr("csvuserid").get().getValues().isEmpty());
206         assertFalse(membership.getDerAttr("noschema").get().getValues().isEmpty());
207 
208         // now remove the group -> all related memberships should have been removed as well
209         GROUP_SERVICE.delete(groupTO.getKey());
210 
211         // re-read user and verify that no memberships are available any more
212         user = USER_SERVICE.read(user.getKey());
213         assertTrue(user.getMemberships().isEmpty());
214     }
215 
216     @Test
217     public void pull() {
218         // 0. create ad-hoc resource, with adequate mapping
219         ResourceTO newResource = RESOURCE_SERVICE.read(RESOURCE_NAME_DBPULL);
220         newResource.setKey(getUUIDString());
221 
222         Item item = newResource.getProvision("USER").get().getMapping().getItems().stream().
223                 filter(object -> "firstname".equals(object.getIntAttrName())).findFirst().get();
224         assertNotNull(item);
225         assertEquals("ID", item.getExtAttrName());
226         item.setIntAttrName("memberships[additional].aLong");
227         item.setPurpose(MappingPurpose.BOTH);
228 
229         item = newResource.getProvision("USER").get().getMapping().getItems().stream().
230                 filter(object -> "fullname".equals(object.getIntAttrName())).findFirst().get();
231         item.setPurpose(MappingPurpose.PULL);
232 
233         PullTaskTO newTask = null;
234         try {
235             newResource = createResource(newResource);
236             assertNotNull(newResource);
237 
238             // 1. create user with new resource assigned
239             UserCR userCR = UserITCase.getUniqueSample("memb@apache.org");
240             userCR.setRealm("/even/two");
241             UserTO user;
242             userCR.getPlainAttrs().removeIf(attr -> "ctype".equals(attr.getSchema()));
243             userCR.getResources().clear();
244             userCR.getResources().add(newResource.getKey());
245 
246             MembershipTO membership = new MembershipTO.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build();
247             membership.getPlainAttrs().add(new Attr.Builder("aLong").value("5432").build());
248             userCR.getMemberships().add(membership);
249 
250             user = createUser(userCR).getEntity();
251             assertNotNull(user);
252 
253             // 2. verify that user was found on resource
254             JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
255             String idOnResource = queryForObject(
256                     jdbcTemplate, MAX_WAIT_SECONDS, "SELECT id FROM testpull WHERE id=?", String.class, "5432");
257             assertEquals("5432", idOnResource);
258 
259             // 3. unlink user from resource, then remove it
260             ResourceDR req = new ResourceDR();
261             req.setKey(user.getKey());
262             req.setAction(ResourceDeassociationAction.UNLINK);
263             req.getResources().add(newResource.getKey());
264             assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(req)));
265 
266             USER_SERVICE.delete(user.getKey());
267 
268             // 4. create pull task and execute
269             newTask = TASK_SERVICE.read(TaskType.PULL, "7c2242f4-14af-4ab5-af31-cdae23783655", true);
270             newTask.setName(getUUIDString());
271             newTask.setResource(newResource.getKey());
272             newTask.setDestinationRealm("/even/two");
273 
274             Response response = TASK_SERVICE.create(TaskType.PULL, newTask);
275             newTask = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
276             assertNotNull(newTask);
277 
278             ExecTO execution = AbstractTaskITCase.execProvisioningTask(
279                     TASK_SERVICE, TaskType.PULL, newTask.getKey(), MAX_WAIT_SECONDS, false);
280             assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
281 
282             // 5. verify that pulled user has
283             if (IS_EXT_SEARCH_ENABLED) {
284                 try {
285                     Thread.sleep(2000);
286                 } catch (InterruptedException ex) {
287                     // ignore
288                 }
289             }
290             PagedResult<UserTO> users = USER_SERVICE.search(new AnyQuery.Builder().
291                     realm("/").
292                     fiql(SyncopeClient.getUserSearchConditionBuilder().
293                             is("username").equalTo(user.getUsername()).query()).build());
294             assertEquals(1, users.getTotalCount());
295             assertEquals(1, users.getResult().get(0).getMemberships().size());
296             assertEquals("5432", users.getResult().get(0).getMemberships().get(0).
297                     getPlainAttr("aLong").get().getValues().get(0));
298         } catch (Exception e) {
299             LOG.error("Unexpected error", e);
300             fail(e::getMessage);
301         } finally {
302             if (newTask != null && !"83f7e85d-9774-43fe-adba-ccd856312994".equals(newTask.getKey())) {
303                 TASK_SERVICE.delete(TaskType.PULL, newTask.getKey());
304             }
305             RESOURCE_SERVICE.delete(newResource.getKey());
306         }
307     }
308 
309     @Test
310     public void createDoubleMembership() {
311         AnyObjectCR anyObjectCR = AnyObjectITCase.getSample("createDoubleMembership");
312         anyObjectCR.setRealm("/even/two");
313         anyObjectCR.getMemberships().add(new MembershipTO.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build());
314         anyObjectCR.getMemberships().add(new MembershipTO.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build());
315 
316         try {
317             createAnyObject(anyObjectCR);
318             fail("This should not happen");
319         } catch (SyncopeClientException e) {
320             assertEquals(ClientExceptionType.InvalidMembership, e.getType());
321         }
322     }
323 
324     @Test
325     public void updateDoubleMembership() {
326         AnyObjectCR anyObjecCR = AnyObjectITCase.getSample("update");
327         anyObjecCR.setRealm("/even/two");
328         AnyObjectTO anyObjecTO = createAnyObject(anyObjecCR).getEntity();
329         assertNotNull(anyObjecTO.getKey());
330 
331         AnyObjectUR req = new AnyObjectUR();
332         req.setKey(anyObjecTO.getKey());
333         req.getMemberships().add(new MembershipUR.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build());
334         MembershipUR mp = new MembershipUR.Builder("034740a9-fa10-453b-af37-dc7897e98fb1").build();
335         mp.getPlainAttrs().add(attr("any", "useless"));
336         req.getMemberships().add(mp);
337 
338         try {
339             updateAnyObject(req).getEntity();
340             fail("This should not happen");
341         } catch (SyncopeClientException e) {
342             assertEquals(ClientExceptionType.InvalidMembership, e.getType());
343         }
344     }
345 }