Java tutorial
/** * Copyright 2009, 2010 The Regents of the University of California * Licensed under the Educational Community License, Version 2.0 * (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. * */ package org.opencastproject.userdirectory.ldap; import org.opencastproject.security.api.CachingUserProviderMXBean; import org.opencastproject.security.api.User; import org.opencastproject.security.api.UserProvider; import com.google.common.base.Function; import com.google.common.collect.MapMaker; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper; import org.springframework.security.ldap.userdetails.LdapUserDetailsService; import java.lang.management.ManagementFactory; import java.util.Collection; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.management.InstanceNotFoundException; import javax.management.MBeanServer; import javax.management.ObjectName; /** * A UserProvider that reads user roles from LDAP entries. */ public class LdapUserProviderInstance implements UserProvider, CachingUserProviderMXBean { /** The logger */ private static final Logger logger = LoggerFactory.getLogger(LdapUserProviderInstance.class); /** The spring ldap userdetails service delegate */ private LdapUserDetailsService delegate = null; /** The organization id */ private String organization = null; /** Total number of requests made to load users */ private AtomicLong requests = null; /** The number of requests made to ldap */ private AtomicLong ldapLoads = null; /** A cache of users, which lightens the load on the LDAP server */ private ConcurrentMap<String, Object> cache = null; /** A token to store in the miss cache */ protected Object nullToken = new Object(); /** * Constructs an ldap user provider with the needed settings. * * @param pid * the pid of this service * @param organization * the organization * @param searchBase * the ldap search base * @param searchFilter * the ldap search filter * @param url * the url of the ldap server * @param userDn * the user to authenticate as * @param password * the user credentials * @param roleAttributesGlob * the comma separate list of ldap attributes to treat as roles * @param cacheSize * the number of users to cache * @param cacheExpiration * the number of minutes to cache users */ // CHECKSTYLE:OFF LdapUserProviderInstance(String pid, String organization, String searchBase, String searchFilter, String url, String userDn, String password, String roleAttributesGlob, int cacheSize, int cacheExpiration) { // CHECKSTYLE:ON this.organization = organization; logger.debug("Creating LdapUserProvider instance with pid=" + pid + ", and organization=" + organization + ", to LDAP server at url: " + url); DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(url); if (StringUtils.isNotBlank(userDn)) { contextSource.setPassword(password); contextSource.setUserDn(userDn); // Required so that authentication will actually be used contextSource.setAnonymousReadOnly(false); } else { // No password set so try to connect anonymously. contextSource.setAnonymousReadOnly(true); } try { contextSource.afterPropertiesSet(); } catch (Exception e) { throw new org.opencastproject.util.ConfigurationException("Unable to create a spring context source", e); } FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(searchBase, searchFilter, contextSource); userSearch.setReturningAttributes(roleAttributesGlob.split(",")); this.delegate = new LdapUserDetailsService(userSearch); if (StringUtils.isNotBlank(roleAttributesGlob)) { LdapUserDetailsMapper mapper = new LdapUserDetailsMapper(); mapper.setRoleAttributes(roleAttributesGlob.split(",")); this.delegate.setUserDetailsMapper(mapper); } // Setup the caches cache = new MapMaker().maximumSize(cacheSize).expireAfterWrite(cacheExpiration, TimeUnit.MINUTES) .makeComputingMap(new Function<String, Object>() { public Object apply(String id) { User user = loadUserFromLdap(id); return user == null ? nullToken : user; } }); registerMBean(pid); } /** * Registers an MXBean. */ protected void registerMBean(String pid) { // register with jmx requests = new AtomicLong(); ldapLoads = new AtomicLong(); try { ObjectName name; name = LdapUserProviderFactory.getObjectName(pid); Object mbean = this; MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); try { mbs.unregisterMBean(name); } catch (InstanceNotFoundException e) { logger.debug(name + " was not registered"); } mbs.registerMBean(mbean, name); } catch (Exception e) { logger.warn("Unable to register {} as an mbean: {}", this, e); } } /** * {@inheritDoc} * * @see org.opencastproject.security.api.UserProvider#getOrganization() */ @Override public String getOrganization() { return organization; } /** * {@inheritDoc} * * @see org.opencastproject.security.api.UserProvider#loadUser(java.lang.String) */ @Override public User loadUser(String userName) { logger.debug("LdapUserProvider is loading user " + userName); requests.incrementAndGet(); try { Object user = cache.get(userName); if (user == nullToken) { return null; } else { return (User) user; } } catch (NullPointerException e) { logger.debug("This map throws NPE rather than returning null. Swallowing that exception here."); return null; } } /** * Loads a user from LDAP. * * @param userName * the username * @return the user */ protected User loadUserFromLdap(String userName) { if (delegate == null || cache == null) { throw new IllegalStateException("The LDAP user detail service has not yet been configured"); } ldapLoads.incrementAndGet(); UserDetails userDetails = null; Thread currentThread = Thread.currentThread(); ClassLoader originalClassloader = currentThread.getContextClassLoader(); try { currentThread.setContextClassLoader(LdapUserProviderFactory.class.getClassLoader()); try { userDetails = delegate.loadUserByUsername(userName); } catch (UsernameNotFoundException e) { cache.put(userName, nullToken); return null; } Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); String[] roles = null; if (authorities != null) { int i = 0; roles = new String[authorities.size()]; for (GrantedAuthority authority : authorities) { String role = authority.getAuthority(); roles[i++] = role; } } User user = new User(userDetails.getUsername(), getOrganization(), roles); cache.put(userName, user); return user; } finally { currentThread.setContextClassLoader(originalClassloader); } } /** * {@inheritDoc} * * @see org.opencastproject.security.api.CachingUserProviderMXBean#getCacheHitRatio() */ public float getCacheHitRatio() { if (requests.get() == 0) { return 0; } return (float) (requests.get() - ldapLoads.get()) / requests.get(); } }