1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.userdirectory.ldap;
22
23 import org.opencastproject.security.api.Organization;
24 import org.opencastproject.security.api.Role;
25 import org.opencastproject.security.api.SecurityService;
26 import org.opencastproject.userdirectory.JpaGroupRoleProvider;
27
28 import org.apache.commons.lang3.StringUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.springframework.ldap.core.DirContextOperations;
32 import org.springframework.security.core.GrantedAuthority;
33 import org.springframework.security.core.authority.SimpleGrantedAuthority;
34 import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
35
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44
45
46 public class OpencastLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
47
48 public static final String ROLE_CLEAN_REGEXP = "[\\s_]+";
49 public static final String ROLE_CLEAN_REPLACEMENT = "_";
50
51 private Set<String> attributeNames;
52 private String[] additionalAuthorities;
53 private String prefix = "";
54 private Set<String> excludedPrefixes = new HashSet<>();
55 private String groupCheckPrefix = null;
56 private boolean applyAttributesAsRoles = true;
57 private boolean applyAttributesAsGroups = true;
58 private Map<String, String[]> ldapAssignmentRoleMap = new HashMap<>();
59 private Map<String, String[]> ldapAssignmentGroupMap = new HashMap<>();
60 private boolean uppercase = true;
61 private Organization organization;
62 private SecurityService securityService;
63 private JpaGroupRoleProvider groupRoleProvider;
64 private static final Logger logger = LoggerFactory.getLogger(OpencastLdapAuthoritiesPopulator.class);
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public OpencastLdapAuthoritiesPopulator(
82 String attributeNames,
83 String prefix,
84 String[] aExcludedPrefixes,
85 String groupCheckPrefix,
86 boolean applyAttributesAsRoles,
87 boolean applyAttributesAsGroups,
88 Map<String, String[]> ldapAssignmentRoleMap,
89 Map<String, String[]> ldapAssignmentGroupMap,
90 boolean uppercase,
91 Organization organization,
92 SecurityService securityService,
93 JpaGroupRoleProvider groupRoleProvider,
94 String... additionalAuthorities
95 ) {
96
97 logger.debug("Creating new instance");
98
99 if (attributeNames == null) {
100 throw new IllegalArgumentException("The attribute list cannot be null");
101 }
102
103 if (securityService == null) {
104 throw new IllegalArgumentException("The security service cannot be null");
105 }
106 this.securityService = securityService;
107
108 if (organization == null) {
109 throw new IllegalArgumentException("The organization cannot be null");
110 }
111 this.organization = organization;
112
113 this.attributeNames = new HashSet<>();
114 for (String attributeName : attributeNames.split(",")) {
115 String temp = attributeName.trim();
116 if (!temp.isEmpty()) {
117 this.attributeNames.add(temp);
118 }
119 }
120 if (this.attributeNames.size() == 0) {
121 throw new IllegalArgumentException("At least one valid attribute must be provided");
122 }
123
124 if (logger.isDebugEnabled()) {
125 logger.debug("Roles will be read from the LDAP attributes:");
126 for (String attribute : this.attributeNames) {
127 logger.debug("\t* {}", attribute);
128 }
129 }
130
131 if (groupRoleProvider == null) {
132 logger.info("Provided GroupRoleProvider was null. Group roles will therefore not be expanded");
133 }
134 this.groupRoleProvider = groupRoleProvider;
135
136 this.uppercase = uppercase;
137 if (uppercase) {
138 logger.debug("Roles will be converted to uppercase");
139 } else {
140 logger.debug("Roles will NOT be converted to uppercase");
141 }
142
143 this.prefix = roleCleanUpperCase(prefix, uppercase);
144 logger.debug("Role prefix set to: {}", this.prefix);
145
146 if (aExcludedPrefixes != null) {
147 for (String origExcludedPrefix : aExcludedPrefixes) {
148 String excludedPrefix;
149 if (uppercase) {
150 excludedPrefix = StringUtils.trimToEmpty(origExcludedPrefix).toUpperCase();
151 } else {
152 excludedPrefix = StringUtils.trimToEmpty(origExcludedPrefix);
153 }
154 if (!excludedPrefix.isEmpty()) {
155 excludedPrefixes.add(excludedPrefix);
156 }
157 }
158 }
159
160 if (groupCheckPrefix == null) {
161 throw new IllegalArgumentException("The parameter groupCheckPrefix cannot be null");
162 }
163 this.groupCheckPrefix = groupCheckPrefix;
164 if (uppercase) {
165 this.groupCheckPrefix = this.groupCheckPrefix.toUpperCase();
166 }
167
168 this.applyAttributesAsRoles = applyAttributesAsRoles;
169 this.applyAttributesAsGroups = applyAttributesAsGroups;
170
171 if (ldapAssignmentRoleMap != null) {
172 this.ldapAssignmentRoleMap = ldapAssignmentRoleMap;
173 }
174
175 if (ldapAssignmentGroupMap != null) {
176 this.ldapAssignmentGroupMap = ldapAssignmentGroupMap;
177 }
178
179 if (additionalAuthorities == null) {
180 this.additionalAuthorities = new String[0];
181 } else {
182 this.additionalAuthorities = Arrays.stream(additionalAuthorities)
183 .map(x -> roleCleanUpperCase(x, uppercase))
184 .toArray(String[]::new);
185 }
186
187 if (logger.isDebugEnabled()) {
188 StringBuilder additionalAuthoritiesAsStr = new StringBuilder();
189 for (String role : this.additionalAuthorities) {
190 additionalAuthoritiesAsStr.append(String.format("\n\t* %s", role));
191 }
192 logger.debug("Authenticated users will receive the following extra roles:{}", additionalAuthoritiesAsStr);
193 }
194 }
195
196 @Override
197 public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
198
199 logger.debug("user attributes for user {}:\n\t{}", username, userData.getAttributes());
200
201 Set<GrantedAuthority> authorities = new HashSet<>();
202 for (String attributeName : attributeNames) {
203 logger.debug("Looking for attribute name '{}'", attributeName);
204 try {
205 String[] attributeValues = userData.getStringAttributes(attributeName);
206
207 if (attributeValues != null) {
208 for (String attributeValue : attributeValues) {
209
210 String[] splitValue = attributeValue.split(",");
211 if (applyAttributesAsRoles) {
212 String[] roles = splitValue;
213 addAuthorities(authorities, roles, false, true);
214 if (applyAttributesAsGroups) {
215
216 String[] groups = Arrays.stream(splitValue)
217 .filter(x -> {
218 String filter = roleCleanUpperCase(x, uppercase);
219 return filter.startsWith(groupCheckPrefix);
220 })
221 .toArray(String[]::new);
222 addAuthorities(authorities, groups, true, true);
223 }
224 }
225
226
227 String[] mappedRoles = Arrays.stream(splitValue)
228 .map(x -> roleCleanUpperCase(x, uppercase))
229 .map(x -> ldapAssignmentRoleMap.get(x))
230 .filter(x -> x != null)
231 .flatMap(x -> Arrays.stream(x))
232 .toArray(String[]::new);
233 addAuthorities(authorities, mappedRoles, false, false);
234
235 String[] mappedGroups = Arrays.stream(splitValue)
236 .map(x -> roleCleanUpperCase(x, uppercase))
237 .map(x -> ldapAssignmentGroupMap.get(x))
238 .filter(x -> x != null)
239 .flatMap(x -> Arrays.stream(x))
240 .toArray(String[]::new);
241 addAuthorities(authorities, mappedGroups, true, false);
242 }
243 } else {
244 logger.debug("Could not find any attribute named '{}' in user '{}'", attributeName, userData.getDn());
245 }
246 } catch (ClassCastException e) {
247 logger.error(
248 "Specified attribute containing user roles ('{}') was not of expected type String",
249 attributeName, e);
250 }
251 }
252
253
254 addAuthorities(authorities, additionalAuthorities, false, false);
255 addAuthorities(authorities, Arrays.stream(additionalAuthorities)
256 .filter(x -> x.startsWith(groupCheckPrefix))
257 .toArray(String[]::new),
258 true, false
259 );
260
261 if (logger.isDebugEnabled()) {
262 StringBuilder authorityListAsString = new StringBuilder();
263 for (GrantedAuthority authority : authorities) {
264 authorityListAsString.append(String.format("\n\t%s", authority));
265 }
266 logger.debug("Returning user {} with authorities:{}", username, authorityListAsString);
267 }
268
269
270
271 return authorities;
272 }
273
274
275
276
277
278
279 public Collection<String> getAttributeNames() {
280 return new HashSet<>(attributeNames);
281 }
282
283
284
285
286
287
288 public String getRolePrefix() {
289 return prefix;
290 }
291
292
293
294
295
296
297 public String[] getExcludePrefixes() {
298 return excludedPrefixes.toArray(new String[0]);
299 }
300
301
302
303
304
305
306 public boolean getConvertToUpperCase() {
307 return uppercase;
308 }
309
310
311
312
313
314
315 public String[] getAdditionalAuthorities() {
316 return additionalAuthorities.clone();
317 }
318
319
320
321
322
323
324
325
326
327 private String roleCleanUpperCase(String rawRole, boolean toUpperCase) {
328 if (toUpperCase) {
329 return StringUtils.trimToEmpty(rawRole).replaceAll(ROLE_CLEAN_REGEXP, ROLE_CLEAN_REPLACEMENT)
330 .toUpperCase();
331 }
332 else {
333 return StringUtils.trimToEmpty(rawRole).replaceAll(ROLE_CLEAN_REGEXP, ROLE_CLEAN_REPLACEMENT);
334 }
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349 private void addAuthorities(Set<GrantedAuthority> authorities, final String[] values,
350 final boolean addAsGroup, final boolean addPrefix) {
351
352 if (values != null) {
353 Organization org = securityService.getOrganization();
354 if (!organization.equals(org)) {
355 throw new SecurityException(String.format(
356 "Current request belongs to the organization \"%s\". Expected \"%s\"",
357 org.getId(), organization.getId()));
358 }
359
360 for (String value : values) {
361
362
363
364
365
366
367
368
369
370 String authority = roleCleanUpperCase(value, uppercase);
371
372
373 if (!authority.isEmpty()) {
374
375 List<Role> groupRoles;
376 if (groupRoleProvider != null && addAsGroup) {
377 groupRoles = groupRoleProvider.getRolesForGroup(authority);
378 } else {
379 groupRoles = Collections.emptyList();
380 }
381
382
383 String prefix = this.prefix;
384
385 if (addPrefix) {
386 if (!prefix.isEmpty()) {
387 boolean hasExcludePrefix = false;
388 for (String excludePrefix : excludedPrefixes) {
389 if (authority.startsWith(excludePrefix)) {
390 hasExcludePrefix = true;
391 break;
392 }
393 }
394 if (hasExcludePrefix) {
395 prefix = "";
396 }
397 }
398 }
399 else {
400 prefix = "";
401 }
402
403 authority = (prefix + authority).replaceAll(ROLE_CLEAN_REGEXP, ROLE_CLEAN_REPLACEMENT);
404
405 logger.debug("Parsed LDAP role \"{}\" to role \"{}\"", value, authority);
406
407 if (!groupRoles.isEmpty()) {
408
409 logger.debug("Found group for the group with group role \"{}\"", authority);
410 for (Role role : groupRoles) {
411 authorities.add(new SimpleGrantedAuthority(role.getName()));
412 logger.debug("\tAdded role from role \"{}\"'s group: {}", authority, role);
413 }
414 }
415
416
417 authorities.add(new SimpleGrantedAuthority(authority));
418
419 } else {
420 logger.debug("Found empty authority. Ignoring...");
421 }
422 }
423 }
424 }
425
426
427 public void setOrgDirectory(JpaGroupRoleProvider groupRoleProvider) {
428 this.groupRoleProvider = groupRoleProvider;
429 }
430
431
432 public void setSecurityService(SecurityService securityService) {
433 this.securityService = securityService;
434 }
435
436 }