1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
190 if (!authorized) {
191 authorized = findDynRealms(key).stream().anyMatch(authRealms::contains);
192 }
193
194
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 }