1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.security.aai;
22
23 import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;
24
25 import org.opencastproject.security.api.JaxbOrganization;
26 import org.opencastproject.security.api.JaxbRole;
27 import org.opencastproject.security.api.Organization;
28 import org.opencastproject.security.api.Role;
29 import org.opencastproject.security.api.RoleProvider;
30 import org.opencastproject.security.api.SecurityService;
31 import org.opencastproject.security.api.UserProvider;
32 import org.opencastproject.security.impl.jpa.JpaOrganization;
33 import org.opencastproject.security.impl.jpa.JpaRole;
34 import org.opencastproject.security.impl.jpa.JpaUserReference;
35 import org.opencastproject.security.shibboleth.ShibbolethLoginHandler;
36 import org.opencastproject.userdirectory.api.UserReferenceProvider;
37
38 import org.apache.commons.lang3.BooleanUtils;
39 import org.apache.commons.lang3.StringUtils;
40 import org.osgi.framework.BundleContext;
41 import org.osgi.framework.FrameworkUtil;
42 import org.osgi.service.cm.ConfigurationException;
43 import org.osgi.service.cm.ManagedService;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46 import org.springframework.security.core.userdetails.UsernameNotFoundException;
47
48 import java.nio.charset.StandardCharsets;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.Date;
52 import java.util.Dictionary;
53 import java.util.HashSet;
54 import java.util.Hashtable;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Set;
58 import java.util.regex.Pattern;
59
60 import javax.servlet.http.HttpServletRequest;
61
62
63
64
65
66
67
68 public class ConfigurableLoginHandler implements ShibbolethLoginHandler, RoleProvider, ManagedService {
69
70
71
72 private static final String CFG_AAI_ENABLED_KEY = "enabled";
73
74
75 private static final boolean CFG_AAI_ENABLED_DEFAULT = false;
76
77
78
79 private static final String CFG_BOOTSTRAP_USER_ID_KEY = "bootstrap.user.id";
80
81
82
83
84
85 private static final String CFG_HEADER_GIVEN_NAME_KEY = "header.given_name";
86
87
88
89 private static final String CFG_HEADER_SURNAME_KEY = "header.surname";
90
91
92
93 private static final String CFG_HEADER_MAIL_KEY = "header.mail";
94
95
96 private static final String CFG_HEADER_HOME_ORGANIZATION_KEY = "header.home_organization";
97
98
99
100 private static final String CFG_HEADER_AFFILIATION_KEY = "header.affiliation";
101
102
103
104
105
106
107
108 private static final String CFG_ROLE_USER_PREFIX_KEY = "role.user.prefix";
109
110
111 private static final String CFG_ROLE_USER_PREFIX_DEFAULT = "ROLE_AAI_USER_";
112
113
114
115
116
117 private static final String CFG_ROLE_ORGANIZATION_PREFIX_KEY = "role.organization.prefix";
118
119
120 private static final String CFG_ROLE_ORGANIZATION_PREFIX_DEFAULT = "ROLE_AAI_ORG_";
121
122
123 private static final String CFG_ROLE_ORGANIZATION_SUFFIX_KEY = "role.organization.suffix";
124
125
126 private static final String CFG_ROLE_ORGANIZATION_SUFFIX_DEFAULT = "_MEMBER";
127
128
129
130 private static final String CFG_ROLE_FEDERATION_KEY = "role.federation";
131
132
133 private static final String CFG_ROLE_FEDERATION_DEFAULT = "ROLE_AAI_USER";
134
135
136
137
138
139 private static final String CFG_ROLE_AFFILIATION_PREFIX_KEY = "role.affiliation.prefix";
140
141
142 private static final String CFG_ROLE_AFFILIATION_PREFIX_DEFAULT = "ROLE_AAI_USER_AFFILIATION_";
143
144
145 private static final Logger logger = LoggerFactory.getLogger(ConfigurableLoginHandler.class);
146
147
148 private UserReferenceProvider userReferenceProvider = null;
149
150
151 private SecurityService securityService = null;
152
153
154 private boolean enabled = CFG_AAI_ENABLED_DEFAULT;
155
156
157 private String bootstrapUserId = null;
158
159
160 private String headerGivenName = null;
161
162
163 private String headerSurname = null;
164
165
166 private String headerMail = null;
167
168
169 private String headerHomeOrganization = null;
170
171
172 private String headerAffiliation = null;
173
174
175 private String roleFederationMember = CFG_ROLE_FEDERATION_DEFAULT;
176
177
178 private String roleUserPrefix = CFG_ROLE_USER_PREFIX_DEFAULT;
179
180
181 private String roleOrganizationPrefix = CFG_ROLE_ORGANIZATION_PREFIX_DEFAULT;
182
183
184 private String roleOrganizationSuffix = CFG_ROLE_ORGANIZATION_SUFFIX_DEFAULT;
185
186
187 private String roleAffiliationPrefix = CFG_ROLE_AFFILIATION_PREFIX_DEFAULT;
188
189
190
191
192
193
194
195
196
197
198
199
200
201 public ConfigurableLoginHandler() {
202 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
203 registerAsManagedService(bundleContext);
204 }
205
206 protected ConfigurableLoginHandler(BundleContext bundleContext) {
207 registerAsManagedService(bundleContext);
208 }
209
210 private void registerAsManagedService(BundleContext bundleContext) {
211 Dictionary<String, String> properties = new Hashtable<String, String>();
212 properties.put("service.pid", this.getClass().getName());
213 bundleContext.registerService(ManagedService.class.getName(), this, properties);
214 }
215
216 @Override
217 public void updated(Dictionary properties) throws ConfigurationException {
218 if (properties == null) {
219 return;
220 }
221
222 String cfgEnabled = StringUtils.trimToNull((String) properties.get(CFG_AAI_ENABLED_KEY));
223 if (cfgEnabled != null) {
224 enabled = BooleanUtils.toBoolean(cfgEnabled);
225 }
226
227 if (enabled) {
228 logger.info("AAI login handler is enabled.");
229 } else {
230 logger.info("AAI login handler is disabled.");
231 return;
232 }
233
234 String cfgBootstrapUserId = StringUtils.trimToNull((String) properties.get(CFG_BOOTSTRAP_USER_ID_KEY));
235 if (cfgBootstrapUserId != null) {
236 bootstrapUserId = cfgBootstrapUserId;
237 logger.warn("AAI User ID '{}' is configured as AAI boostrap user. You want to disable this after bootstrapping.",
238 bootstrapUserId);
239 } else {
240 bootstrapUserId = null;
241 }
242
243
244
245 String cfgGivenName = StringUtils.trimToNull((String) properties.get(CFG_HEADER_GIVEN_NAME_KEY));
246 if (cfgGivenName != null) {
247 headerGivenName = cfgGivenName;
248 logger.info("Header '{}' set to '{}'", CFG_HEADER_GIVEN_NAME_KEY, headerGivenName);
249 } else {
250 logger.error("Header '{}' is not configured ", CFG_HEADER_GIVEN_NAME_KEY);
251 }
252
253 String cfgSurname = StringUtils.trimToNull((String) properties.get(CFG_HEADER_SURNAME_KEY));
254 if (cfgSurname != null) {
255 headerSurname = cfgSurname;
256 logger.info("Header '{}' set to '{}'", CFG_HEADER_SURNAME_KEY, headerSurname);
257 } else {
258 logger.error("Header '{}' is not configured ", CFG_HEADER_SURNAME_KEY);
259 }
260
261 String cfgMail = StringUtils.trimToNull((String) properties.get(CFG_HEADER_MAIL_KEY));
262 if (cfgMail != null) {
263 headerMail = cfgMail;
264 logger.info("Header '{}' set to '{}'", CFG_HEADER_MAIL_KEY, headerMail);
265 } else {
266 logger.error("Header '{}' is not configured ", CFG_HEADER_MAIL_KEY);
267 }
268
269 String cfgHomeOrganization = StringUtils.trimToNull((String) properties.get(CFG_HEADER_HOME_ORGANIZATION_KEY));
270 if (cfgHomeOrganization != null) {
271 headerHomeOrganization = cfgHomeOrganization;
272 logger.info("Header '{}' set to '{}'", CFG_HEADER_HOME_ORGANIZATION_KEY, headerHomeOrganization);
273 } else {
274 logger.warn("Optional header '{}' is not configured ", CFG_HEADER_HOME_ORGANIZATION_KEY);
275 }
276
277 String cfgAffiliation = StringUtils.trimToNull((String) properties.get(CFG_HEADER_AFFILIATION_KEY));
278 if (cfgAffiliation != null) {
279 headerAffiliation = cfgAffiliation;
280 logger.info("Header '{}' set to '{}'", CFG_HEADER_AFFILIATION_KEY, headerAffiliation);
281 } else {
282 logger.warn("Optional header '{}' is not configured ", CFG_HEADER_AFFILIATION_KEY);
283 }
284
285
286
287 String cfgRoleFederationMember = StringUtils.trimToNull((String) properties.get(CFG_ROLE_FEDERATION_KEY));
288 if (cfgRoleFederationMember != null) {
289 roleFederationMember = cfgRoleFederationMember;
290 logger.info("AAI federation membership role '{}' set to '{}'", CFG_ROLE_FEDERATION_KEY,
291 roleFederationMember);
292 } else {
293 roleFederationMember = CFG_ROLE_FEDERATION_DEFAULT;
294 logger.info("AAI federation membership role '{}' is not configured, using default '{}'",
295 CFG_ROLE_FEDERATION_KEY, roleFederationMember);
296 }
297
298 String cfgRoleUserPrefix = StringUtils.trimToNull((String) properties.get(CFG_ROLE_USER_PREFIX_KEY));
299 if (cfgRoleUserPrefix != null) {
300 roleUserPrefix = cfgRoleUserPrefix;
301 logger.info("AAI user role prefix '{}' set to '{}'", CFG_ROLE_USER_PREFIX_KEY, roleUserPrefix);
302 } else {
303 roleUserPrefix = CFG_ROLE_USER_PREFIX_DEFAULT;
304 logger.info("AAI user role prefix '{}' is not configured, using default '{}'", CFG_ROLE_USER_PREFIX_KEY,
305 roleUserPrefix);
306 }
307
308 String cfgRoleOrganizationPrefix = StringUtils.trimToNull((String) properties.get(
309 CFG_ROLE_ORGANIZATION_PREFIX_KEY));
310 if (cfgRoleOrganizationPrefix != null) {
311 roleOrganizationPrefix = cfgRoleOrganizationPrefix;
312 logger.info("AAI organization membership role prefix '{}' set to '{}'", CFG_ROLE_ORGANIZATION_PREFIX_KEY,
313 cfgRoleOrganizationPrefix);
314 } else {
315 roleOrganizationPrefix = CFG_ROLE_ORGANIZATION_PREFIX_DEFAULT;
316 logger.info("AAI organization membership role prefix '{}' is not configured, using default '{}'",
317 CFG_ROLE_ORGANIZATION_PREFIX_KEY, roleOrganizationPrefix);
318 }
319
320 String cfgRoleOrganizationSuffix = StringUtils.trimToNull((String) properties.get(
321 CFG_ROLE_ORGANIZATION_SUFFIX_KEY));
322 if (cfgRoleOrganizationSuffix != null) {
323 roleOrganizationSuffix = cfgRoleOrganizationSuffix;
324 logger.info("AAI organization membership role suffix '{}' set to '{}'", CFG_ROLE_ORGANIZATION_SUFFIX_KEY,
325 cfgRoleOrganizationSuffix);
326 } else {
327 roleOrganizationSuffix = CFG_ROLE_ORGANIZATION_SUFFIX_DEFAULT;
328 logger.info("AAI organization membership role suffix '{}' is not configured, using default '{}'",
329 CFG_ROLE_ORGANIZATION_SUFFIX_KEY, roleOrganizationSuffix);
330 }
331
332 String cfgRoleAffiliationPrefix = StringUtils.trimToNull((String) properties.get(
333 CFG_ROLE_AFFILIATION_PREFIX_KEY));
334 if (cfgRoleAffiliationPrefix != null) {
335 roleAffiliationPrefix = cfgRoleAffiliationPrefix;
336 logger.info("AAI affiliation role prefix '{}' set to '{}'", CFG_ROLE_AFFILIATION_PREFIX_KEY,
337 cfgRoleAffiliationPrefix);
338 } else {
339 roleAffiliationPrefix = CFG_ROLE_AFFILIATION_PREFIX_DEFAULT;
340 logger.info("AAI affiliation role prefix '{}' is not configured, using default '{}'",
341 CFG_ROLE_AFFILIATION_PREFIX_KEY, roleAffiliationPrefix);
342 }
343 }
344
345
346
347
348
349
350
351
352
353 @Override
354 public void newUserLogin(String id, HttpServletRequest request) {
355 String name = extractName(request);
356 String email = extractEmail(request);
357 Date loginDate = new Date();
358 JpaOrganization organization = fromOrganization(securityService.getOrganization());
359
360
361 Set<JpaRole> roles = extractRoles(id, request);
362
363
364 JpaUserReference userReference = new JpaUserReference(id, name, email, MECH_SHIBBOLETH, loginDate, organization,
365 roles);
366
367 logger.debug("Shibboleth user '{}' logged in for the first time", id);
368 userReferenceProvider.addUserReference(userReference, MECH_SHIBBOLETH);
369 }
370
371
372
373
374
375
376
377
378
379 @Override
380 public void existingUserLogin(String id, HttpServletRequest request) {
381 Organization organization = securityService.getOrganization();
382
383
384 JpaUserReference userReference = userReferenceProvider.findUserReference(id, organization.getId());
385 if (userReference == null) {
386 throw new UsernameNotFoundException("User reference '" + id + "' was not found");
387 }
388
389
390 userReference.setName(extractName(request));
391 userReference.setEmail(extractEmail(request));
392 userReference.setLastLogin(new Date());
393 Set<JpaRole> roles = extractRoles(id, request);
394 userReference.setRoles(roles);
395
396 logger.debug("Shibboleth user '{}' logged in", id);
397 userReferenceProvider.updateUserReference(userReference);
398 }
399
400
401
402
403
404
405
406 public void setSecurityService(SecurityService securityService) {
407 this.securityService = securityService;
408 }
409
410
411
412
413
414
415
416 public void setUserReferenceProvider(UserReferenceProvider userReferenceProvider) {
417 this.userReferenceProvider = userReferenceProvider;
418 }
419
420
421
422
423
424
425
426
427 private String extractName(HttpServletRequest request) {
428 String givenName = StringUtils.isBlank(request.getHeader(headerGivenName)) ? ""
429 : new String(request.getHeader(headerGivenName).getBytes(StandardCharsets.ISO_8859_1),
430 StandardCharsets.UTF_8);
431 String surname = StringUtils.isBlank(request.getHeader(headerSurname)) ? ""
432 : new String(request.getHeader(headerSurname).getBytes(StandardCharsets.ISO_8859_1),
433 StandardCharsets.UTF_8);
434
435 return StringUtils.join(new String[] { givenName, surname }, " ");
436 }
437
438
439
440
441
442
443
444
445 private String extractEmail(HttpServletRequest request) {
446 return request.getHeader(headerMail);
447 }
448
449
450
451
452
453
454
455
456 private Set<JpaRole> extractRoles(String id, HttpServletRequest request) {
457 JpaOrganization organization = fromOrganization(securityService.getOrganization());
458 Set<JpaRole> roles = new HashSet<JpaRole>();
459 roles.add(new JpaRole(roleFederationMember, organization));
460 roles.add(new JpaRole(roleUserPrefix + id, organization));
461 roles.add(new JpaRole(organization.getAnonymousRole(), organization));
462 if (headerHomeOrganization != null) {
463 String homeOrganization = request.getHeader(headerHomeOrganization);
464 roles.add(new JpaRole(roleOrganizationPrefix + homeOrganization + roleOrganizationSuffix, organization));
465 }
466 if (StringUtils.equals(id, bootstrapUserId)) {
467 roles.add(new JpaRole(GLOBAL_ADMIN_ROLE, organization));
468 }
469 if (headerAffiliation != null) {
470 String affiliation = request.getHeader(headerAffiliation);
471 if (affiliation != null) {
472 List<String> affiliations = Arrays.asList(affiliation.split(";"));
473 for (String eachAffiliation : affiliations) {
474 roles.add(new JpaRole(roleAffiliationPrefix + eachAffiliation, organization));
475 }
476 }
477 }
478
479 return roles;
480 }
481
482
483
484
485
486
487
488 private JpaOrganization fromOrganization(Organization org) {
489 if (org instanceof JpaOrganization) {
490 return (JpaOrganization) org;
491 } else {
492 return new JpaOrganization(org.getId(), org.getName(), org.getServers(), org.getAdminRole(),
493 org.getAnonymousRole(), org.getProperties());
494 }
495 }
496
497
498
499
500 @Override
501 public List<Role> getRolesForUser(String userName) {
502 return Collections.emptyList();
503 }
504
505
506
507
508 @Override
509 public String getOrganization() {
510 return UserProvider.ALL_ORGANIZATIONS;
511 }
512
513
514
515
516 @Override
517 public Iterator<Role> findRoles(String query, Role.Target target, int offset, int limit) {
518 if (query == null) {
519 throw new IllegalArgumentException("Query must be set");
520 }
521 JaxbOrganization organization = JaxbOrganization.fromOrganization(securityService.getOrganization());
522 HashSet<Role> roles = new HashSet<>(2);
523 final String[] roleNames = new String[] {roleFederationMember, organization.getAnonymousRole()};
524 for (String name: roleNames) {
525 if (like(name, query)) {
526 roles.add(new JaxbRole(name, organization));
527 }
528 }
529 return offsetLimitCollection(offset, limit, roles).iterator();
530 }
531
532 private <T> HashSet<T> offsetLimitCollection(int offset, int limit, HashSet<T> entries) {
533 HashSet<T> result = new HashSet<T>();
534 int i = 0;
535 for (T entry : entries) {
536 if (limit != 0 && result.size() >= limit) {
537 break;
538 }
539 if (i >= offset) {
540 result.add(entry);
541 }
542 i++;
543 }
544 return result;
545 }
546
547 private boolean like(String string, final String query) {
548 if (string == null) {
549 return false;
550 }
551 String regex = query.replace("_", ".").replace("%", ".*?");
552 Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
553 return p.matcher(string).matches();
554 }
555
556 }