The company I work for has a product that uses Active Directory to enable our product's security features using a library that includes DirectoryEntry and DirectorySearcher components.
If someone is a member of group FOO
, they have standard access. If they are a member of FOO-ADMIN
, they have Admin rights.
We have a potential client who does not use Active Directory. They have an Apache server running their LDAP, and they have provided this screenshot.
Above, it looks like I would need to connect to a domain of xxx.xxx.5.101:389 (i.e. DirectoryEntry("LDAP://xxx.xxx.5.101:389")), but how does that "DN or user" field fit with the password?
Are Active Directory components able to do LDAP authentication on an Apache system, or would the code need completely different controls?
Here is some crude code that I have put together:
/// <summary>
/// Untested Method
/// </summary>
/// <param name="hostIp">String (EX: xxx.xxx.5.101)</param>
/// <param name="port">Int (EX: 389)</param>
/// <param name="user">String (EX: cn=danibla,ou=sysdata,ou=townhall,o=toh)</param>
/// <param name="password">String - provided password</param>
/// <param name="groupsLike">String (EX: find all groups like FOO)</param>
/// <returns>String[] array of matching membership groups</returns>
public static String[] GetMemberships(String hostIp, int port, String user, String password, String groupsLike)
{
var results = new List<String>();
var path = String.Format("LDAP://{0}:{1}", hostIp, port);
using (var entry = new DirectoryEntry(path, user, password))
{
using (var search = new DirectorySearcher(entry, String.Format("(CN={0}*)", groupsLike)))
{
var expression = new Regex("CN=([^,]*),", RegexOptions.Compiled & RegexOptions.IgnoreCase);
foreach (SearchResult item in search.FindAll())
{
var match = expression.Match(item.Path);
var name = match.Groups[1].Value;
if (name.StartsWith(groupsLike, StringComparison.OrdinalIgnoreCase))
{
if (!results.Contains(name))
{
results.Add(name);
}
}
}
}
}
return results.ToArray();
}
I am bothered by the "path like" parameters they pass in for the "DN or user" field, particularly when it shows them providing a password with it.
We do not have an Apache environment to test this on. Our company does not want me going to this client with a lot of unnecessary questions.
UPDATE:
Still need a way to do this. Starting a bounty. Maybe bringing some attention to this will get me a solution.
In the screenshot above, the value for username
in the code was both cn-mikead,ou=sysdata,ou=townhall,o=toh
and separately mikead
, both with the same COM Exception at the call to FindAll()
.
Here is the code I have now.
public static String[] Groups(String domain, int port, String username, int authenticationValue, String startsWith)
{
String name;
var results = new List<String>();
var ldapPath =
String.IsNullOrEmpty(domain) ? null :
(0 < port) ?
String.Format("LDAP://DC={0}:{1}", domain, port) :
String.Format("LDAP://DC={0}", domain);
using (var entry = new DirectoryEntry(String.Format("WinNT://{0}/{1}", Environment.UserDomainName, username)))
{
name = String.Format("{0}", entry.Properties["fullName"].Value);
}
var filter = String.Format("(CN={0}", name);
var expression = new Regex("CN=([^,]*),", RegexOptions.Compiled & RegexOptions.IgnoreCase);
using (var entry = new DirectoryEntry(ldapPath))
{
entry.AuthenticationType = (AuthenticationTypes)authenticationValue;
using (var search = new DirectorySearcher(entry) { Filter = filter })
{
search.PropertiesToLoad.Add("memberOf");
try
{
foreach (SearchResult item in search.FindAll())
{
foreach (var property in item.Properties["memberOf"])
{
var name = expression.Match(String.Format("{0}", property)).Groups[1].Value;
if (name.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase))
{
if (!results.Contains(name))
{
results.Add(name);
}
}
}
}
}
catch (Exception err)
{
LogError("Groups", err);
}
}
}
return results.ToArray();
}