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.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 import javax.persistence.NoResultException;
32 import javax.persistence.Query;
33 import javax.persistence.TypedQuery;
34 import org.apache.commons.lang3.tuple.Pair;
35 import org.apache.syncope.common.lib.types.AnyTypeKind;
36 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
37 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
38 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
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.FIQLQueryDAO;
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.RoleDAO;
45 import org.apache.syncope.core.persistence.api.dao.UserDAO;
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.Privilege;
51 import org.apache.syncope.core.persistence.api.entity.Realm;
52 import org.apache.syncope.core.persistence.api.entity.Role;
53 import org.apache.syncope.core.persistence.api.entity.group.Group;
54 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
55 import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
56 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
57 import org.apache.syncope.core.persistence.api.entity.user.User;
58 import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
59 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
60 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
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.apache.syncope.core.spring.security.SecurityProperties;
65 import org.springframework.transaction.annotation.Propagation;
66 import org.springframework.transaction.annotation.Transactional;
67
68 public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
69
70 protected final RoleDAO roleDAO;
71
72 protected final AccessTokenDAO accessTokenDAO;
73
74 protected final GroupDAO groupDAO;
75
76 protected final DelegationDAO delegationDAO;
77
78 protected final FIQLQueryDAO fiqlQueryDAO;
79
80 protected final SecurityProperties securityProperties;
81
82 public JPAUserDAO(
83 final AnyUtilsFactory anyUtilsFactory,
84 final PlainSchemaDAO plainSchemaDAO,
85 final DerSchemaDAO derSchemaDAO,
86 final DynRealmDAO dynRealmDAO,
87 final RoleDAO roleDAO,
88 final AccessTokenDAO accessTokenDAO,
89 final GroupDAO groupDAO,
90 final DelegationDAO delegationDAO,
91 final FIQLQueryDAO fiqlQueryDAO,
92 final SecurityProperties securityProperties) {
93
94 super(anyUtilsFactory, plainSchemaDAO, derSchemaDAO, dynRealmDAO);
95 this.roleDAO = roleDAO;
96 this.accessTokenDAO = accessTokenDAO;
97 this.groupDAO = groupDAO;
98 this.delegationDAO = delegationDAO;
99 this.fiqlQueryDAO = fiqlQueryDAO;
100 this.securityProperties = securityProperties;
101 }
102
103 @Override
104 protected AnyUtils init() {
105 return anyUtilsFactory.getInstance(AnyTypeKind.USER);
106 }
107
108 @Transactional(readOnly = true)
109 @Override
110 public String findKey(final String username) {
111 Query query = entityManager().createNativeQuery("SELECT id FROM " + JPAUser.TABLE + " WHERE username=?");
112 query.setParameter(1, username);
113
114 String key = null;
115
116 for (Object resultKey : query.getResultList()) {
117 key = resultKey instanceof Object[]
118 ? (String) ((Object[]) resultKey)[0]
119 : ((String) resultKey);
120 }
121
122 return key;
123 }
124
125 @Transactional(readOnly = true)
126 @Override
127 public OffsetDateTime findLastChange(final String key) {
128 return findLastChange(key, JPAUser.TABLE);
129 }
130
131 @Transactional(readOnly = true)
132 @Override
133 public Optional<String> findUsername(final String key) {
134 Query query = entityManager().createNativeQuery("SELECT username FROM " + JPAUser.TABLE + " WHERE id=?");
135 query.setParameter(1, key);
136
137 String username = null;
138 for (Object resultKey : query.getResultList()) {
139 username = resultKey instanceof Object[]
140 ? (String) ((Object[]) resultKey)[0]
141 : ((String) resultKey);
142 }
143
144 return Optional.ofNullable(username);
145 }
146
147 @Override
148 public int count() {
149 Query query = entityManager().createQuery(
150 "SELECT COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e");
151 return ((Number) query.getSingleResult()).intValue();
152 }
153
154 @Override
155 public Map<String, Integer> countByRealm() {
156 Query query = entityManager().createQuery(
157 "SELECT e.realm, COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e GROUP BY e.realm");
158
159 @SuppressWarnings("unchecked")
160 List<Object[]> results = query.getResultList();
161 return results.stream().collect(Collectors.toMap(
162 result -> ((Realm) result[0]).getFullPath(),
163 result -> ((Number) result[1]).intValue()));
164 }
165
166 @Override
167 public Map<String, Integer> countByStatus() {
168 Query query = entityManager().createQuery(
169 "SELECT e.status, COUNT(e) FROM " + anyUtils().anyClass().getSimpleName() + " e GROUP BY e.status");
170
171 @SuppressWarnings("unchecked")
172 List<Object[]> results = query.getResultList();
173 return results.stream().collect(Collectors.toMap(
174 result -> (String) result[0],
175 result -> ((Number) result[1]).intValue()));
176 }
177
178 @Transactional(readOnly = true)
179 @Override
180 public void securityChecks(
181 final Set<String> authRealms,
182 final String key,
183 final String realm,
184 final Collection<String> groups) {
185
186
187 boolean authorized = authRealms.stream().
188 map(authRealm -> RealmUtils.parseGroupOwnerRealm(authRealm).orElse(null)).
189 filter(Objects::nonNull).
190 anyMatch(pair -> groups.contains(pair.getRight()));
191
192
193 if (!authorized) {
194 authorized = findDynRealms(key).stream().anyMatch(authRealms::contains);
195 }
196
197
198 if (!authorized) {
199 authorized = authRealms.stream().anyMatch(realm::startsWith);
200 }
201
202 if (!authorized) {
203 throw new DelegatedAdministrationException(realm, AnyTypeKind.USER.name(), key);
204 }
205 }
206
207 @Override
208 protected void securityChecks(final User user) {
209
210
211 if (!AuthContextUtils.getUsername().equals(securityProperties.getAnonymousUser())
212 && !AuthContextUtils.getUsername().equals(user.getUsername())) {
213
214 Set<String> authRealms = AuthContextUtils.getAuthorizations().
215 getOrDefault(IdRepoEntitlement.USER_READ, Set.of());
216
217 securityChecks(authRealms, user.getKey(), user.getRealm().getFullPath(), findAllGroupKeys(user));
218 }
219 }
220
221 @Override
222 public User findByUsername(final String username) {
223 TypedQuery<User> query = entityManager().createQuery(
224 "SELECT e FROM " + anyUtils().anyClass().getSimpleName()
225 + " e WHERE e.username = :username", User.class);
226 query.setParameter("username", username);
227
228 User result = null;
229 try {
230 result = query.getSingleResult();
231 } catch (NoResultException e) {
232 LOG.debug("No user found with username {}", username, e);
233 }
234
235 return result;
236 }
237
238 @Override
239 public User findByToken(final String token) {
240 TypedQuery<User> query = entityManager().createQuery(
241 "SELECT e FROM " + anyUtils().anyClass().getSimpleName()
242 + " e WHERE e.token LIKE :token", User.class);
243 query.setParameter("token", token);
244
245 User result = null;
246 try {
247 result = query.getSingleResult();
248 } catch (NoResultException e) {
249 LOG.debug("No user found with token {}", token, e);
250 }
251
252 return result;
253 }
254
255 @Override
256 public List<User> findBySecurityQuestion(final SecurityQuestion securityQuestion) {
257 TypedQuery<User> query = entityManager().createQuery(
258 "SELECT e FROM " + anyUtils().anyClass().getSimpleName()
259 + " e WHERE e.securityQuestion = :securityQuestion", User.class);
260 query.setParameter("securityQuestion", securityQuestion);
261
262 return query.getResultList();
263 }
264
265 @Override
266 public UMembership findMembership(final String key) {
267 return entityManager().find(JPAUMembership.class, key);
268 }
269
270 @Override
271 public List<User> findAll(final int page, final int itemsPerPage) {
272 TypedQuery<User> query = entityManager().createQuery(
273 "SELECT e FROM " + anyUtils().anyClass().getSimpleName() + " e ORDER BY e.id", User.class);
274 query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
275 query.setMaxResults(itemsPerPage);
276
277 return query.getResultList();
278 }
279
280 @Override
281 public List<String> findAllKeys(final int page, final int itemsPerPage) {
282 return findAllKeys(JPAUser.TABLE, page, itemsPerPage);
283 }
284
285 protected Pair<User, Pair<Set<String>, Set<String>>> doSave(final User user) {
286 User merged = super.save(user);
287 roleDAO.refreshDynMemberships(merged);
288 Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
289 dynRealmDAO.refreshDynMemberships(merged);
290
291 return Pair.of(merged, dynGroupMembs);
292 }
293
294 @Override
295 public User save(final User user) {
296 return doSave(user).getLeft();
297 }
298
299 @Override
300 public Pair<Set<String>, Set<String>> saveAndGetDynGroupMembs(final User user) {
301 return doSave(user).getRight();
302 }
303
304 @Override
305 public void delete(final User user) {
306 roleDAO.removeDynMemberships(user.getKey());
307 groupDAO.removeDynMemberships(user);
308 dynRealmDAO.removeDynMemberships(user.getKey());
309
310 delegationDAO.findByDelegating(user).forEach(delegationDAO::delete);
311 delegationDAO.findByDelegated(user).forEach(delegationDAO::delete);
312
313 fiqlQueryDAO.findByOwner(user, null).forEach(fiqlQueryDAO::delete);
314
315 Optional.ofNullable(accessTokenDAO.findByOwner(user.getUsername())).ifPresent(accessTokenDAO::delete);
316
317 entityManager().remove(user);
318 }
319
320 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
321 @Override
322 public Collection<Role> findAllRoles(final User user) {
323 Set<Role> result = new HashSet<>();
324 result.addAll(user.getRoles());
325 result.addAll(findDynRoles(user.getKey()));
326
327 return result;
328 }
329
330 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
331 @Override
332 @SuppressWarnings("unchecked")
333 public List<Role> findDynRoles(final String key) {
334 Query query = entityManager().createNativeQuery(
335 "SELECT role_id FROM " + JPARoleDAO.DYNMEMB_TABLE + " WHERE any_id=?");
336 query.setParameter(1, key);
337
338 List<Role> result = new ArrayList<>();
339 query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
340 ? (String) ((Object[]) resultKey)[0]
341 : ((String) resultKey)).
342 forEach(roleKey -> {
343 Role role = roleDAO.find(roleKey.toString());
344 if (role == null) {
345 LOG.error("Could not find role {}, even though returned by the native query", roleKey);
346 } else if (!result.contains(role)) {
347 result.add(role);
348 }
349 });
350 return result;
351 }
352
353 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
354 @Override
355 @SuppressWarnings("unchecked")
356 public List<Group> findDynGroups(final String key) {
357 Query query = entityManager().createNativeQuery(
358 "SELECT group_id FROM " + JPAGroupDAO.UDYNMEMB_TABLE + " WHERE any_id=?");
359 query.setParameter(1, key);
360
361 List<Group> result = new ArrayList<>();
362 query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
363 ? (String) ((Object[]) resultKey)[0]
364 : ((String) resultKey)).
365 forEach(groupKey -> {
366 Group group = groupDAO.find(groupKey.toString());
367 if (group == null) {
368 LOG.error("Could not find group {}, even though returned by the native query", groupKey);
369 } else if (!result.contains(group)) {
370 result.add(group);
371 }
372 });
373 return result;
374 }
375
376 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
377 @Override
378 public Collection<Group> findAllGroups(final User user) {
379 Set<Group> result = new HashSet<>();
380 result.addAll(user.getMemberships().stream().
381 map(Membership::getRightEnd).collect(Collectors.toSet()));
382 result.addAll(findDynGroups(user.getKey()));
383
384 return result;
385 }
386
387 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
388 @Override
389 public Collection<String> findAllGroupKeys(final User user) {
390 return findAllGroups(user).stream().map(Group::getKey).collect(Collectors.toList());
391 }
392
393 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
394 @Override
395 public Collection<String> findAllGroupNames(final User user) {
396 return findAllGroups(user).stream().map(Group::getName).collect(Collectors.toList());
397 }
398
399 @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
400 @Override
401 public Collection<ExternalResource> findAllResources(final User user) {
402 Set<ExternalResource> result = new HashSet<>();
403 result.addAll(user.getResources());
404 findAllGroups(user).forEach(group -> result.addAll(group.getResources()));
405
406 return result;
407 }
408
409 @Transactional(readOnly = true)
410 @Override
411 public Collection<String> findAllResourceKeys(final String key) {
412 return findAllResources(authFind(key)).stream().map(ExternalResource::getKey).collect(Collectors.toList());
413 }
414
415 @Transactional(readOnly = true)
416 @Override
417 public boolean linkedAccountExists(final String userKey, final String connObjectKeyValue) {
418 Query query = entityManager().createNativeQuery(
419 "SELECT id FROM " + JPALinkedAccount.TABLE + " WHERE owner_id=? AND connObjectKeyValue=?");
420 query.setParameter(1, userKey);
421 query.setParameter(2, connObjectKeyValue);
422
423 return !query.getResultList().isEmpty();
424 }
425
426 @Override
427 public Optional<? extends LinkedAccount> findLinkedAccount(
428 final ExternalResource resource, final String connObjectKeyValue) {
429
430 TypedQuery<LinkedAccount> query = entityManager().createQuery(
431 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
432 + "WHERE e.resource=:resource AND e.connObjectKeyValue=:connObjectKeyValue", LinkedAccount.class);
433 query.setParameter("resource", resource);
434 query.setParameter("connObjectKeyValue", connObjectKeyValue);
435
436 List<LinkedAccount> result = query.getResultList();
437 return result.isEmpty() ? Optional.empty() : Optional.of(result.get(0));
438 }
439
440 @Transactional(readOnly = true)
441 @Override
442 public List<LinkedAccount> findLinkedAccounts(final String userKey) {
443 TypedQuery<LinkedAccount> query = entityManager().createQuery(
444 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
445 + "WHERE e.owner.id=:userKey", LinkedAccount.class);
446 query.setParameter("userKey", userKey);
447 return query.getResultList();
448 }
449
450 @Override
451 public List<LinkedAccount> findLinkedAccountsByPrivilege(final Privilege privilege) {
452 TypedQuery<LinkedAccount> query = entityManager().createQuery(
453 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
454 + "WHERE :privilege MEMBER OF e.privileges", LinkedAccount.class);
455 query.setParameter("privilege", privilege);
456 return query.getResultList();
457 }
458
459 @Override
460 public List<LinkedAccount> findLinkedAccountsByResource(final ExternalResource resource) {
461 TypedQuery<LinkedAccount> query = entityManager().createQuery(
462 "SELECT e FROM " + JPALinkedAccount.class.getSimpleName() + " e "
463 + "WHERE e.resource=:resource", LinkedAccount.class);
464 query.setParameter("resource", resource);
465
466 return query.getResultList();
467 }
468 }