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.ldap;
23
24 import org.opencastproject.security.api.Organization;
25 import org.opencastproject.security.api.OrganizationDirectoryService;
26 import org.opencastproject.security.api.SecurityService;
27 import org.opencastproject.security.api.UserProvider;
28 import org.opencastproject.userdirectory.JpaGroupRoleProvider;
29 import org.opencastproject.util.NotFoundException;
30
31 import org.apache.commons.lang3.ArrayUtils;
32 import org.apache.commons.lang3.BooleanUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.math.NumberUtils;
35 import org.osgi.framework.BundleContext;
36 import org.osgi.framework.ServiceRegistration;
37 import org.osgi.service.cm.ConfigurationException;
38 import org.osgi.service.cm.ManagedServiceFactory;
39 import org.osgi.service.component.ComponentContext;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
46
47 import java.lang.management.ManagementFactory;
48 import java.util.Arrays;
49 import java.util.Dictionary;
50 import java.util.Enumeration;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.Hashtable;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.concurrent.ConcurrentHashMap;
58
59 import javax.management.MalformedObjectNameException;
60 import javax.management.ObjectName;
61
62
63
64
65 @Component(
66 immediate = true,
67 service = ManagedServiceFactory.class,
68 property = {
69 "service.pid=org.opencastproject.userdirectory.ldap",
70 "service.description=Provides ldap user directory instances"
71 }
72 )
73 public class LdapUserProviderFactory implements ManagedServiceFactory {
74
75
76 private static final Logger logger = LoggerFactory.getLogger(LdapUserProviderFactory.class);
77
78
79 private static final String PID = "org.opencastproject.userdirectory.ldap";
80
81
82 private static final String SEARCH_FILTER_KEY = "org.opencastproject.userdirectory.ldap.searchfilter";
83
84
85 private static final String SEARCH_BASE_KEY = "org.opencastproject.userdirectory.ldap.searchbase";
86
87
88 private static final String LDAP_URL_KEY = "org.opencastproject.userdirectory.ldap.url";
89
90
91 private static final String ROLE_ATTRIBUTES_KEY = "org.opencastproject.userdirectory.ldap.roleattributes";
92
93
94 private static final String USER_NAME_ATTRIBUTES_KEY = "org.opencastproject.userdirectory.ldap.userattributes.name";
95
96
97 private static final String USER_MAIL_ATTRIBUTE_KEY = "org.opencastproject.userdirectory.ldap.userattributes.mail";
98
99
100 private static final String ORGANIZATION_KEY = "org.opencastproject.userdirectory.ldap.org";
101
102
103 private static final String SEARCH_USER_DN = "org.opencastproject.userdirectory.ldap.userDn";
104
105
106 private static final String SEARCH_PASSWORD = "org.opencastproject.userdirectory.ldap.password";
107
108
109 private static final String CACHE_SIZE = "org.opencastproject.userdirectory.ldap.cache.size";
110
111
112 private static final String CACHE_EXPIRATION = "org.opencastproject.userdirectory.ldap.cache.expiration";
113
114
115 private static final String ROLE_PREFIX_KEY = "org.opencastproject.userdirectory.ldap.roleprefix";
116
117
118
119
120
121 private static final String EXCLUDE_PREFIXES_KEY = "org.opencastproject.userdirectory.ldap.exclude.prefixes";
122
123
124
125
126
127 private static final String GROUP_CHECK_PREFIX_KEY = "org.opencastproject.userdirectory.ldap.groupcheckprefix";
128
129
130 private static final String APPLY_ROLEATTRIBUTES_AS_ROLES_KEY
131 = "org.opencastproject.userdirectory.ldap.roleattributes.applyasroles";
132
133
134 private static final String APPLY_ROLEATTRIBUTES_AS_GROUPS_KEY
135 = "org.opencastproject.userdirectory.ldap.roleattributes.applyasgroups";
136
137
138 private static final String ATTRIBUTE_MAPPING_KEY_PREFIX = "org.opencastproject.userdirectory.ldap.map.";
139
140
141 private static final String ATTRIBUTE_MAPPING_KEY_POSTFIX_VALUE = "value";
142
143
144 private static final String ATTRIBUTE_MAPPING_KEY_POSTFIX_ROLES = "roles";
145
146
147 private static final String ATTRIBUTE_MAPPING_KEY_POSTFIX_GROUPS = "groups";
148
149
150 private static final String UPPERCASE_KEY = "org.opencastproject.userdirectory.ldap.uppercase";
151
152
153 private static final String INSTANCE_ID_KEY = "org.opencastproject.userdirectory.ldap.id";
154
155
156 private static final String EXTRA_ROLES_KEY = "org.opencastproject.userdirectory.ldap.extra.roles";
157
158
159 private static final String INSTANCE_ID_SERVICE_PROPERTY_KEY = "instanceId";
160
161
162 private Map<String, ServiceRegistration> providerRegistrations = new ConcurrentHashMap<>();
163
164
165 private Map<String, ServiceRegistration> authoritiesPopulatorRegistrations = new ConcurrentHashMap<>();
166
167
168 private BundleContext bundleContext = null;
169
170
171 private OrganizationDirectoryService orgDirectory;
172
173
174 private JpaGroupRoleProvider groupRoleProvider;
175
176
177 private SecurityService securityService;
178
179
180 @Reference
181 public void setOrgDirectory(OrganizationDirectoryService orgDirectory) {
182 this.orgDirectory = orgDirectory;
183 }
184
185
186 @Reference
187 public void setGroupRoleProvider(JpaGroupRoleProvider groupRoleProvider) {
188 this.groupRoleProvider = groupRoleProvider;
189 }
190
191
192 @Reference
193 public void setSecurityService(SecurityService securityService) {
194 this.securityService = securityService;
195 }
196
197
198
199
200
201
202
203 @Activate
204 public void activate(ComponentContext cc) {
205 logger.debug("Activate LdapUserProviderFactory");
206 bundleContext = cc.getBundleContext();
207 }
208
209
210
211
212
213
214 @Override
215 public String getName() {
216 return PID;
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230
231 private String getRequiredProperty(final Dictionary properties, final String key) throws ConfigurationException {
232 final String value = (String) properties.get(key);
233 if (StringUtils.isBlank(value)) {
234 throw new ConfigurationException(key, "missing configuration value");
235 }
236 return value;
237 }
238
239
240
241
242
243
244 @Override
245 public void updated(String pid, Dictionary properties) throws ConfigurationException {
246 logger.debug("Updating LdapUserProviderFactory");
247
248
249 String searchBase = getRequiredProperty(properties, SEARCH_BASE_KEY);
250 String searchFilter = getRequiredProperty(properties, SEARCH_FILTER_KEY);
251 String url = getRequiredProperty(properties, LDAP_URL_KEY);
252 String instanceId = getRequiredProperty(properties, INSTANCE_ID_KEY);
253 String roleAttributes = getRequiredProperty(properties, ROLE_ATTRIBUTES_KEY);
254
255
256 String organization = (String) properties.get(ORGANIZATION_KEY);
257 String userDn = (String) properties.get(SEARCH_USER_DN);
258 String password = (String) properties.get(SEARCH_PASSWORD);
259 String[] userAttributeName = StringUtils.split((String) properties.get(USER_NAME_ATTRIBUTES_KEY),',');
260 String userAttributeMail = (String) properties.get(USER_MAIL_ATTRIBUTE_KEY);
261
262
263 String rolePrefix = Objects.toString(properties.get(ROLE_PREFIX_KEY), "ROLE_");
264 String[] excludePrefixes = StringUtils.split((String) properties.get(EXCLUDE_PREFIXES_KEY), ",");
265 String groupCheckPrefix = Objects.toString(properties.get(GROUP_CHECK_PREFIX_KEY), "ROLE_GROUP_");
266 boolean convertToUppercase = BooleanUtils.toBoolean(Objects.toString(properties.get(UPPERCASE_KEY), "true"));
267 int cacheSize = NumberUtils.toInt((String) properties.get(CACHE_SIZE), 1000);
268 int cacheExpiration = NumberUtils.toInt((String) properties.get(CACHE_EXPIRATION), 5);
269 boolean applyRoleattributesAsRoles = BooleanUtils.toBoolean(Objects.toString(
270 properties.get(APPLY_ROLEATTRIBUTES_AS_ROLES_KEY), "true"));
271 boolean applyRoleattributesAsGroups = BooleanUtils.toBoolean(Objects.toString(
272 properties.get(APPLY_ROLEATTRIBUTES_AS_GROUPS_KEY), "true"));
273 if (applyRoleattributesAsGroups && !applyRoleattributesAsRoles) {
274 throw new ConfigurationException(APPLY_ROLEATTRIBUTES_AS_GROUPS_KEY,
275 "'" + APPLY_ROLEATTRIBUTES_AS_ROLES_KEY + "' needs to be 'true' to enable this option");
276 }
277
278
279 String[] extraRoles = StringUtils.split(Objects.toString(properties.get(EXTRA_ROLES_KEY), ""), ",");
280 Set<String> extraRoleSet = new HashSet<>(Arrays.asList(extraRoles));
281 extraRoleSet.addAll(Arrays.asList("ROLE_ANONYMOUS", "ROLE_USER"));
282 extraRoles = extraRoleSet.toArray(new String[extraRoles.length]);
283
284
285 HashMap<String, HashMap<String, String>> ldapAssignmentMappingsPreparation = new HashMap();
286 for (Enumeration<String> e = properties.keys(); e.hasMoreElements();) {
287 String key = e.nextElement();
288
289 if (key.startsWith(ATTRIBUTE_MAPPING_KEY_PREFIX)) {
290 final String[] postfix = key.substring(ATTRIBUTE_MAPPING_KEY_PREFIX.length()).split("\\.");
291
292 if (postfix.length != 2) {
293 throw new ConfigurationException(key,
294 "Invalid Configkey format, the following format is needed: "
295 + ATTRIBUTE_MAPPING_KEY_PREFIX + "<identifier>.<key>");
296 }
297
298 final String mappingIdentifier = postfix[0];
299 final String mappingKey = postfix[1];
300
301 HashMap keyValueMap = ldapAssignmentMappingsPreparation.getOrDefault(mappingIdentifier, new HashMap());
302
303 keyValueMap.put(mappingKey, (String) properties.get(key));
304
305 ldapAssignmentMappingsPreparation.put(mappingIdentifier, keyValueMap);
306 }
307 }
308 HashMap<String, String[]> ldapAssignmentRoleMap = new HashMap();
309 HashMap<String, String[]> ldapAssignmentGroupMap = new HashMap();
310 for (HashMap.Entry<String, HashMap<String, String>> entry : ldapAssignmentMappingsPreparation.entrySet()) {
311 HashMap<String, String> mappingConf = entry.getValue();
312 String value = StringUtils.trimToNull(mappingConf.get(ATTRIBUTE_MAPPING_KEY_POSTFIX_VALUE));
313 String roles = StringUtils.trimToNull(mappingConf.get(ATTRIBUTE_MAPPING_KEY_POSTFIX_ROLES));
314 String groups = StringUtils.trimToNull(mappingConf.get(ATTRIBUTE_MAPPING_KEY_POSTFIX_GROUPS));
315
316 if (value == null) {
317 throw new ConfigurationException(ATTRIBUTE_MAPPING_KEY_PREFIX + entry.getKey() + ".*",
318 "LDAP mapping incomplete, the key 'value' is needed");
319 }
320 if (roles == null && groups == null) {
321 throw new ConfigurationException(ATTRIBUTE_MAPPING_KEY_PREFIX + entry.getKey() + ".*",
322 "LDAP mapping incomplete, one of the keys 'roles' or 'groups' is needed");
323 }
324
325 if (convertToUppercase) {
326 value = value.toUpperCase();
327 }
328
329 if (roles != null) {
330 if (convertToUppercase) {
331 roles = roles.toUpperCase();
332 }
333 ldapAssignmentRoleMap.put(value,
334 ArrayUtils.addAll(
335 ldapAssignmentRoleMap.getOrDefault(value, new String[0]),
336 Arrays.stream(roles.split(","))
337 .map(r -> StringUtils.trimToNull(r))
338 .filter(r -> r != null)
339 .toArray(String[]::new)
340 )
341 );
342 }
343
344 if (groups != null) {
345 if (convertToUppercase) {
346 groups = groups.toUpperCase();
347 }
348 ldapAssignmentGroupMap.put(value,
349 ArrayUtils.addAll(
350 ldapAssignmentGroupMap.getOrDefault(value, new String[0]),
351 Arrays.stream(groups.split(","))
352 .map(r -> StringUtils.trimToNull(r))
353 .filter(r -> r != null)
354 .toArray(String[]::new)
355 )
356 );
357 }
358 }
359
360
361 ServiceRegistration existingRegistration = providerRegistrations.remove(pid);
362 if (existingRegistration != null) {
363 existingRegistration.unregister();
364 }
365
366
367 Organization org;
368 try {
369 if (StringUtils.isNoneBlank(organization)) {
370 org = orgDirectory.getOrganization(organization);
371 } else {
372 if (orgDirectory.getOrganizations().size() != 1) {
373 throw new NotFoundException("Multiple organizations exist but none is specified");
374 }
375 org = orgDirectory.getOrganizations().get(0);
376 }
377 } catch (NotFoundException e) {
378 throw new ConfigurationException(ORGANIZATION_KEY, "no organization with configured id", e);
379 }
380
381
382 Hashtable<String, String> dict = new Hashtable<>();
383 dict.put(INSTANCE_ID_SERVICE_PROPERTY_KEY, instanceId);
384
385
386
387 OpencastLdapAuthoritiesPopulator authoritiesPopulator = new OpencastLdapAuthoritiesPopulator(roleAttributes,
388 rolePrefix, excludePrefixes, groupCheckPrefix, applyRoleattributesAsRoles, applyRoleattributesAsGroups,
389 ldapAssignmentRoleMap, ldapAssignmentGroupMap, convertToUppercase, org, securityService,
390 groupRoleProvider, extraRoles);
391
392
393 authoritiesPopulatorRegistrations.put(pid,
394 bundleContext.registerService(LdapAuthoritiesPopulator.class.getName(), authoritiesPopulator, dict));
395
396 OpencastUserDetailsContextMapper mapper = null;
397 if (userAttributeName != null && userAttributeMail != null) {
398 mapper = new OpencastUserDetailsContextMapper(userAttributeName, userAttributeMail);
399 }
400
401 LdapUserProviderInstance provider = new LdapUserProviderInstance(pid, org, searchBase, searchFilter, url, userDn,
402 password, roleAttributes, cacheSize,
403 cacheExpiration, securityService, authoritiesPopulator, mapper);
404
405 providerRegistrations.put(pid, bundleContext.registerService(UserProvider.class.getName(), provider, null));
406
407 }
408
409
410
411
412
413
414 @Override
415 public void deleted(String pid) {
416 ServiceRegistration providerRegistration = null;
417 ServiceRegistration authoritiesPopulatorRegistration = null;
418
419 try {
420 providerRegistration = providerRegistrations.remove(pid);
421 authoritiesPopulatorRegistration = authoritiesPopulatorRegistrations.remove(pid);
422 if ((providerRegistration != null) || (authoritiesPopulatorRegistration != null)) {
423 try {
424 ManagementFactory.getPlatformMBeanServer().unregisterMBean(LdapUserProviderFactory.getObjectName(pid));
425 } catch (Exception e) {
426 logger.warn("Unable to unregister mbean for pid='{}': {}", pid, e.getMessage());
427 }
428 }
429 } finally {
430 if (providerRegistration != null) {
431 providerRegistration.unregister();
432 }
433 if (authoritiesPopulatorRegistration != null) {
434 authoritiesPopulatorRegistration.unregister();
435 }
436 }
437 }
438
439
440
441
442
443
444
445
446
447
448 public static final ObjectName getObjectName(String pid) throws MalformedObjectNameException, NullPointerException {
449 return new ObjectName(pid + ":type=LDAPRequests");
450 }
451
452 }