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.core.persistence.jpa.outer;
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.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Set;
33  import java.util.stream.Collectors;
34  import javax.persistence.Query;
35  import org.apache.syncope.common.lib.SyncopeConstants;
36  import org.apache.syncope.common.lib.types.AnyTypeKind;
37  import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
38  import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
39  import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
40  import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
41  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
42  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
43  import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
44  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
45  import org.apache.syncope.core.persistence.api.dao.UserDAO;
46  import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
47  import org.apache.syncope.core.persistence.api.entity.anyobject.APlainAttr;
48  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
49  import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
50  import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrValue;
51  import org.apache.syncope.core.persistence.api.entity.group.Group;
52  import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
53  import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
54  import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
55  import org.apache.syncope.core.persistence.api.entity.user.User;
56  import org.apache.syncope.core.persistence.jpa.AbstractTest;
57  import org.apache.syncope.core.persistence.jpa.dao.JPAGroupDAO;
58  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership;
59  import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
60  import org.junit.jupiter.api.Test;
61  import org.springframework.beans.factory.annotation.Autowired;
62  import org.springframework.transaction.annotation.Transactional;
63  
64  @Transactional("Master")
65  public class GroupTest extends AbstractTest {
66  
67      @Autowired
68      private AnyTypeDAO anyTypeDAO;
69  
70      @Autowired
71      private AnyObjectDAO anyObjectDAO;
72  
73      @Autowired
74      private UserDAO userDAO;
75  
76      @Autowired
77      private GroupDAO groupDAO;
78  
79      @Autowired
80      private RealmDAO realmDAO;
81  
82      @Autowired
83      private PlainSchemaDAO plainSchemaDAO;
84  
85      @Autowired
86      private AnyTypeClassDAO anyTypeClassDAO;
87  
88      @Autowired
89      private PlainAttrValidationManager validator;
90  
91      @Test
92      public void saveWithTwoOwners() {
93          assertThrows(InvalidEntityException.class, () -> {
94              Group root = groupDAO.findByName("root");
95              assertNotNull(root);
96  
97              User user = userDAO.findByUsername("rossini");
98              assertNotNull(user);
99  
100             Group group = entityFactory.newEntity(Group.class);
101             group.setRealm(realmDAO.getRoot());
102             group.setName("error");
103             group.setUserOwner(user);
104             group.setGroupOwner(root);
105 
106             groupDAO.save(group);
107         });
108     }
109 
110     @Test
111     public void findByOwner() {
112         Group group = groupDAO.find("ebf97068-aa4b-4a85-9f01-680e8c4cf227");
113         assertNotNull(group);
114 
115         User user = userDAO.find("823074dc-d280-436d-a7dd-07399fae48ec");
116         assertNotNull(user);
117 
118         assertEquals(user, group.getUserOwner());
119 
120         List<Group> ownedGroups = groupDAO.findOwnedByUser(user.getKey());
121         assertFalse(ownedGroups.isEmpty());
122         assertEquals(1, ownedGroups.size());
123         assertTrue(ownedGroups.contains(group));
124     }
125 
126     @Test
127     public void create() {
128         Group group = entityFactory.newEntity(Group.class);
129         group.setRealm(realmDAO.getRoot());
130         group.setName("new");
131 
132         TypeExtension typeExt = entityFactory.newEntity(TypeExtension.class);
133         typeExt.setAnyType(anyTypeDAO.findUser());
134         typeExt.add(anyTypeClassDAO.find("csv"));
135         typeExt.add(anyTypeClassDAO.find("other"));
136 
137         group.add(typeExt);
138         typeExt.setGroup(group);
139 
140         groupDAO.save(group);
141 
142         entityManager().flush();
143 
144         group = groupDAO.findByName("new");
145         assertNotNull(group);
146         assertEquals(1, group.getTypeExtensions().size());
147         assertEquals(2, group.getTypeExtension(anyTypeDAO.findUser()).get().getAuxClasses().size());
148     }
149 
150     @Test
151     public void createWithInternationalCharacters() {
152         Group group = entityFactory.newEntity(Group.class);
153         group.setName("räksmörgås");
154         group.setRealm(realmDAO.findByFullPath(SyncopeConstants.ROOT_REALM));
155 
156         groupDAO.save(group);
157         entityManager().flush();
158     }
159 
160     @Test
161     public void delete() {
162         Collection<Group> groups = userDAO.findAllGroups(userDAO.findByUsername("verdi"));
163         assertTrue(groups.stream().anyMatch(g -> "b1f7c12d-ec83-441f-a50e-1691daaedf3b".equals(g.getKey())));
164         int before = userDAO.findAllGroups(userDAO.findByUsername("verdi")).size();
165 
166         groupDAO.delete("b1f7c12d-ec83-441f-a50e-1691daaedf3b");
167 
168         entityManager().flush();
169 
170         assertNull(groupDAO.find("b1f7c12d-ec83-441f-a50e-1691daaedf3b"));
171         assertEquals(before - 1, userDAO.findAllGroups(userDAO.findByUsername("verdi")).size());
172         assertNull(findPlainAttr("f82fc61f-8e74-4a4b-9f9e-b8a41f38aad9", GPlainAttr.class));
173         assertNull(findPlainAttrValue("49f35879-2510-4f11-a901-24152f753538", GPlainAttrValue.class));
174         assertNotNull(plainSchemaDAO.find("icon"));
175     }
176 
177     /**
178      * Static copy of {@link org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO} method with same signature:
179      * required for avoiding creating of a new transaction - good for general use case but bad for the way how
180      * this test class is architected.
181      */
182     @SuppressWarnings("unchecked")
183     public List<Group> findDynGroups(final User user) {
184         Query query = entityManager().createNativeQuery(
185                 "SELECT group_id FROM " + JPAGroupDAO.UDYNMEMB_TABLE + " WHERE any_id=?");
186         query.setParameter(1, user.getKey());
187 
188         List<Group> result = new ArrayList<>();
189         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
190                 ? (String) ((Object[]) resultKey)[0]
191                 : ((String) resultKey)).
192                 forEach(actualKey -> {
193                     Group group = groupDAO.find(actualKey.toString());
194                     if (group != null && !result.contains(group)) {
195                         result.add(group);
196                     }
197                 });
198         return result;
199     }
200 
201     @Test
202     public void udynMembership() {
203         // 0. create user matching the condition below
204         User user = entityFactory.newEntity(User.class);
205         user.setUsername("username");
206         user.setRealm(realmDAO.findByFullPath("/even/two"));
207         user.add(anyTypeClassDAO.find("other"));
208 
209         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
210         attr.setOwner(user);
211         attr.setSchema(plainSchemaDAO.find("cool"));
212         attr.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
213         user.add(attr);
214 
215         user = userDAO.save(user);
216         String newUserKey = user.getKey();
217         assertNotNull(newUserKey);
218 
219         // 1. create group with dynamic membership
220         Group group = entityFactory.newEntity(Group.class);
221         group.setRealm(realmDAO.getRoot());
222         group.setName("new");
223 
224         UDynGroupMembership dynMembership = entityFactory.newEntity(UDynGroupMembership.class);
225         dynMembership.setFIQLCond("cool==true");
226         dynMembership.setGroup(group);
227 
228         group.setUDynMembership(dynMembership);
229 
230         Group actual = groupDAO.saveAndRefreshDynMemberships(group);
231         assertNotNull(actual);
232 
233         entityManager().flush();
234 
235         // 2. verify that dynamic membership is there
236         actual = groupDAO.find(actual.getKey());
237         assertNotNull(actual);
238         assertNotNull(actual.getUDynMembership());
239         assertNotNull(actual.getUDynMembership().getKey());
240         assertEquals(actual, actual.getUDynMembership().getGroup());
241 
242         // 3. verify that expected users have the created group dynamically assigned
243         List<String> members = groupDAO.findUDynMembers(actual);
244         assertEquals(2, members.size());
245         assertEquals(Set.of("c9b2dec2-00a7-4855-97c0-d854842b4b24", newUserKey), new HashSet<>(members));
246 
247         user = userDAO.findByUsername("bellini");
248         assertNotNull(user);
249         Collection<Group> dynGroupMemberships = findDynGroups(user);
250         assertEquals(1, dynGroupMemberships.size());
251         assertTrue(dynGroupMemberships.contains(actual.getUDynMembership().getGroup()));
252 
253         // 4. delete the new user and verify that dynamic membership was updated
254         userDAO.delete(newUserKey);
255 
256         entityManager().flush();
257 
258         actual = groupDAO.find(actual.getKey());
259         members = groupDAO.findUDynMembers(actual);
260         assertEquals(1, members.size());
261         assertEquals("c9b2dec2-00a7-4855-97c0-d854842b4b24", members.get(0));
262 
263         // 5. delete group and verify that dynamic membership was also removed
264         String dynMembershipKey = actual.getUDynMembership().getKey();
265 
266         groupDAO.delete(actual);
267 
268         entityManager().flush();
269 
270         assertNull(entityManager().find(JPAUDynGroupMembership.class, dynMembershipKey));
271 
272         dynGroupMemberships = findDynGroups(user);
273         assertTrue(dynGroupMemberships.isEmpty());
274     }
275 
276     /**
277      * Static copy of {@link org.apache.syncope.core.persistence.jpa.dao.JPAAnyObjectDAO} method with same signature:
278      * required for avoiding creating of a new transaction - good for general use case but bad for the way how
279      * this test class is architected.
280      */
281     @SuppressWarnings("unchecked")
282     public List<Group> findDynGroups(final AnyObject anyObject) {
283         Query query = entityManager().createNativeQuery(
284                 "SELECT group_id FROM " + JPAGroupDAO.ADYNMEMB_TABLE + " WHERE any_id=?");
285         query.setParameter(1, anyObject.getKey());
286 
287         List<Group> result = new ArrayList<>();
288         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
289                 ? (String) ((Object[]) resultKey)[0]
290                 : ((String) resultKey)).
291                 forEach(actualKey -> {
292                     Group group = groupDAO.find(actualKey.toString());
293                     if (group != null && !result.contains(group)) {
294                         result.add(group);
295                     }
296                 });
297         return result;
298     }
299 
300     @Test
301     public void adynMembership() {
302         // 0. create any object matching the condition below
303         AnyObject anyObject = entityFactory.newEntity(AnyObject.class);
304         anyObject.setName("name");
305         anyObject.setType(anyTypeDAO.find("PRINTER"));
306         anyObject.setRealm(realmDAO.findByFullPath("/even/two"));
307 
308         APlainAttr attr = entityFactory.newEntity(APlainAttr.class);
309         attr.setOwner(anyObject);
310         attr.setSchema(plainSchemaDAO.find("model"));
311         attr.add(validator, "Canon MFC8030", anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
312         anyObject.add(attr);
313 
314         anyObject = anyObjectDAO.save(anyObject);
315         String newAnyObjectKey = anyObject.getKey();
316         assertNotNull(newAnyObjectKey);
317 
318         // 1. create group with dynamic membership
319         Group group = entityFactory.newEntity(Group.class);
320         group.setRealm(realmDAO.getRoot());
321         group.setName("new");
322 
323         ADynGroupMembership dynMembership = entityFactory.newEntity(ADynGroupMembership.class);
324         dynMembership.setAnyType(anyTypeDAO.find("PRINTER"));
325         dynMembership.setFIQLCond("model==Canon MFC8030");
326         dynMembership.setGroup(group);
327 
328         group.add(dynMembership);
329 
330         Group actual = groupDAO.saveAndRefreshDynMemberships(group);
331         assertNotNull(actual);
332 
333         entityManager().flush();
334 
335         // 2. verify that dynamic membership is there
336         actual = groupDAO.find(actual.getKey());
337         assertNotNull(actual);
338         assertNotNull(actual.getADynMembership(anyTypeDAO.find("PRINTER")).get());
339         assertNotNull(actual.getADynMembership(anyTypeDAO.find("PRINTER")).get().getKey());
340         assertEquals(actual, actual.getADynMembership(anyTypeDAO.find("PRINTER")).get().getGroup());
341 
342         // 3. verify that expected any objects have the created group dynamically assigned
343         List<String> members = groupDAO.findADynMembers(actual).stream().filter(object
344                 -> "PRINTER".equals(anyObjectDAO.find(object).getType().getKey())).collect(Collectors.toList());
345         assertEquals(2, members.size());
346         assertEquals(
347                 Set.of("fc6dbc3a-6c07-4965-8781-921e7401a4a5", newAnyObjectKey),
348                 new HashSet<>(members));
349 
350         anyObject = anyObjectDAO.find("fc6dbc3a-6c07-4965-8781-921e7401a4a5");
351         assertNotNull(anyObject);
352         Collection<Group> dynGroupMemberships = findDynGroups(anyObject);
353         assertEquals(1, dynGroupMemberships.size());
354         assertTrue(dynGroupMemberships.contains(actual.getADynMembership(anyTypeDAO.find("PRINTER")).get().getGroup()));
355 
356         // 4. delete the new any object and verify that dynamic membership was updated
357         anyObjectDAO.delete(newAnyObjectKey);
358 
359         entityManager().flush();
360 
361         actual = groupDAO.find(actual.getKey());
362         members = groupDAO.findADynMembers(actual).stream().filter(object
363                 -> "PRINTER".equals(anyObjectDAO.find(object).getType().getKey())).collect(Collectors.toList());
364         assertEquals(1, members.size());
365         assertEquals("fc6dbc3a-6c07-4965-8781-921e7401a4a5", members.get(0));
366 
367         // 5. delete group and verify that dynamic membership was also removed
368         String dynMembershipKey = actual.getADynMembership(anyTypeDAO.find("PRINTER")).get().getKey();
369 
370         groupDAO.delete(actual);
371 
372         entityManager().flush();
373 
374         assertNull(entityManager().find(JPAADynGroupMembership.class, dynMembershipKey));
375 
376         dynGroupMemberships = findDynGroups(anyObject);
377         assertTrue(dynGroupMemberships.isEmpty());
378     }
379 
380     @Test
381     public void issueSYNCOPE1512() {
382         Group group = groupDAO.findByName("root");
383         assertNotNull(group);
384 
385         // non unique
386         GPlainAttr title = entityFactory.newEntity(GPlainAttr.class);
387         title.setOwner(group);
388         title.setSchema(plainSchemaDAO.find("title"));
389         title.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
390         group.add(title);
391 
392         // unique
393         GPlainAttr originalName = entityFactory.newEntity(GPlainAttr.class);
394         originalName.setOwner(group);
395         originalName.setSchema(plainSchemaDAO.find("originalName"));
396         originalName.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
397         group.add(originalName);
398 
399         groupDAO.save(group);
400 
401         entityManager().flush();
402 
403         group = groupDAO.find(group.getKey());
404         assertEquals("syncope's group", group.getPlainAttr("title").get().getValuesAsStrings().get(0));
405         assertEquals("syncope's group", group.getPlainAttr("originalName").get().getValuesAsStrings().get(0));
406     }
407 }