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?
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();
}
Apache can run LDAP, my advice would be to make sure your client has LDAP configured properly on their server. This could be done in the httpd.conf on their server