1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.userdirectory;
23
24 import static org.opencastproject.db.Queries.namedQuery;
25
26 import org.opencastproject.db.DBSession;
27 import org.opencastproject.db.DBSessionFactory;
28 import org.opencastproject.security.api.Group;
29 import org.opencastproject.security.api.Role;
30 import org.opencastproject.security.api.RoleProvider;
31 import org.opencastproject.security.api.SecurityService;
32 import org.opencastproject.security.api.UnauthorizedException;
33 import org.opencastproject.security.api.User;
34 import org.opencastproject.security.api.UserProvider;
35 import org.opencastproject.security.impl.jpa.JpaOrganization;
36 import org.opencastproject.security.impl.jpa.JpaRole;
37 import org.opencastproject.security.impl.jpa.JpaUserReference;
38 import org.opencastproject.userdirectory.api.AAIRoleProvider;
39 import org.opencastproject.userdirectory.api.UserReferenceProvider;
40 import org.opencastproject.userdirectory.utils.UserDirectoryUtils;
41 import org.opencastproject.util.NotFoundException;
42 import org.opencastproject.util.function.ThrowingConsumer;
43
44 import com.google.common.cache.CacheBuilder;
45 import com.google.common.cache.CacheLoader;
46 import com.google.common.cache.LoadingCache;
47
48 import org.apache.commons.lang3.tuple.Pair;
49 import org.osgi.service.component.ComponentContext;
50 import org.osgi.service.component.annotations.Activate;
51 import org.osgi.service.component.annotations.Component;
52 import org.osgi.service.component.annotations.Reference;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import java.util.ArrayList;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Optional;
62 import java.util.Set;
63 import java.util.concurrent.TimeUnit;
64 import java.util.function.Function;
65 import java.util.stream.Collectors;
66
67 import javax.persistence.EntityManager;
68 import javax.persistence.EntityManagerFactory;
69 import javax.persistence.TypedQuery;
70
71
72
73
74 @Component(
75 property = {
76 "service.description=Provides a user reference directory"
77 },
78 immediate = true,
79 service = { UserProvider.class, RoleProvider.class, UserReferenceProvider.class, JpaUserReferenceProvider.class }
80 )
81 public class JpaUserReferenceProvider implements UserReferenceProvider, UserProvider, RoleProvider {
82
83
84 private static final Logger logger = LoggerFactory.getLogger(JpaUserReferenceProvider.class);
85
86 public static final String PROVIDER_NAME = "matterhorn-reference";
87
88
89 public static final String USERNAME = "username";
90
91
92 public static final String ROLES = "roles";
93
94
95 public static final String ENCODING = "UTF-8";
96
97
98 protected SecurityService securityService = null;
99
100
101 protected JpaGroupRoleProvider groupRoleProvider;
102
103
104 protected AAIRoleProvider roleProvider;
105
106
107 private static final String DELIMITER = ";==;";
108
109
110 private LoadingCache<String, Object> cache = null;
111
112
113 protected final Object nullToken = new Object();
114
115
116 protected EntityManagerFactory emf = null;
117
118 protected DBSessionFactory dbSessionFactory;
119
120 protected DBSession db;
121
122
123 @Reference(target = "(osgi.unit.name=org.opencastproject.common)")
124 void setEntityManagerFactory(EntityManagerFactory emf) {
125 this.emf = emf;
126 }
127
128 @Reference
129 public void setDBSessionFactory(DBSessionFactory dbSessionFactory) {
130 this.dbSessionFactory = dbSessionFactory;
131 }
132
133
134
135
136
137 @Reference
138 public void setSecurityService(SecurityService securityService) {
139 this.securityService = securityService;
140 }
141
142
143
144
145
146 @Reference
147 public void setGroupRoleProvider(JpaGroupRoleProvider groupRoleProvider) {
148 this.groupRoleProvider = groupRoleProvider;
149 }
150
151
152
153
154
155
156
157 @Activate
158 public void activate(ComponentContext cc) {
159 logger.debug("activate");
160
161
162 cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<>() {
163 @Override
164 public Object load(String id) {
165 String[] key = id.split(DELIMITER);
166 logger.trace("Loading user '{}':'{}' from reference database", key[0], key[1]);
167 User user = loadUserFromDB(key[0], key[1]);
168 return user == null ? nullToken : user;
169 }
170 });
171
172
173 db = dbSessionFactory.createSession(emf);
174 }
175
176 @Override
177 public String getName() {
178 return PROVIDER_NAME;
179 }
180
181
182
183
184
185
186 @Override
187 public String toString() {
188 return getClass().getName();
189 }
190
191
192
193
194
195
196 @Override
197 public List<Role> getRolesForUser(String userName) {
198 if (roleProvider != null) {
199 return roleProvider.getRolesForUser(userName);
200 }
201
202 ArrayList<Role> roles = new ArrayList<>();
203 User user = loadUser(userName);
204 if (user != null) {
205 roles.addAll(user.getRoles());
206 }
207 return roles;
208 }
209
210
211
212
213
214
215 @Override
216 public Iterator<User> findUsers(String query, int offset, int limit) {
217 if (query == null) {
218 throw new IllegalArgumentException("Query must be set");
219 }
220 String orgId = securityService.getOrganization().getId();
221 return db.exec(findUserReferencesByQueryQuery(orgId, query, limit, offset)).stream()
222 .map(ref -> ref.toUser(PROVIDER_NAME))
223 .collect(Collectors.toList())
224 .iterator();
225 }
226
227 @Override
228 public Iterator<User> findUsers(Collection<String> userNames) {
229 String orgId = securityService.getOrganization().getId();
230 return db.exec(findUsersByUserNameQuery(orgId, userNames)).stream()
231 .map(ref -> ref.toUser(PROVIDER_NAME))
232 .collect(Collectors.toList())
233 .iterator();
234 }
235
236
237
238
239
240
241 @Override
242 public Iterator<Role> findRoles(String query, Role.Target target, int offset, int limit) {
243 if (roleProvider == null) {
244 return Collections.emptyIterator();
245 }
246 return roleProvider.findRoles(query, target, offset, limit);
247 }
248
249
250
251
252
253
254 @Override
255 public User loadUser(String userName) {
256 String orgId = securityService.getOrganization().getId();
257 return loadUserFromCache(userName, orgId);
258 }
259
260
261
262
263
264
265
266
267
268
269 private User loadUserFromDB(String userName, String organization) {
270 return db.exec(findUserReferenceQuery(userName, organization))
271 .map(ref -> ref.toUser(PROVIDER_NAME))
272 .orElse(null);
273 }
274
275
276
277
278
279
280
281
282
283
284 private User loadUserFromCache(String userName, String organization) {
285 Object user = cache.getUnchecked(userName.concat(DELIMITER).concat(organization));
286 if (user == nullToken) {
287 return null;
288 } else {
289 return (User) user;
290 }
291 }
292
293 @Override
294 public Iterator<User> getUsers() {
295 String orgId = securityService.getOrganization().getId();
296 return db.exec(findUserReferences(orgId, 0, 0)).stream()
297 .map(ref -> ref.toUser(PROVIDER_NAME))
298 .collect(Collectors.toList())
299 .iterator();
300 }
301
302
303
304
305
306
307 public Iterator<Role> getRoles() {
308 if (roleProvider == null) {
309 return Collections.emptyIterator();
310 }
311 return roleProvider.getRoles();
312 }
313
314
315
316
317
318
319 @Override
320 public String getOrganization() {
321 return ALL_ORGANIZATIONS;
322 }
323
324
325
326
327 public void addUserReference(JpaUserReference user, String mechanism) {
328 db.execTx(em -> {
329
330 Set<JpaRole> roles = UserDirectoryPersistenceUtil.saveRolesQuery(user.getRoles()).apply(em);
331 JpaOrganization organization = UserDirectoryPersistenceUtil.saveOrganizationQuery(
332 (JpaOrganization) user.getOrganization()).apply(em);
333 JpaUserReference userReference = new JpaUserReference(user.getUsername(), user.getName(), user.getEmail(),
334 mechanism, user.getLastLogin(), organization, roles);
335
336
337 Optional<JpaUserReference> foundUserRef = findUserReferenceQuery(user.getUsername(),
338 user.getOrganization().getId()).apply(em);
339 if (foundUserRef.isPresent()) {
340 throw new IllegalStateException("User '" + user.getUsername() + "' already exists");
341 }
342 em.persist(userReference);
343 });
344
345
346 cache.put(user.getUsername() + DELIMITER + user.getOrganization().getId(), user.toUser(PROVIDER_NAME));
347 updateGroupMembership(user);
348 }
349
350
351
352
353 public void updateUserReference(JpaUserReference user) {
354 db.execTx(em -> {
355 Optional<JpaUserReference> foundUserRef = findUserReferenceQuery(user.getUsername(),
356 user.getOrganization().getId()).apply(em);
357 if (foundUserRef.isEmpty()) {
358 throw new IllegalStateException("User '" + user.getUsername() + "' does not exist");
359 }
360 foundUserRef.get().setName(user.getName());
361 foundUserRef.get().setEmail(user.getEmail());
362 foundUserRef.get().setLastLogin(user.getLastLogin());
363 foundUserRef.get().setRoles(UserDirectoryPersistenceUtil.saveRolesQuery(user.getRoles()).apply(em));
364 em.merge(foundUserRef.get());
365 });
366
367
368 cache.put(user.getUsername() + DELIMITER + user.getOrganization().getId(), user.toUser(PROVIDER_NAME));
369 updateGroupMembership(user);
370 }
371
372
373
374
375
376
377
378 private void updateGroupMembership(JpaUserReference user) {
379 logger.debug("updateGroupMembership({}, roles={})", user.getUsername(), user.getRoles().size());
380 List<String> internalGroupRoles = new ArrayList<>();
381
382 for (Role role : user.getRoles()) {
383 if (Role.Type.GROUP.equals(role.getType())
384 || (Role.Type.INTERNAL.equals(role.getType()) && role.getName().startsWith(Group.ROLE_PREFIX))) {
385 internalGroupRoles.add(role.getName());
386 }
387 }
388
389 groupRoleProvider.updateGroupMembershipFromRoles(
390 user.getUsername(),
391 user.getOrganization().getId(),
392 internalGroupRoles
393 );
394 }
395
396
397
398
399
400
401
402
403
404
405 public JpaUserReference findUserReference(String userName, String organizationId) {
406 return db.exec(findUserReferenceQuery(userName, organizationId))
407 .orElse(null);
408 }
409
410
411
412
413
414
415
416
417
418
419 private Function<EntityManager, Optional<JpaUserReference>> findUserReferenceQuery(String userName,
420 String organizationId) {
421 return namedQuery.findOpt(
422 "UserReference.findByUsername",
423 JpaUserReference.class,
424 Pair.of("u", userName),
425 Pair.of("org", organizationId)
426 );
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443 private Function<EntityManager, List<JpaUserReference>> findUserReferencesByQueryQuery(String orgId, String query,
444 int limit, int offset) {
445 return em -> {
446 TypedQuery<JpaUserReference> q = em.createNamedQuery("UserReference.findByQuery", JpaUserReference.class)
447 .setMaxResults(limit)
448 .setFirstResult(offset);
449 q.setParameter("query", query.toUpperCase());
450 q.setParameter("org", orgId);
451 return q.getResultList();
452 };
453 }
454
455
456
457
458
459
460
461 private Function<EntityManager, List<JpaUserReference>> findUsersByUserNameQuery(String orgId,
462 Collection<String> names) {
463 return em -> {
464 if (names.isEmpty()) {
465 return Collections.emptyList();
466 }
467 TypedQuery<JpaUserReference> q = em.createNamedQuery("UserReference.findAllByUserNames", JpaUserReference.class);
468 q.setParameter("org", orgId);
469 q.setParameter("names", names);
470 return q.getResultList();
471 };
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485 private Function<EntityManager, List<JpaUserReference>> findUserReferences(String orgId, int limit, int offset) {
486 return em -> {
487 TypedQuery<JpaUserReference> q = em.createNamedQuery("UserReference.findAll", JpaUserReference.class)
488 .setMaxResults(limit)
489 .setFirstResult(offset);
490 q.setParameter("org", orgId);
491 return q.getResultList();
492 };
493 }
494
495 @Override
496 public long countUsers() {
497 String orgId = securityService.getOrganization().getId();
498 return db.exec(namedQuery.find(
499 "UserReference.countAll",
500 Number.class,
501 Pair.of("org", orgId)
502 )).longValue();
503 }
504
505 @Override
506 public void invalidate(String userName) {
507 String orgId = securityService.getOrganization().getId();
508 cache.invalidate(userName.concat(DELIMITER).concat(orgId));
509 }
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524 public void deleteUser(String username, String orgId) throws NotFoundException, UnauthorizedException, Exception {
525 User user = loadUser(username);
526 if (user != null && !UserDirectoryUtils.isCurrentUserAuthorizedHandleRoles(securityService, user.getRoles())) {
527 throw new UnauthorizedException("The user is not allowed to delete an admin user");
528 }
529
530
531 groupRoleProvider.removeMemberFromAllGroups(username, orgId);
532
533
534 db.execTxChecked(deleteUserQuery(username, orgId));
535
536 cache.invalidate(username + DELIMITER + orgId);
537 }
538
539 private ThrowingConsumer<EntityManager, NotFoundException> deleteUserQuery(String username, String orgId) {
540 return em -> {
541 Optional<JpaUserReference> user = findUserReferenceQuery(username, orgId).apply(em);
542 if (user.isEmpty()) {
543 throw new NotFoundException("User with name " + username + " does not exist");
544 }
545 em.remove(em.merge(user.get()));
546 };
547 }
548
549 public void setRoleProvider(RoleProvider roleProvider) {
550 this.roleProvider = (AAIRoleProvider) roleProvider;
551 }
552 }