Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented a new Generic LDAP user directory #178

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ namespace Inedo.Extensions.UserDirectories;
internal sealed class DirectoryServicesLdapClient : LdapClient
{
private LdapConnection connection;
private readonly AuthType? authType;
private readonly string[] attributes;

/// <inheritdoc />
public DirectoryServicesLdapClient(AuthType? authType = null, string[] attributes = null)
{
this.authType = authType;
this.attributes = attributes;
}

public override void Connect(string server, int? port, bool ldaps, bool bypassSslCertificate)
{
this.connection = new LdapConnection(new LdapDirectoryIdentifier(server, port ?? (ldaps ? 636 : 389)));
if (authType != null)
{
connection.AuthType = authType.Value;
}

if (ldaps)
{
this.connection.SessionOptions.SecureSocketLayer = true;
Expand All @@ -25,6 +39,11 @@ public override void Bind(NetworkCredential credentials)
public override IEnumerable<LdapClientEntry> Search(string distinguishedName, string filter, LdapClientSearchScope scope)
{
var request = new SearchRequest(distinguishedName, filter, (SearchScope)scope);
if (attributes != null)
{
request.Attributes.AddRange(attributes);
}

var response = this.connection.SendRequest(request);

if (response is SearchResponse sr)
Expand Down Expand Up @@ -55,9 +74,8 @@ public override string GetPropertyValue(string propertyName)

return propertyCollection[0]?.ToString() ?? string.Empty;
}
public override ISet<string> ExtractGroupNames(string memberOfPropertyName = null)
public override ISet<string> ExtractGroupNames(string memberOfPropertyName = "memberof", string groupNamePropertyName = "CN", bool includeDomainPath = false)
{

Logger.Log(MessageLevel.Debug, "Begin ExtractGroupNames", "AD User Directory");
var groups = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
Expand Down Expand Up @@ -86,12 +104,20 @@ public override ISet<string> ExtractGroupNames(string memberOfPropertyName = nul
if (!string.IsNullOrWhiteSpace(memberOf))
{
var groupNames = from part in memberOf.Split(',')
where part.StartsWith("CN=", StringComparison.OrdinalIgnoreCase)
let name = part["CN=".Length..]
where part.StartsWith($"{groupNamePropertyName}=", StringComparison.OrdinalIgnoreCase)
let name = part[$"{groupNamePropertyName}=".Length..]
where !string.IsNullOrWhiteSpace(name)
select name;

groups.UnionWith(groupNames);
foreach (var groupName in groupNames)
{
string groupNameToAdd = groupName;
if (includeDomainPath)
{
groupNameToAdd = $"{groupName}@{GetDomainPath(memberOf)}";
}

groups.Add(groupNameToAdd);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Inedo.Extensibility.UserDirectories;

namespace Inedo.Extensions.UserDirectories.GenericLdap;

public partial class GenericLdapUserDirectory
{
private sealed class GenericLdapGroup : IUserDirectoryGroup, IEquatable<GenericLdapGroup>
{
private readonly GroupId groupId;
private readonly GenericLdapUserDirectory directory;
private readonly HashSet<string> isMemberOfGroupCache = new(StringComparer.OrdinalIgnoreCase);

public Lazy<ISet<string>> Groups { get; }

public GenericLdapGroup(GenericLdapUserDirectory directory, LdapClientEntry entry)
{
this.directory = directory;
groupId = new GroupId(entry.GetPropertyValue(directory.GroupNameAttributeName), entry.GetDomainPath());
groupId.DistinguishedName = entry.DistinguishedName;

Groups = new Lazy<ISet<string>>(() => GetGroups(directory, entry), LazyThreadSafetyMode.ExecutionAndPublication);
}

public string Name => groupId.ToFullyQualifiedName();
public string DisplayName => groupId.Principal;

public bool IsMemberOfGroup(string groupName)
{
Logger.Log(MessageLevel.Debug, "Begin GenericLdapGroup IsMemberOfGroup", "Generic LDAP User Directory");
ArgumentNullException.ThrowIfNull(groupName);
if (isMemberOfGroupCache.Contains(groupName))
{
Logger.Log(MessageLevel.Debug, "End GenericLdapGroup IsMemberOfGroup", "Generic LDAP User Directory");
return true;
}

if (Groups.Value.Contains(groupName))
{
Logger.Log(MessageLevel.Debug, "End GenericLdapGroup IsMemberOfGroup", "Generic LDAP User Directory");
isMemberOfGroupCache.Add(groupName);
return true;
}

Logger.Log(MessageLevel.Debug, "End GenericLdapGroup IsMemberOfGroup", "Generic LDAP User Directory");
return false;
}

public IEnumerable<IUserDirectoryUser> GetMemberUsers()
{
Logger.Log(MessageLevel.Debug, "Begin GenericLdapGroup GetMembers", "Generic LDAP User Directory");
if (directory.GroupSearchType != GroupSearchType.RecursiveSearchActiveDirectory)
{
var memberUsers = directory.FindUsers($"({directory.GroupMemberOfAttributeName}={groupId.DistinguishedName})");
foreach (var memberUser in memberUsers)
{
yield return memberUser;
}

if (directory.GroupSearchType == GroupSearchType.RecursiveSearch)
{
var memberGroups = directory.FindGroups($"({directory.GroupMemberOfAttributeName}={groupId.DistinguishedName})").Cast<GenericLdapGroup>();
foreach (var memberGroup in memberGroups)
{
foreach (var memberUser in memberGroup.GetMemberUsers())
{
yield return memberUser;
}
}
}
}
else
{
foreach (var userEntry in directory.FindUsers($"memberOf:1.2.840.113556.1.4.1941:={groupId.DistinguishedName}"))
{
yield return userEntry;
}
}
Logger.Log(MessageLevel.Debug, "End GenericLdapGroup GetMembers", "Generic LDAP User Directory");
}

public bool Equals(GenericLdapGroup other) => groupId.Equals(other?.groupId);
public bool Equals(IUserDirectoryGroup other) => Equals(other as GenericLdapGroup);
public bool Equals(IUserDirectoryPrincipal other) => Equals(other as GenericLdapGroup);
public override bool Equals(object obj) => Equals(obj as GenericLdapGroup);
public override int GetHashCode() => groupId.GetHashCode();
public override string ToString() => groupId.Principal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Inedo.Extensibility.UserDirectories;

namespace Inedo.Extensions.UserDirectories.GenericLdap;

public partial class GenericLdapUserDirectory
{
internal sealed class GenericLdapUser : IUserDirectoryUser, IEquatable<GenericLdapUser>
{
private readonly GenericLdapUserDirectory directory;
private readonly UserId userId;
private readonly HashSet<string> isMemberOfGroupCache = new(StringComparer.OrdinalIgnoreCase);

public Lazy<ISet<string>> Groups { get; }

public GenericLdapUser(GenericLdapUserDirectory directory, LdapClientEntry entry)
{
this.directory = directory;
userId = new UserId(entry.GetPropertyValue(directory.UserNameAttributeName), entry.GetDomainPath());
userId.DistinguishedName = entry.DistinguishedName;
DisplayName = AH.CoalesceString(entry.GetPropertyValue(directory.UserDisplayNameAttributeName), userId.Principal);
EmailAddress = entry.GetPropertyValue(directory.EmailAddressAttributeName);
DistinguishedName = entry.DistinguishedName;
Groups = new Lazy<ISet<string>>(() => GetGroups(directory, entry), LazyThreadSafetyMode.ExecutionAndPublication);
}

string IUserDirectoryPrincipal.Name => userId.ToFullyQualifiedName();
public string EmailAddress { get; }
public string DisplayName { get; }
public string DistinguishedName { get; }

public bool IsMemberOfGroup(string groupName)
{
Logger.Log(MessageLevel.Debug, "Begin GenericLdapUser IsMemberOfGroup", "Generic LDAP User Directory");
ArgumentNullException.ThrowIfNull(groupName);
if (isMemberOfGroupCache.Contains(groupName))
{
Logger.Log(MessageLevel.Debug, "End GenericLdapUser IsMemberOfGroup", "Generic LDAP User Directory");
return true;
}

if (Groups.Value.Contains(groupName))
{
Logger.Log(MessageLevel.Debug, "End GenericLdapUser IsMemberOfGroup", "Generic LDAP User Directory");
isMemberOfGroupCache.Add(groupName);
return true;
}

Logger.Log(MessageLevel.Debug, "End GenericLdapUser IsMemberOfGroup", "Generic LDAP User Directory");
return false;
}

public bool Equals(GenericLdapUser other) => userId.Equals(other?.userId);
public bool Equals(IUserDirectoryUser other) => Equals(other as GenericLdapUser);
public bool Equals(IUserDirectoryPrincipal other) => Equals(other as GenericLdapUser);
public override bool Equals(object obj) => Equals(obj as GenericLdapUser);
public override int GetHashCode() => userId.GetHashCode();
}
}
Loading