/******************************************************************************* * Copyright (c) 2004, 2007-2010 IBM Corporation and Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * File: $Source: /cvsroot/slrp/boca/com.ibm.adtech.boca.model/src/com/ibm/adtech/boca/model/security/LdapAuthenticationProvider.java,v $ * Created by: Matthew Roy ( <a href="mailto:mroy@us.ibm.com">mroy@us.ibm.com </a>) * Created on: 8/25/2006 * Revision: $Id: LdapAuthenticationProvider.java 180 2007-07-31 14:24:13Z mroy $ * * Contributors: * IBM Corporation - initial API and implementation * Cambridge Semantics Incorporated - Fork to Anzo *******************************************************************************/ package org.openanzo.security.ldap; import java.io.UnsupportedEncodingException; import java.security.Security; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Dictionary; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.AnzoRuntimeException; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.exceptions.LogUtils; import org.openanzo.rdf.Constants; import org.openanzo.rdf.URI; import org.openanzo.security.keystore.KeyStoreDictionary; import org.openanzo.services.AnzoPrincipal; import org.openanzo.services.IOperationContext; import org.openanzo.services.IUserRolesExtender; import org.openanzo.services.LDAPDictionary; import org.openanzo.services.ServicesDictionary; import org.openanzo.services.impl.BaseAuthenticationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.novell.ldap.LDAPAttribute; import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPEntry; import com.novell.ldap.LDAPException; import com.novell.ldap.LDAPJSSESecureSocketFactory; import com.novell.ldap.LDAPReferralException; import com.novell.ldap.LDAPSearchConstraints; import com.novell.ldap.LDAPSearchResults; /** * Ldap authentication providers an example that authenticates userid and password against an ldap store, and returns the a value from ldap that maps to an Anzo * id * * @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>) * */ class LdapAuthenticationProvider extends BaseAuthenticationService { private static final Logger log = LoggerFactory.getLogger(LdapAuthenticationProvider.class); private String ldapHost = null; private Integer ldapPort = null; /** Basedn for ldap search */ private String userBaseDN = "ou=users,dc=openanzo,dc=org"; private String roleBaseDN = "ou=groups,dc=openanzo,dc=org"; private String ldapPrefix = "ldap://"; private String rolesSearchTemplate = "(&(member={0})(objectclass=groupOfNames))"; private MessageFormat rolesSearchTemplateFormat = null; private String userSearchTemplate = "(uid={0})"; private MessageFormat userSearchTemplateFormat = null; /** LDAP attribute that maps to user's id in server, if null DN is converted to URI */ private String uidIdAttribute = null; /** LDAP administrator userId */ private String ldapAdministratorDN = null; /** LDAP administrator password */ private String ldapAdministratorPassword = null; private String serviceUserName = null; private String servicePassword = null; private boolean anonymousUserEnabled = false; private String keystoreFile = null; private String keystorePassword = null; private String keystoreType = null; private String truststoreFile = null; private String truststorePassword = null; private String truststoreType = null; private LdapConnectionManager connectionManager = null; private Set<URI> sysadminRoles = new HashSet<URI>(); private Dictionary<? extends Object, ? extends Object> configProperties = null; private static int MAX_RETRIES = 10; private boolean connecting = false; /** * Determines whether to try to connect to LDAP. If true, the system won't connect to an LDAP server. This is used mainly for testing to avoid making * connections that will need to timeout. */ private boolean offline = false; private boolean useSSL = false; private LDAPJSSESecureSocketFactory ssf; private ReentrantReadWriteLock resetLock = new ReentrantReadWriteLock(); static final String KEY_OFFLINE = "org.openanzo.security.ldap.offline"; static final LDAPSearchConstraints defaultConstraints = new LDAPSearchConstraints(); static { defaultConstraints.setServerTimeLimit(60); } static final LDAPSearchConstraints userConstraints = new LDAPSearchConstraints(); static { userConstraints.setMaxResults(2); userConstraints.setServerTimeLimit(60); userConstraints.setReferralFollowing(false); userConstraints.setHopLimit(0); } /** * Create new LdapAuthenticationProvider with provided configuration properties */ protected LdapAuthenticationProvider(Dictionary<? extends Object, ? extends Object> configProperties) { this.configProperties = configProperties; } public String getName() { return "service=LdapAuthenticationProvider"; } public String getDescription() { return "Ldap Authentication Provider"; } public void start() throws AnzoException { String rolesSearchTemplate = LDAPAuthDictionary.getRolesSearch(configProperties); if (rolesSearchTemplate != null) { this.rolesSearchTemplate = rolesSearchTemplate; } String userSearchTemplate = LDAPAuthDictionary.getUserSearch(configProperties); if (userSearchTemplate != null) { this.userSearchTemplate = userSearchTemplate; } String uidIdAttribute = LDAPAuthDictionary.getUserIdAttribute(configProperties); if (uidIdAttribute != null && uidIdAttribute.length() > 0) { this.uidIdAttribute = uidIdAttribute; } String userBaseDN = LDAPAuthDictionary.getUserBaseDN(configProperties); if (userBaseDN != null) { this.userBaseDN = userBaseDN; } String roleBaseDN = LDAPAuthDictionary.getRoleBaseDN(configProperties); if (roleBaseDN != null) { this.roleBaseDN = roleBaseDN; } String sysadminRolesIn = LDAPAuthDictionary.getSysadminRole(configProperties); StringTokenizer st = new StringTokenizer(sysadminRolesIn, "|"); while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.startsWith(ldapPrefix)) { sysadminRoles.add(Constants.valueFactory.createURI(token)); } else { if (token.endsWith(roleBaseDN)) { sysadminRoles.add(dnToUri(token)); } else { sysadminRoles.add(dnToUri(token + "," + roleBaseDN)); } } } Boolean useSSL = LDAPDictionary.getUseSSL(configProperties); if (useSSL != null) { this.useSSL = useSSL.booleanValue(); } Boolean anonymousEnabled = LDAPAuthDictionary.getAnonymousAccessEnabled(configProperties); this.anonymousUserEnabled = anonymousEnabled == null ? false : anonymousEnabled; this.serviceUserName = ServicesDictionary.getUser(configProperties, null); this.servicePassword = ServicesDictionary.getPassword(configProperties, null); this.ldapHost = LDAPDictionary.getHost(configProperties, null); this.ldapPort = LDAPDictionary.getPort(configProperties, null); this.ldapAdministratorDN = LDAPDictionary.getLdapServerUser(configProperties); this.ldapAdministratorPassword = LDAPDictionary.getLdapServerPassword(configProperties); keystoreFile = KeyStoreDictionary.getKeyFileLocation(configProperties); keystorePassword = KeyStoreDictionary.getKeyPassword(configProperties); keystoreType = KeyStoreDictionary.getKeystoreType(configProperties); truststoreFile = KeyStoreDictionary.getClientTrustFileLocation(configProperties); truststorePassword = KeyStoreDictionary.getClientTrustPassword(configProperties); truststoreType = KeyStoreDictionary.getClientTruststoreType(configProperties); Object offlineObj = configProperties.get(KEY_OFFLINE); if (offlineObj != null && offlineObj.equals("true")) { this.offline = true; } initialize(); } void close() throws AnzoException { disconnect(); } private void connect() throws AnzoException { synchronized (this) { if (offline) { // Don't connect in offline mode throw new AnzoException(ExceptionConstants.SERVER.LDAP_ERROR); } if (connectionManager == null) { AnzoException lastThrownException = null; if (connecting) { try { this.wait(10000); } catch (InterruptedException ie) { if (log.isInfoEnabled()) { log.info(LogUtils.LIFECYCLE_MARKER, "Connecting to LDAP interrupted", ie); } return; } } else { connecting = true; try { int retries = 0; while (retries < MAX_RETRIES) { try { LDAPConnection connection = bindUser(ldapAdministratorDN, ldapAdministratorPassword); connection.disconnect(); connectionManager = new LdapConnectionManager(ldapAdministratorDN, ldapAdministratorPassword, ldapHost, ldapPort, useSSL, keystoreFile, keystorePassword, keystoreType, truststoreFile, truststorePassword, truststoreType); break; } catch (Exception ae) { if (log.isInfoEnabled()) { log.info(LogUtils.LIFECYCLE_MARKER, "Retrying connection to ldap server:" + retries, ae); } if (ae instanceof AnzoException) lastThrownException = (AnzoException) ae; retries++; } try { this.wait((retries * 1000)); } catch (InterruptedException ie) { if (log.isInfoEnabled()) { log.info(LogUtils.LIFECYCLE_MARKER, "Connecting to LDAP interrupted", ie); } return; } } if (connectionManager == null) { if (lastThrownException != null) { log.error(LogUtils.LIFECYCLE_MARKER, "Error connecting to ldap server", lastThrownException); } throw new AnzoException(ExceptionConstants.SERVER.LDAP_ERROR); } } finally { this.notifyAll(); connecting = false; } } } } } private void disconnect() throws AnzoException { if (connectionManager != null) { connectionManager.close(); connectionManager = null; } } private URI dnToUri(String dn) throws AnzoException { try { String uri = ldapPrefix + dn; return Constants.valueFactory.createURI(Utils.encodeLdapUri(uri)); } catch (AnzoRuntimeException are) { throw are.getAnzoException(); } } private LDAPConnection bindUser(String userDN, String password) throws AnzoException, LDAPException { LDAPConnection ctx = null; try { if (this.useSSL) { if (ssf == null) { Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); ssf = new LDAPJSSESecureSocketFactory(Utils.getSSLSocketFactory(keystoreFile, keystorePassword, keystoreType, truststoreFile, truststorePassword, truststoreType)); } ctx = new LDAPConnection(ssf); } else { ctx = new LDAPConnection(); } ctx.connect(ldapHost, ldapPort); ctx.bind(LDAPConnection.LDAP_V3, userDN, password.getBytes("UTF8")); } catch (LDAPException ae) { log.error(LogUtils.SECURITY_MARKER, "LdapException binding user [" + userDN + "] :" + ae.getLDAPErrorMessage(), ae); throw new AnzoException(ExceptionConstants.SERVER.BAD_USER_PASSWORD, ae, userDN); } catch (UnsupportedEncodingException uee) { throw new AnzoException(ExceptionConstants.IO.ENCODING_ERROR, uee); } return ctx; } private UserResult searchForUser(LDAPConnection connection, String userName) throws AnzoException { LDAPConnection ctx = null; LDAPSearchResults results = null; String uniqueId = null; String authenticateDN = ""; try { String resultAttributes[] = (uidIdAttribute != null) ? new String[] { uidIdAttribute } : null; String filter = userSearchTemplateFormat.format(new String[] { userName }); if (connection == null) { ctx = connectionManager.getConnection(); } else { ctx = connection; } results = ctx.search(userBaseDN, LDAPConnection.SCOPE_SUB, filter, resultAttributes, false, userConstraints); if (results.hasMore()) { LDAPEntry searchResult = results.next(); if (results.hasMore()) { try { results.next(); throw new AnzoException(ExceptionConstants.SERVER.USERID_MULTIPLE_USERS, userName); } catch (LDAPReferralException lre) { log.debug(LogUtils.SECURITY_MARKER, "Error dereferencing referrel", lre); } } authenticateDN = searchResult.getDN(); uniqueId = authenticateDN; if (resultAttributes != null) { LDAPAttribute attribute = searchResult.getAttribute(uidIdAttribute); Object value = attribute.getStringValue(); if (value instanceof String) { uniqueId = (String) value; } else { try { byte b[] = (byte[]) value; String s = new String(b, "ISO-8859-1"); uniqueId = s; } catch (UnsupportedEncodingException e) { log.error(LogUtils.SECURITY_MARKER, "Invalid byte encoding for ldap value", e); } } } return new UserResult(authenticateDN, uniqueId); } else { throw new AnzoException(ExceptionConstants.SERVER.UNKNOWN_USER_ERROR, userName); } } catch (LDAPException ne) { log.warn(LogUtils.SECURITY_MARKER, "LdapException searching for user", ne); throw new AnzoException(ExceptionConstants.SERVER.ERROR_SEARCHING_USERS, ne, userName); } finally { if (results != null) { try { if (ctx != null && connection == null) { ctx.disconnect(); } } catch (LDAPException e) { log.warn(LogUtils.SECURITY_MARKER, "Error diconnecting ldap connection", e); } } } } @Override protected AnzoPrincipal getUserPrincipalInternal(IOperationContext context, String userName) throws AnzoException { try { if (userName.equals(serviceUserName)) { HashSet<URI> rolesSet = new HashSet<URI>(); rolesSet.add(Constants.DEFAULT_SYSADMIN); rolesSet.add(Constants.AUTHENTICATED_USERS_ROLE); rolesSet.add(Constants.EVERYONE_ROLE); AnzoPrincipal principal = new AnzoPrincipal(userName, Constants.DEFAULT_SYSADMIN, rolesSet, true, false); return principal; } else if (anonymousUserEnabled && userName.equals(Constants.DEFAULT_ANONYMOUS_USER)) { HashSet<URI> rolesSet = new HashSet<URI>(); rolesSet.add(Constants.DEFAULT_ANONYMOUS); rolesSet.add(Constants.EVERYONE_ROLE); AnzoPrincipal principal = new AnzoPrincipal(userName, Constants.DEFAULT_ANONYMOUS, rolesSet, false, true); return principal; } if (connectionManager == null) { connect(); } LDAPConnection connection = connectionManager.getConnection(); try { UserResult result = searchForUser(connection, userName); ArrayList<String> roles = getRoles(connection, result.authenticateDN); boolean sysAdmin = false; HashSet<URI> rolesSet = new HashSet<URI>(); URI userURI = dnToUri(result.uniqueID); rolesSet.add(userURI); rolesSet.add(Constants.AUTHENTICATED_USERS_ROLE); rolesSet.add(Constants.EVERYONE_ROLE); for (String role : roles) { URI roleURI = dnToUri(role); rolesSet.add(roleURI); if (!sysAdmin && sysadminRoles.contains(roleURI)) { sysAdmin = true; } } for (IUserRolesExtender extender : roleExtenders) { Set<URI> extraRoles = extender.getRolesForUser(context, userURI); if (extraRoles != null) { rolesSet.addAll(extraRoles); } } AnzoPrincipal principal = new AnzoPrincipal(userName, userURI, rolesSet, sysAdmin, false); return principal; } finally { connectionManager.returnConnection(connection); } } catch (AnzoException ae) { log.info(LogUtils.SECURITY_MARKER, "Error getting user pricipal", ae); throw ae; } } @Override protected AnzoPrincipal authenticateUserInternal(IOperationContext context, String userName, String password) throws AnzoException { if (userName.equals(serviceUserName) && password.equals(servicePassword)) { HashSet<URI> rolesSet = new HashSet<URI>(); rolesSet.add(Constants.DEFAULT_SYSADMIN); rolesSet.add(Constants.AUTHENTICATED_USERS_ROLE); rolesSet.add(Constants.EVERYONE_ROLE); AnzoPrincipal principal = new AnzoPrincipal(userName, Constants.DEFAULT_SYSADMIN, rolesSet, true, false); return principal; } else if (anonymousUserEnabled && userName.equals(Constants.DEFAULT_ANONYMOUS_USER)) { HashSet<URI> rolesSet = new HashSet<URI>(); rolesSet.add(Constants.DEFAULT_ANONYMOUS); rolesSet.add(Constants.EVERYONE_ROLE); AnzoPrincipal principal = new AnzoPrincipal(userName, Constants.DEFAULT_ANONYMOUS, rolesSet, false, true); return principal; } if (connectionManager == null) { connect(); } LDAPConnection ctx = null; ArrayList<String> roles = null; LDAPConnection connection = connectionManager.getConnection(); UserResult result = null; try { try { result = searchForUser(connection, userName); } finally { connectionManager.returnConnection(connection); } try { ctx = bindUser(result.authenticateDN, password); roles = getRoles(ctx, result.authenticateDN); } finally { if (ctx != null) { ctx.disconnect(); } } } catch (AnzoException ae) { log.info(LogUtils.SECURITY_MARKER, "Error authentication user", ae); throw ae; } catch (LDAPException ne) { log.error(LogUtils.SECURITY_MARKER, "Ldap exception authentication user [" + userName + "] : " + ne.getLDAPErrorMessage(), ne); throw new AnzoException(ExceptionConstants.SERVER.ERROR_SEARCHING_USERS, userName); } boolean sysAdmin = false; URI userURI = dnToUri(result.uniqueID); HashSet<URI> rolesSet = new HashSet<URI>(); rolesSet.add(userURI); rolesSet.add(Constants.AUTHENTICATED_USERS_ROLE); rolesSet.add(Constants.EVERYONE_ROLE); for (String role : roles) { URI roleURI = dnToUri(role); rolesSet.add(roleURI); if (!sysAdmin && sysadminRoles.contains(roleURI)) { sysAdmin = true; } } for (IUserRolesExtender extender : roleExtenders) { Set<URI> extraRoles = extender.getRolesForUser(context, userURI); if (extraRoles != null) { rolesSet.addAll(extraRoles); } } AnzoPrincipal principal = new AnzoPrincipal(userName, userURI, rolesSet, sysAdmin, false); return principal; } private ArrayList<String> getRoles(LDAPConnection context, String userDn) throws AnzoException { ArrayList<String> list = new ArrayList<String>(); String filter = rolesSearchTemplateFormat.format(new String[] { Utils.escapeDN(userDn) }); try { LDAPSearchResults results = context.search(roleBaseDN, LDAPConnection.SCOPE_SUB, filter, null, false, defaultConstraints); while (results.hasMore()) { try { LDAPEntry result = results.next(); list.add(result.getDN()); } catch (LDAPReferralException rfe) { log.debug(LogUtils.SECURITY_MARKER, "Error dereferencing referrel", rfe); } } } catch (LDAPException ne) { log.error(LogUtils.SECURITY_MARKER, "LdapException searching for user's [" + userDn + "] roles:" + ne.getLDAPErrorMessage(), ne); throw new AnzoException(ExceptionConstants.SERVER.ERROR_SEARCHING_USERS, ne, userDn); } return list; } void initialize() throws AnzoException { rolesSearchTemplateFormat = new MessageFormat(rolesSearchTemplate); userSearchTemplateFormat = new MessageFormat(userSearchTemplate); } static class UserResult { String authenticateDN = null; String uniqueID = null; UserResult(String authenticateDN, String uniqueID) { this.authenticateDN = authenticateDN; this.uniqueID = uniqueID; } } public ReentrantReadWriteLock getLockProvider() { return resetLock; } }