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.dao;
20  
21  import java.time.OffsetDateTime;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Objects;
30  import java.util.Set;
31  import java.util.stream.Collectors;
32  import javax.persistence.NoResultException;
33  import javax.persistence.Query;
34  import javax.persistence.TypedQuery;
35  import org.apache.commons.lang3.tuple.Pair;
36  import org.apache.syncope.common.lib.types.AnyEntitlement;
37  import org.apache.syncope.common.lib.types.AnyTypeKind;
38  import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
39  import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
40  import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
41  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
42  import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
43  import org.apache.syncope.core.persistence.api.dao.UserDAO;
44  import org.apache.syncope.core.persistence.api.entity.Any;
45  import org.apache.syncope.core.persistence.api.entity.AnyType;
46  import org.apache.syncope.core.persistence.api.entity.AnyUtils;
47  import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
48  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
49  import org.apache.syncope.core.persistence.api.entity.Membership;
50  import org.apache.syncope.core.persistence.api.entity.Realm;
51  import org.apache.syncope.core.persistence.api.entity.Relationship;
52  import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
53  import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
54  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
55  import org.apache.syncope.core.persistence.api.entity.group.Group;
56  import org.apache.syncope.core.persistence.api.entity.user.URelationship;
57  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
58  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
59  import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
60  import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
61  import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
62  import org.apache.syncope.core.spring.security.AuthContextUtils;
63  import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
64  import org.springframework.transaction.annotation.Propagation;
65  import org.springframework.transaction.annotation.Transactional;
66  
67  public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObjectDAO {
68  
69      protected final UserDAO userDAO;
70  
71      protected final GroupDAO groupDAO;
72  
73      public JPAAnyObjectDAO(
74              final AnyUtilsFactory anyUtilsFactory,
75              final PlainSchemaDAO plainSchemaDAO,
76              final DerSchemaDAO derSchemaDAO,
77              final DynRealmDAO dynRealmDAO,
78              final UserDAO userDAO,
79              final GroupDAO groupDAO) {
80  
81          super(anyUtilsFactory, plainSchemaDAO, derSchemaDAO, dynRealmDAO);
82          this.userDAO = userDAO;
83          this.groupDAO = groupDAO;
84      }
85  
86      @Override
87      protected AnyUtils init() {
88          return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT);
89      }
90  
91      @Transactional(readOnly = true)
92      @Override
93      public AnyObject findByName(final String type, final String name) {
94          TypedQuery<AnyObject> query = entityManager().createQuery(
95                  "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e "
96                  + "WHERE e.type.id = :type AND e.name = :name",
97                  AnyObject.class);
98          query.setParameter("type", type);
99          query.setParameter("name", name);
100 
101         AnyObject result = null;
102         try {
103             result = query.getSingleResult();
104         } catch (NoResultException e) {
105             LOG.debug("No any object found with name {}", name, e);
106         }
107 
108         return result;
109     }
110 
111     @Transactional(readOnly = true)
112     @Override
113     public List<AnyObject> findByName(final String name) {
114         TypedQuery<AnyObject> query = entityManager().createQuery(
115                 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e WHERE e.name = :name",
116                 AnyObject.class);
117         query.setParameter("name", name);
118 
119         return query.getResultList();
120     }
121 
122     @Transactional(readOnly = true)
123     @Override
124     public String findKey(final String type, final String name) {
125         Query query = entityManager().createNativeQuery(
126                 "SELECT id FROM " + JPAAnyObject.TABLE + " WHERE type_id=? AND name=?");
127         query.setParameter(1, type);
128         query.setParameter(2, name);
129 
130         String key = null;
131 
132         for (Object resultKey : query.getResultList()) {
133             key = resultKey instanceof Object[]
134                     ? (String) ((Object[]) resultKey)[0]
135                     : ((String) resultKey);
136         }
137 
138         return key;
139     }
140 
141     @Transactional(readOnly = true)
142     @Override
143     public OffsetDateTime findLastChange(final String key) {
144         return findLastChange(key, JPAAnyObject.TABLE);
145     }
146 
147     @Override
148     public Map<AnyType, Integer> countByType() {
149         Query query = entityManager().createQuery(
150                 "SELECT e.type, COUNT(e) AS countByType FROM " + anyUtils().anyClass().getSimpleName() + " e "
151                 + "GROUP BY e.type ORDER BY countByType DESC");
152         @SuppressWarnings("unchecked")
153         List<Object[]> results = query.getResultList();
154 
155         Map<AnyType, Integer> countByRealm = new LinkedHashMap<>(results.size());
156         results.forEach(result -> countByRealm.put((AnyType) result[0], ((Number) result[1]).intValue()));
157 
158         return Collections.unmodifiableMap(countByRealm);
159     }
160 
161     @Override
162     public Map<String, Integer> countByRealm(final AnyType anyType) {
163         Query query = entityManager().createQuery(
164                 "SELECT e.realm, COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e "
165                 + "WHERE e.type=:type GROUP BY e.realm");
166         query.setParameter("type", anyType);
167 
168         @SuppressWarnings("unchecked")
169         List<Object[]> results = query.getResultList();
170         return results.stream().collect(Collectors.toMap(
171                 result -> ((Realm) result[0]).getFullPath(),
172                 result -> ((Number) result[1]).intValue()));
173     }
174 
175     @Transactional(readOnly = true)
176     @Override
177     public void securityChecks(
178             final Set<String> authRealms,
179             final String key,
180             final String realm,
181             final Collection<String> groups) {
182 
183         // 1. check if AuthContextUtils.getUsername() is owner of at least one group of which anyObject is member
184         boolean authorized = authRealms.stream().
185                 map(authRealm -> RealmUtils.parseGroupOwnerRealm(authRealm).orElse(null)).
186                 filter(Objects::nonNull).
187                 anyMatch(pair -> groups.contains(pair.getRight()));
188 
189         // 2. check if anyObject is in at least one DynRealm for which AuthContextUtils.getUsername() owns entitlement
190         if (!authorized) {
191             authorized = findDynRealms(key).stream().anyMatch(authRealms::contains);
192         }
193 
194         // 3. check if anyObject is in Realm (or descendants) for which AuthContextUtils.getUsername() owns entitlement
195         if (!authorized) {
196             authorized = authRealms.stream().anyMatch(realm::startsWith);
197         }
198 
199         if (!authorized) {
200             throw new DelegatedAdministrationException(realm, AnyTypeKind.ANY_OBJECT.name(), key);
201         }
202     }
203 
204     @Override
205     protected void securityChecks(final AnyObject anyObject) {
206         Set<String> authRealms = AuthContextUtils.getAuthorizations().
207                 getOrDefault(AnyEntitlement.READ.getFor(anyObject.getType().getKey()), Set.of());
208 
209         securityChecks(authRealms, anyObject.getKey(), anyObject.getRealm().getFullPath(), findAllGroupKeys(anyObject));
210     }
211 
212     @Override
213     public AMembership findMembership(final String key) {
214         return entityManager().find(JPAAMembership.class, key);
215     }
216 
217     @Override
218     public List<Relationship<Any<?>, AnyObject>> findAllRelationships(final AnyObject anyObject) {
219         List<Relationship<Any<?>, AnyObject>> result = new ArrayList<>();
220 
221         @SuppressWarnings("unchecked")
222         TypedQuery<Relationship<Any<?>, AnyObject>> aquery =
223                 (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
224                         "SELECT e FROM " + JPAARelationship.class.getSimpleName()
225                         + " e WHERE e.rightEnd=:anyObject OR e.leftEnd=:anyObject");
226         aquery.setParameter("anyObject", anyObject);
227         result.addAll(aquery.getResultList());
228 
229         @SuppressWarnings("unchecked")
230         TypedQuery<Relationship<Any<?>, AnyObject>> uquery =
231                 (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
232                         "SELECT e FROM " + JPAURelationship.class.getSimpleName()
233                         + " e WHERE e.rightEnd=:anyObject");
234         uquery.setParameter("anyObject", anyObject);
235         result.addAll(uquery.getResultList());
236 
237         return result;
238     }
239 
240     @Override
241     public int count() {
242         Query query = entityManager().createQuery(
243                 "SELECT COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e");
244         return ((Number) query.getSingleResult()).intValue();
245     }
246 
247     @Override
248     public List<AnyObject> findAll(final int page, final int itemsPerPage) {
249         TypedQuery<AnyObject> query = entityManager().createQuery(
250                 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e ORDER BY e.id", AnyObject.class);
251         query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
252         query.setMaxResults(itemsPerPage);
253 
254         return query.getResultList();
255     }
256 
257     @Override
258     public List<String> findAllKeys(final int page, final int itemsPerPage) {
259         return findAllKeys(JPAAnyObject.TABLE, page, itemsPerPage);
260     }
261 
262     protected Pair<AnyObject, Pair<Set<String>, Set<String>>> doSave(final AnyObject anyObject) {
263         AnyObject merged = super.save(anyObject);
264 
265         Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
266         dynRealmDAO.refreshDynMemberships(merged);
267 
268         return Pair.of(merged, dynGroupMembs);
269     }
270 
271     @Override
272     public AnyObject save(final AnyObject anyObject) {
273         return doSave(anyObject).getLeft();
274     }
275 
276     @Override
277     public Pair<Set<String>, Set<String>> saveAndGetDynGroupMembs(final AnyObject anyObject) {
278         return doSave(anyObject).getRight();
279     }
280 
281     protected List<ARelationship> findARelationships(final AnyObject anyObject) {
282         TypedQuery<ARelationship> query = entityManager().createQuery(
283                 "SELECT e FROM " + JPAARelationship.class.getSimpleName()
284                 + " e WHERE e.rightEnd=:anyObject", ARelationship.class);
285         query.setParameter("anyObject", anyObject);
286 
287         return query.getResultList();
288     }
289 
290     protected List<URelationship> findURelationships(final AnyObject anyObject) {
291         TypedQuery<URelationship> query = entityManager().createQuery(
292                 "SELECT e FROM " + JPAURelationship.class.getSimpleName()
293                 + " e WHERE e.rightEnd=:anyObject", URelationship.class);
294         query.setParameter("anyObject", anyObject);
295 
296         return query.getResultList();
297     }
298 
299     @Override
300     public void delete(final AnyObject anyObject) {
301         groupDAO.removeDynMemberships(anyObject);
302         dynRealmDAO.removeDynMemberships(anyObject.getKey());
303 
304         findARelationships(anyObject).forEach(relationship -> {
305             relationship.getLeftEnd().getRelationships().remove(relationship);
306             save(relationship.getLeftEnd());
307 
308             entityManager().remove(relationship);
309         });
310         findURelationships(anyObject).forEach(relationship -> {
311             relationship.getLeftEnd().getRelationships().remove(relationship);
312             userDAO.save(relationship.getLeftEnd());
313 
314             entityManager().remove(relationship);
315         });
316 
317         entityManager().remove(anyObject);
318     }
319 
320     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
321     @Override
322     @SuppressWarnings("unchecked")
323     public List<Group> findDynGroups(final String key) {
324         Query query = entityManager().createNativeQuery(
325                 "SELECT group_id FROM " + JPAGroupDAO.ADYNMEMB_TABLE + " WHERE any_id=?");
326         query.setParameter(1, key);
327 
328         List<Group> result = new ArrayList<>();
329         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
330                 ? (String) ((Object[]) resultKey)[0]
331                 : ((String) resultKey)).
332                 forEach(groupKey -> {
333                     Group group = groupDAO.find(groupKey.toString());
334                     if (group == null) {
335                         LOG.error("Could not find group {}, even though returned by the native query", groupKey);
336                     } else if (!result.contains(group)) {
337                         result.add(group);
338                     }
339                 });
340         return result;
341     }
342 
343     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
344     @Override
345     public Collection<Group> findAllGroups(final AnyObject anyObject) {
346         Set<Group> result = new HashSet<>();
347         result.addAll(anyObject.getMemberships().stream().
348                 map(Membership::getRightEnd).collect(Collectors.toSet()));
349         result.addAll(findDynGroups(anyObject.getKey()));
350 
351         return result;
352     }
353 
354     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
355     @Override
356     public Collection<String> findAllGroupKeys(final AnyObject anyObject) {
357         return findAllGroups(anyObject).stream().map(Group::getKey).collect(Collectors.toList());
358     }
359 
360     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
361     @Override
362     public Collection<ExternalResource> findAllResources(final AnyObject anyObject) {
363         Set<ExternalResource> result = new HashSet<>();
364         result.addAll(anyObject.getResources());
365         findAllGroups(anyObject).forEach(group -> result.addAll(group.getResources()));
366 
367         return result;
368     }
369 
370     @Transactional(readOnly = true)
371     @Override
372     public Collection<String> findAllResourceKeys(final String key) {
373         return findAllResources(authFind(key)).stream().map(ExternalResource::getKey).collect(Collectors.toList());
374     }
375 }