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.CachingUserProviderMXBean;
25 import org.opencastproject.security.api.JaxbOrganization;
26 import org.opencastproject.security.api.JaxbRole;
27 import org.opencastproject.security.api.JaxbUser;
28 import org.opencastproject.security.api.Organization;
29 import org.opencastproject.security.api.SecurityService;
30 import org.opencastproject.security.api.User;
31 import org.opencastproject.security.api.UserProvider;
32
33 import com.google.common.cache.CacheBuilder;
34 import com.google.common.cache.CacheLoader;
35 import com.google.common.cache.LoadingCache;
36 import com.google.common.util.concurrent.UncheckedExecutionException;
37
38 import org.apache.commons.lang3.StringUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.springframework.security.core.userdetails.UserDetails;
42 import org.springframework.security.core.userdetails.UsernameNotFoundException;
43 import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
44 import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
45 import org.springframework.security.ldap.userdetails.LdapUserDetailsService;
46
47 import java.lang.management.ManagementFactory;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.Set;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicLong;
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
57
58 import javax.management.InstanceNotFoundException;
59 import javax.management.MBeanServer;
60 import javax.management.ObjectName;
61
62
63
64
65 public class LdapUserProviderInstance implements UserProvider, CachingUserProviderMXBean {
66
67
68 private static final Logger logger = LoggerFactory.getLogger(LdapUserProviderInstance.class);
69
70 public static final String PROVIDER_NAME = "ldap";
71
72
73 private final LdapUserDetailsService delegate;
74
75
76 private Organization organization = null;
77
78
79 private AtomicLong requests = null;
80
81
82 private AtomicLong ldapLoads = null;
83
84
85 private LoadingCache<String, Object> cache = null;
86
87
88 protected Object nullToken = new Object();
89
90
91 private final SecurityService securityService;
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 LdapUserProviderInstance(
125 String pid,
126 Organization organization,
127 String searchBase,
128 String searchFilter,
129 String url,
130 String userDn,
131 String password,
132 String roleAttributesGlob,
133 int cacheSize,
134 int cacheExpiration,
135 SecurityService securityService,
136 OpencastLdapAuthoritiesPopulator authoritiesPopulator,
137 OpencastUserDetailsContextMapper userDetailsContextMapper
138 ) {
139
140 this.organization = organization;
141 this.securityService = securityService;
142 logger.debug("Creating LdapUserProvider instance with pid=" + pid + ", and organization=" + organization
143 + ", to LDAP server at url: " + url);
144
145 DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(url);
146 if (StringUtils.isNotBlank(userDn)) {
147 contextSource.setPassword(password);
148 contextSource.setUserDn(userDn);
149
150 contextSource.setAnonymousReadOnly(false);
151 } else {
152
153 contextSource.setAnonymousReadOnly(true);
154 }
155
156 try {
157 contextSource.afterPropertiesSet();
158 } catch (Exception e) {
159 throw new org.opencastproject.util.ConfigurationException("Unable to create a spring context source", e);
160 }
161 FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource);
162 userSearch.setReturningAttributes(roleAttributesGlob.split(","));
163
164 delegate = new LdapUserDetailsService(userSearch, authoritiesPopulator);
165
166 if (userDetailsContextMapper != null) {
167 userSearch.setReturningAttributes(
168 Stream.of(roleAttributesGlob.split(","), userDetailsContextMapper.getAttributes())
169 .flatMap(Stream::of)
170 .collect(Collectors.toList()).toArray(new String[] { })
171 );
172 delegate.setUserDetailsMapper(userDetailsContextMapper);
173 }
174
175
176 cache = CacheBuilder.newBuilder().maximumSize(cacheSize).expireAfterWrite(cacheExpiration, TimeUnit.MINUTES)
177 .build(new CacheLoader<String, Object>() {
178 @Override
179 public Object load(String id) throws Exception {
180 User user = loadUserFromLdap(id);
181 return user == null ? nullToken : user;
182 }
183 });
184
185 registerMBean(pid);
186 }
187
188 @Override
189 public String getName() {
190 return PROVIDER_NAME;
191 }
192
193
194
195
196 protected void registerMBean(String pid) {
197
198 requests = new AtomicLong();
199 ldapLoads = new AtomicLong();
200 try {
201 ObjectName name;
202 name = LdapUserProviderFactory.getObjectName(pid);
203 Object mbean = this;
204 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
205 try {
206 mbs.unregisterMBean(name);
207 } catch (InstanceNotFoundException e) {
208 logger.debug(name + " was not registered");
209 }
210 mbs.registerMBean(mbean, name);
211 } catch (Exception e) {
212 logger.warn("Unable to register {} as an mbean", this, e);
213 }
214 }
215
216
217
218
219
220
221 @Override
222 public String getOrganization() {
223 return organization.getId();
224 }
225
226
227
228
229
230
231 @Override
232 public User loadUser(String userName) {
233 logger.debug("LdapUserProvider is loading user " + userName);
234 requests.incrementAndGet();
235 try {
236
237 Object user = cache.getUnchecked(userName);
238 if (user == nullToken) {
239 return null;
240 } else {
241 return (JaxbUser) user;
242 }
243 } catch (UncheckedExecutionException e) {
244 logger.warn("Exception while loading user " + userName, e);
245 return null;
246 }
247 }
248
249
250
251
252
253
254
255
256 protected User loadUserFromLdap(String userName) {
257 if (delegate == null || cache == null) {
258 throw new IllegalStateException("The LDAP user detail service has not yet been configured");
259 }
260 ldapLoads.incrementAndGet();
261 UserDetails userDetails = null;
262
263 Thread currentThread = Thread.currentThread();
264 ClassLoader originalClassloader = currentThread.getContextClassLoader();
265 try {
266 currentThread.setContextClassLoader(LdapUserProviderFactory.class.getClassLoader());
267 try {
268 userDetails = delegate.loadUserByUsername(userName);
269 } catch (UsernameNotFoundException e) {
270 cache.put(userName, nullToken);
271 return null;
272 }
273 JaxbOrganization jaxbOrganization = JaxbOrganization.fromOrganization(organization);
274
275 Set<JaxbRole> roles = userDetails.getAuthorities()
276 .stream()
277 .map(a -> new JaxbRole(a.getAuthority(), jaxbOrganization))
278 .collect(Collectors.toUnmodifiableSet());
279
280 User user;
281 if (userDetails instanceof OpencastUserDetails) {
282 user = new JaxbUser(userDetails.getUsername(),null,
283 ((OpencastUserDetails) userDetails).getName(),
284 ((OpencastUserDetails) userDetails).getMail(), PROVIDER_NAME, jaxbOrganization, roles);
285 } else {
286 user = new JaxbUser(userDetails.getUsername(), PROVIDER_NAME, jaxbOrganization, roles);
287 }
288
289 cache.put(userName, user);
290 return user;
291 } finally {
292 currentThread.setContextClassLoader(originalClassloader);
293 }
294 }
295
296
297
298
299
300
301 @Override
302 public float getCacheHitRatio() {
303 if (requests.get() == 0) {
304 return 0;
305 }
306 return (float) (requests.get() - ldapLoads.get()) / requests.get();
307 }
308
309 @Override
310 public Iterator<User> findUsers(String query, int offset, int limit) {
311 if (query == null) {
312 throw new IllegalArgumentException("Query must be set");
313 }
314
315
316
317 User currentUser = securityService.getUser();
318 if (loadUser(currentUser.getUsername()) != null) {
319 List<User> retVal = new ArrayList<>();
320 retVal.add(securityService.getUser());
321 return retVal.iterator();
322 }
323 return Collections.emptyIterator();
324 }
325
326 @Override
327 public Iterator<User> getUsers() {
328
329
330
331
332 User currentUser = securityService.getUser();
333 if (loadUser(currentUser.getUsername()) != null) {
334 List<User> retVal = new ArrayList<>();
335 retVal.add(securityService.getUser());
336 return retVal.iterator();
337 }
338 return Collections.emptyIterator();
339 }
340
341 @Override
342 public long countUsers() {
343
344
345 if (loadUser(securityService.getUser().getUsername()) != null) {
346 return 1;
347 }
348 return 0;
349 }
350
351 @Override
352 public void invalidate(String userName) {
353 cache.invalidate(userName);
354 }
355 }