You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lenya.apache.org by jw...@apache.org on 2004/11/14 16:25:45 UTC
svn commit: rev 65598 - lenya/branches/BRANCH_1_2_X/src/java/org/apache/lenya/ac/ldap
Author: jwkaltz
Date: Sun Nov 14 07:25:44 2004
New Revision: 65598
Modified:
lenya/branches/BRANCH_1_2_X/src/java/org/apache/lenya/ac/ldap/LDAPUser.java
Log:
Removed hard-coding of ou=People branch, added possibility to search a
subtree for a user, and added option to support MS Active Directory
Modified: lenya/branches/BRANCH_1_2_X/src/java/org/apache/lenya/ac/ldap/LDAPUser.java
==============================================================================
--- lenya/branches/BRANCH_1_2_X/src/java/org/apache/lenya/ac/ldap/LDAPUser.java (original)
+++ lenya/branches/BRANCH_1_2_X/src/java/org/apache/lenya/ac/ldap/LDAPUser.java Sun Nov 14 07:25:44 2004
@@ -21,14 +21,17 @@
import java.util.Hashtable;
import java.util.Properties;
+import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapContext;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
@@ -41,24 +44,37 @@
/**
* LDAP user.
- * @version $Id: LDAPUser.java,v 1.6 2004/08/16 16:36:57 andreas Exp $
+ * @version $Id$
*/
public class LDAPUser extends FileUser {
private static Properties defaultProperties = null;
private static Category log = Category.getInstance(LDAPUser.class);
public static final String LDAP_ID = "ldapid";
- private static String PROVIDER_URL = "provider-url";
- private static String MGR_DN = "mgr-dn";
- private static String MGR_PW = "mgr-pw";
- private static String PARTIAL_USER_DN = "partial-user-dn";
- private static String KEY_STORE = "key-store";
- private static String SECURITY_PROTOCOL = "security-protocol";
- private static String SECURITY_AUTHENTICATION = "security-authentication";
- private String ldapId;
+ private static String LDAP_PROPERTIES_FILE = "ldap.properties";
+ private static String PROVIDER_URL_PROP = "provider-url";
+ private static String MGR_DN_PROP = "mgr-dn";
+ private static String MGR_PW_PROP = "mgr-pw";
+ private static String KEY_STORE_PROP = "key-store";
+ private static String SECURITY_PROTOCOL_PROP = "security-protocol";
+ private static String SECURITY_AUTHENTICATION_PROP = "security-authentication";
+ private static String USR_ATTR_PROP = "usr-attr";
+ private static String USR_ATTR_DEFAULT = "uid";
+ private static String USR_NAME_ATTR_PROP = "usr-name-attr";
+ private static String USR_NAME_ATTR_DEFAULT = "gecos";
+ private static String USR_BRANCH_PROP = "usr-branch";
+ private static String USR_BRANCH_DEFAULT = "ou=People";
+ private static String USR_AUTH_TYPE_PROP = "usr-authentication";
+ private static String USR_AUTH_TYPE_DEFAULT = "simple";
+ private static String BASE_DN_PROP = "base-dn";
+ private static String DOMAIN_NAME_PROP = "domain-name";
+ private String ldapId;
private String ldapName;
+ // deprecated: for backwards compatibility only !
+ private static String PARTIAL_USER_DN_PROP = "partial-user-dn";
+
/**
* Creates a new LDAPUser object.
*/
@@ -105,71 +121,78 @@
/**
* Checks if a user exists.
+ *
* @param ldapId The LDAP id.
- * @return A boolean value.
- * @throws AccessControlException when an error occurs. FIXME: This method does not work.
+ * @return A boolean value indicating whether the user is found in the directory
+ * @throws AccessControlException when an error occurs.
*/
public boolean existsUser(String ldapId) throws AccessControlException {
+ if (log.isDebugEnabled())
+ log.debug("existsUser() checking id " + ldapId);
+
boolean exists = false;
- LdapContext context = null;
try {
readProperties();
+ SearchResult entry = getDirectoryEntry(ldapId);
- context = bind(defaultProperties.getProperty(MGR_DN), defaultProperties
- .getProperty(MGR_PW));
-
- String peopleName = "ou=People";
- Attributes attributes = new BasicAttributes("uid", ldapId);
- NamingEnumeration enumeration = context.search(peopleName, attributes);
+ exists = (entry != null);
- exists = enumeration.hasMoreElements();
} catch (Exception e) {
+ if (log.isDebugEnabled())
+ log.debug("existsUser() for id " + ldapId + " got exception: " + e);
throw new AccessControlException("Exception during search: ", e);
- } finally {
- try {
- if (context != null) {
- close(context);
- }
- } catch (NamingException e) {
- throw new AccessControlException("Closing context failed: ", e);
- }
- }
+ }
+
return exists;
}
/**
* Initializes this user.
+ *
+ * The current (already authenticated) ldapId is queried in the directory,
+ * in order to retrieve additional information, such as the user name.
+ * In current implementation, only the user name is actually retrieved, but
+ * other attributes may be used in the future (such as groups ?)
+ *
+ * TODO: should the code be changed to not throw an exception when something
+ * goes wrong ? After all, it's only used to get additional info for display?
+ * This is a design decision, I'm not sure what's best.
*
* @throws ConfigurationException when something went wrong.
*/
protected void initialize() throws ConfigurationException {
- LdapContext context = null;
+ DirContext context = null;
try {
- readProperties();
+ if (log.isDebugEnabled())
+ log.debug("initialize() getting entry ...");
- String name = null;
- context = bind(defaultProperties.getProperty(MGR_DN), defaultProperties
- .getProperty(MGR_PW));
-
- String[] attrs = new String[1];
- attrs[0] = "gecos"; /* users full name */
-
- String searchString = "uid=" + ldapId + ",ou=People";
- Attributes answer = context.getAttributes(searchString, attrs);
-
- if (answer != null) {
- Attribute attr = answer.get("gecos");
-
- if (attr != null) {
- for (NamingEnumeration enum = attr.getAll(); enum.hasMore(); enum.next()) {
- name = (String) attr.get();
- }
- }
- }
+ SearchResult entry = getDirectoryEntry(ldapId);
+ StringBuffer name = new StringBuffer();
+
+ if (entry != null) {
+ /* users full name */
+ String usrNameAttr =
+ defaultProperties.getProperty(USR_NAME_ATTR_PROP, USR_NAME_ATTR_DEFAULT);
+
+ if (log.isDebugEnabled())
+ log.debug("initialize() got entry, going to look for attribute " + usrNameAttr + " in entry, which is: " + entry);
+
+ Attributes attributes = entry.getAttributes();
+ if (attributes != null) {
+ Attribute userNames = attributes.get(usrNameAttr);
+ if (userNames != null) {
+ for (NamingEnumeration enum = userNames.getAll(); enum.hasMore(); enum.next()) {
+ name.append((String)userNames.get());
+ }
+ }
+ }
+ }
+ ldapName = name.toString();
+ if (log.isDebugEnabled())
+ log.debug("initialize() set name to " + ldapName);
- this.ldapName = name;
} catch (Exception e) {
throw new ConfigurationException("Could not read properties", e);
} finally {
@@ -216,29 +239,39 @@
}
/**
- * (non-Javadoc)
+ * Authenticate a user against the directory.
+ *
+ * The principal to be authenticated is either constructed by use of the
+ * configured properties, or by lookup of this ID in the directory.
+ * This principal then attempts to authenticate against the directory with
+ * the provided password.
*
* @see org.apache.lenya.ac.User#authenticate(java.lang.String)
*/
public boolean authenticate(String password) {
- String principal = "uid=" + getLdapId() + ","
- + defaultProperties.getProperty(PARTIAL_USER_DN);
- Context ctx = null;
-
- if (log.isDebugEnabled()) {
- log.debug("Authenticating with principal [" + principal + "]");
- }
-
- boolean authenticated = false;
+ boolean authenticated = false;
+ String principal = "";
+ Context ctx = null;
try {
- ctx = bind(principal, password);
+ principal = getPrincipal();
+
+ if (log.isDebugEnabled())
+ log.debug("Authenticating with principal [" + principal + "]");
+
+ ctx = bind(principal, password,
+ defaultProperties.getProperty(USR_AUTH_TYPE_PROP,
+ USR_AUTH_TYPE_DEFAULT));
authenticated = true;
close(ctx);
if (log.isDebugEnabled()) {
log.debug("Context closed.");
}
+ } catch (IOException e) {
+ log.warn("authenticate handling IOException, check your setup: " + e);
+ } catch (AuthenticationException e) {
+ log.info("authenticate failed for principal " + principal + ", exception " + e);
} catch (NamingException e) {
// log this failure
// StringWriter writer = new StringWriter();
@@ -249,6 +282,7 @@
}
return authenticated;
+
}
/**
@@ -293,10 +327,12 @@
*
* @param principal the principal string for the LDAP connection
* @param credentials the credentials for the LDAP connection
- * @return a <code>LdapContext</code>
+ * @param authMethod the authentication method
+ * @return a <code>DirContext</code>
* @throws NamingException if there are problems establishing the Ldap connection
*/
- private LdapContext bind(String principal, String credentials) throws NamingException {
+ private DirContext bind(String principal, String credentials,
+ String authMethod) throws NamingException {
log.info("Binding principal: [" + principal + "]");
@@ -304,17 +340,19 @@
System.setProperty("javax.net.ssl.trustStore", getConfigurationDirectory()
.getAbsolutePath()
- + File.separator + defaultProperties.getProperty(KEY_STORE));
+ + File.separator + defaultProperties.getProperty(KEY_STORE_PROP));
env.put(Context.INITIAL_CONTEXT_FACTORY, LdapCtxFactory.class.getName());
- env.put(Context.PROVIDER_URL, defaultProperties.getProperty(PROVIDER_URL));
- env.put(Context.SECURITY_PROTOCOL, defaultProperties.getProperty(SECURITY_PROTOCOL));
- env.put(Context.SECURITY_AUTHENTICATION, defaultProperties
- .getProperty(SECURITY_AUTHENTICATION));
- env.put(Context.SECURITY_PRINCIPAL, principal);
- env.put(Context.SECURITY_CREDENTIALS, credentials);
+ env.put(Context.PROVIDER_URL, defaultProperties.getProperty(PROVIDER_URL_PROP));
+ env.put(Context.SECURITY_PROTOCOL, defaultProperties.getProperty(SECURITY_PROTOCOL_PROP));
+
+ env.put(Context.SECURITY_AUTHENTICATION, authMethod);
+ if (authMethod != null && ! authMethod.equals("none")) {
+ env.put(Context.SECURITY_PRINCIPAL, principal);
+ env.put(Context.SECURITY_CREDENTIALS, credentials);
+ }
- LdapContext ctx = new InitialLdapContext(env, null);
+ DirContext ctx = new InitialLdapContext(env, null);
log.info("Finished binding principal.");
@@ -338,7 +376,7 @@
*/
private void readProperties() throws IOException {
// create and load default properties
- File propertiesFile = new File(getConfigurationDirectory(), "ldap.properties");
+ File propertiesFile = new File(getConfigurationDirectory(), LDAP_PROPERTIES_FILE);
if (defaultProperties == null) {
defaultProperties = new Properties();
@@ -354,4 +392,171 @@
}
}
}
-}
\ No newline at end of file
+
+ /**
+ * Wrapping of the decision whether a recursive search is wanted or not.
+ * Implementation:
+ * If the USR_BRANCH_PROP is present, this is the new style of configuration
+ * (starting Lenya 1.2.2); if it has a value, then a specific branch is wanted:
+ * no recursive search. If the property is present, but has no value,
+ * search recursively.
+ */
+ private boolean isSubtreeSearch() {
+ boolean recurse = false;
+ String usrBranchProp = defaultProperties.getProperty(USR_BRANCH_PROP);
+ if (usrBranchProp != null)
+ if (usrBranchProp.trim().length() == 0)
+ recurse = true;
+
+ return recurse;
+ }
+
+
+ private SearchResult getDirectoryEntry(String userId)
+ throws NamingException, IOException
+ {
+ DirContext context = null;
+ String searchFilter = "";
+ String objectName = "";
+ boolean recursiveSearch;
+ SearchResult result = null;
+
+ try {
+ readProperties();
+
+ context = bind(defaultProperties.getProperty(MGR_DN_PROP),
+ defaultProperties.getProperty(MGR_PW_PROP),
+ defaultProperties.getProperty(SECURITY_AUTHENTICATION_PROP));
+
+ // Get search information and user attribute from properties
+ // provide defaults if not present (backward compatibility)
+ String userAttribute =
+ defaultProperties.getProperty(USR_ATTR_PROP, USR_ATTR_DEFAULT);
+ searchFilter = "(" + userAttribute + "=" + userId + ")";
+ SearchControls scope = new SearchControls();
+ NamingEnumeration results;
+
+ recursiveSearch = isSubtreeSearch();
+ if (recursiveSearch) {
+ scope.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ objectName = defaultProperties.getProperty(PROVIDER_URL_PROP);
+ }
+ else {
+ scope.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ objectName =
+ defaultProperties.getProperty(USR_BRANCH_PROP, USR_BRANCH_DEFAULT);
+ }
+
+ if (log.isDebugEnabled())
+ log.debug("searching object " + objectName + " filtering with " + searchFilter + ", recursive search ? " + recursiveSearch);
+
+ results = context.search(objectName, searchFilter, scope);
+
+ if (results != null && results.hasMore()) {
+ result = (SearchResult)results.next();
+
+ // sanity check
+ if (results.hasMore()) {
+ log.warn("Found more than one entry in the directory for user " + userId + ". You probably should deactivate recursive searches. The first entry was used as a work-around.");
+ }
+
+ }
+ }
+ catch (NamingException e) {
+ if (log.isDebugEnabled())
+ log.debug("NamingException caught when searching on objectName = " + objectName + " and searchFilter=" + searchFilter + ", this exception will be propagated: " + e);
+ throw e;
+ }
+ finally {
+ try {
+ if (context != null) {
+ close(context);
+ }
+ } catch (NamingException e) {
+ log.warn("this should not happen: exception closing context " + e);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Encapsulation of the creation of a principal: we need to distinguish
+ * three cases, in order to support different modes of using a directory.
+ * The first is the use of a domain-name (requirement of MS Active Directory):
+ * if this property is set, this is used to construct the principal.
+ * The second case is where a user-id is somewhere in a domain, but not in a
+ * specific branch: in this case, a subtree search is performed to retrieve
+ * the complete path.
+ * The third case is where a specific branch of the directory is to be used;
+ * this is the case where usr-branch is set to a value. In this case, this branch
+ * is used to construct the principal.
+ */
+ private String getPrincipal() throws IOException, NamingException {
+
+ String principal;
+
+ // 1. Check if domain-name is to be supported
+ String domainProp = defaultProperties.getProperty(DOMAIN_NAME_PROP);
+ if (domainProp != null && domainProp.trim().length() > 0) {
+ principal = domainProp + "\\" + getLdapId();
+ }
+ else {
+ if (isSubtreeSearch()) {
+ // 2. Principal is constructed from directory entry
+ SearchResult entry = getDirectoryEntry(getLdapId());
+ principal = entry.getName();
+ if (entry.isRelative()) {
+ if (principal.length()>0){
+ principal = principal +","+ defaultProperties.getProperty(BASE_DN_PROP);
+ }
+ }
+ }
+ else {
+ // 3. Principal is constructed from properties
+ principal = constructPrincipal(getLdapId());
+ }
+ }
+
+ return principal;
+ }
+
+ /**
+ * Construct the principal for a user, by using the given userId along
+ * with the configured properties.
+ *
+ */
+ private String constructPrincipal(String userId) {
+ StringBuffer principal = new StringBuffer();
+ principal
+ .append(defaultProperties.getProperty(USR_ATTR_PROP, USR_ATTR_DEFAULT))
+ .append("=")
+ .append(userId)
+ .append(",");
+
+ String baseDn = defaultProperties.getProperty(BASE_DN_PROP);
+ if (baseDn != null && baseDn.length() > 0) {
+ // USR_BRANCH_PROP may be empty, so only append when not-empty
+ String usrBranch = defaultProperties.getProperty(USR_BRANCH_PROP);
+ if (usrBranch != null) {
+ if (usrBranch.trim().length() > 0)
+ principal.append(usrBranch).append(",");
+ }
+ else
+ principal.append(USR_BRANCH_DEFAULT).append(",");
+
+ principal.append(defaultProperties.getProperty(BASE_DN_PROP));
+ }
+ else {
+ // try for backwards compatibility of ldap properties
+ log.warn("getPrincipal() read a deprecated format in ldap properties, please update");
+ principal.append(defaultProperties.getProperty(PARTIAL_USER_DN_PROP));
+ }
+
+ if (log.isDebugEnabled())
+ log.debug("getPrincipal() returning " + principal.toString());
+
+ return principal.toString();
+ }
+
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@lenya.apache.org
For additional commands, e-mail: commits-help@lenya.apache.org