Skip to content

Commit

Permalink
Merge pull request #18 from FrendsPlatform/issue-16
Browse files Browse the repository at this point in the history
LDAP.AddUserToGroups - Fixed issue with checking user in groups
  • Loading branch information
Svenskapojkarna authored Mar 4, 2024
2 parents 1bcc816 + b23a142 commit 21e42e0
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 69 deletions.
4 changes: 4 additions & 0 deletions Frends.LDAP.AddUserToGroups/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [1.0.1] - 2024-03-01
### Fixed
- Fixed issue with UserExistsAction parameter when AD returned another error message by adding method to actually check if the user is in the group.

## [1.0.0] - 2022-10-07
### Added
- Initial implementation
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Frends.LDAP.AddUserToGroups\Frends.LDAP.AddUserToGroups.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:Field names should not begin with underscore", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.LDAP.AddUserToGroups.Tests")]
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Frends.LDAP.AddUserToGroups.Tests;

using NUnit.Framework;
using Frends.LDAP.AddUserToGroups.Definitions;
using Novell.Directory.Ldap;
namespace Frends.LDAP.AddUserToGroups.Tests;

[TestClass]
[TestFixture]
public class UnitTests
{
/*
Expand All @@ -14,21 +15,15 @@ LDAP server to docker.
private readonly int _port = 10389;
private readonly string? _user = "uid=admin,ou=system";
private readonly string? _pw = "secret";
private readonly string _path = "ou=users,dc=wimpi,dc=net";
private readonly string? _groupDn = "cn=admin,ou=roles,dc=wimpi,dc=net";
private readonly string _testUserDn = "CN=Test User,ou=users,dc=wimpi,dc=net";

Input? input;
Connection? connection;
private Input? input;
private Connection? connection;

[TestMethod]
public void Update_HandleLDAPError_Test()
[SetUp]
public void SetUp()
{
input = new()
{
UserDistinguishedName = "CN=Common Name,CN=Users,DC=Example,DC=Com",
GroupDistinguishedName = "CN=Admins,DC=Example,DC=Com",
UserExistsAction = UserExistsAction.Throw
};
connection = new()
{
Host = _host,
Expand All @@ -39,57 +34,133 @@ public void Update_HandleLDAPError_Test()
TLS = false,
};

var ex = Assert.ThrowsException<Exception>(() => LDAP.AddUserToGroups(input, connection));
CreateTestUser(_testUserDn);
}

[TearDown]
public void Teardown()
{
DeleteTestUsers(_testUserDn, "CN=admin,ou=roles,dc=wimpi,dc=net");
}

[Test]
public void Update_HandleLDAPError_Test()
{
input = new()
{
UserDistinguishedName = "CN=Common Name,CN=Users,DC=Example,DC=Com",
GroupDistinguishedName = "CN=Admins,DC=Example,DC=Com",
UserExistsAction = UserExistsAction.Throw,
};

var ex = Assert.Throws<Exception>(() => LDAP.AddUserToGroups(input, connection, default));
Assert.IsTrue(ex.Message.Contains("No Such Object"));
}

[TestMethod]
[Test]
public void AddUserToGroups_Test()
{
var tuser = "Tes Tuser" + Guid.NewGuid().ToString();
var dn = $"CN={tuser},ou=users,dc=wimpi,dc=net";
CreateTestUsers(tuser);
input = new()
{
UserDistinguishedName = _testUserDn,
GroupDistinguishedName = _groupDn,
UserExistsAction = UserExistsAction.Throw,
};

var result = LDAP.AddUserToGroups(input, connection, default);
Assert.IsTrue(result.Success);
}

[Test]
public void AddUserToGroups_TestWithUserExisting()
{
input = new()
{
UserDistinguishedName = dn,
GroupDistinguishedName = _groupDn
UserDistinguishedName = _testUserDn,
GroupDistinguishedName = _groupDn,
UserExistsAction = UserExistsAction.Throw,
};
connection = new()

var result = LDAP.AddUserToGroups(input, connection, default);
Assert.IsTrue(result.Success);

var ex = Assert.Throws<Exception>(() => LDAP.AddUserToGroups(input, connection, default));
Assert.AreEqual("AddUserToGroups LDAP error: Attribute Or Value Exists", ex.Message);
}

[Test]
public void AddUserToGroups_TestWithUserExistingWithSkip()
{
input = new()
{
Host = _host,
User = _user,
Password = _pw,
SecureSocketLayer = false,
Port = _port,
TLS = false,
UserDistinguishedName = _testUserDn,
GroupDistinguishedName = _groupDn,
UserExistsAction = UserExistsAction.Skip,
};

var result = LDAP.AddUserToGroups(input, connection);
Assert.IsTrue(result.Success.Equals(true));
var result = LDAP.AddUserToGroups(input, connection, default);
Assert.IsTrue(result.Success);

input.UserExistsAction = UserExistsAction.Skip;

result = LDAP.AddUserToGroups(input, connection, default);
Assert.IsFalse(result.Success);
}

public void CreateTestUsers(string tuser)
private void CreateTestUser(string userDn)
{
try
using LdapConnection conn = new()
{
LdapConnection conn = new();
conn.Connect(_host, _port);
conn.Bind(_user, _pw);

var attributeSet = new LdapAttributeSet();
attributeSet.Add(new LdapAttribute("objectclass", "user"));
attributeSet.Add(new LdapAttribute("cn", tuser));
attributeSet.Add(new LdapAttribute("givenname", "Tes"));
attributeSet.Add(new LdapAttribute("sn", tuser.Split(' ', 1)));

var entry = $"CN={tuser},{_path}";
LdapEntry newEntry = new(entry, attributeSet);
conn.Add(newEntry);
conn.Disconnect();
}
catch (Exception)
SecureSocketLayer = false,
};
conn.Connect(_host, _port);
conn.Bind(_user, _pw);

var attributeSet = new LdapAttributeSet
{
new LdapAttribute("objectclass", "inetOrgPerson"),
new LdapAttribute("cn", "Test User"),
new LdapAttribute("givenname", "Test"),
new LdapAttribute("sn", "User"),
};

LdapEntry newEntry = new(userDn, attributeSet);
conn.Add(newEntry);
conn.Disconnect();
}

private void DeleteTestUsers(string userDn, string groupDn)
{
using LdapConnection conn = new();
conn.Connect(_host, _port);
conn.Bind(_user, _pw);

ILdapSearchResults searchResults = conn.Search(
groupDn,
LdapConnection.ScopeSub,
"(objectClass=*)",
null,
false);

LdapEntry groupEntry = searchResults.Next();

var remove = false;

LdapAttribute memberAttr = groupEntry.GetAttribute("member");
var currentMembers = memberAttr.StringValueArray;
if (currentMembers.Where(e => e == userDn).Any())
remove = true;

if (remove)
{
// Remove the user from the group
var mod = new LdapModification(LdapModification.Delete, new LdapAttribute("member", userDn));
conn.Modify(groupDn, mod);
}

conn.Delete(userDn);

// Disconnect from the LDAP server
conn.Disconnect();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.ComponentModel;
using Novell.Directory.Ldap;
using System;
using System.Threading;
using System.Linq;

namespace Frends.LDAP.AddUserToGroups;

Expand All @@ -16,36 +18,39 @@ public class LDAP
/// </summary>
/// <param name="input">Input parameters.</param>
/// <param name="connection">Connection parameters.</param>
/// <returns>Object { bool Success, string Error, string CommonName, string Path }</returns>
public static Result AddUserToGroups([PropertyTab] Input input, [PropertyTab] Connection connection)
/// <param name="cancellationToken">Cancellation token given by Frends.</param>
/// <returns>Object { bool Success, string Error, string UserDistinguishedName, string GroupDistinguishedName }</returns>
public static Result AddUserToGroups([PropertyTab] Input input, [PropertyTab] Connection connection, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(connection.Host) || string.IsNullOrWhiteSpace(connection.User) || string.IsNullOrWhiteSpace(connection.Password))
throw new Exception("AddUserToGroups error: Connection parameters missing.");

LdapConnection conn = new();
using LdapConnection conn = new();

try
{
var defaultPort = connection.SecureSocketLayer ? 636 : 389;

conn.SecureSocketLayer = connection.SecureSocketLayer;
conn.Connect(connection.Host, connection.Port == 0 ? defaultPort : connection.Port);
if (connection.TLS) conn.StartTls();
if (connection.TLS)
conn.StartTls();
conn.Bind(connection.User, connection.Password);

LdapModification[] mods = new LdapModification[1];
var member = new LdapAttribute("member", input.UserDistinguishedName);
mods[0] = new LdapModification(LdapModification.Add, member);
conn.Modify(input.GroupDistinguishedName, mods);
LdapModification[] mods = new LdapModification[1];
var member = new LdapAttribute("member", input.UserDistinguishedName);
mods[0] = new LdapModification(LdapModification.Add, member);

return new Result(true, null, input.UserDistinguishedName, input.GroupDistinguishedName);
if (UserExistsInGroup(conn, input.UserDistinguishedName, input.GroupDistinguishedName, cancellationToken) && input.UserExistsAction.Equals(UserExistsAction.Skip))
return new Result(false, "AddUserToGroups LDAP error: User already exists in the group.", input.UserDistinguishedName, input.GroupDistinguishedName);

conn.Modify(input.GroupDistinguishedName, mods);

return new Result(true, null, input.UserDistinguishedName, input.GroupDistinguishedName);
}
catch (LdapException ex)
{
if (ex.Message.Equals("Attribute Or Value Exists") && input.UserExistsAction.Equals(UserExistsAction.Skip))
return new Result(false, ex.Message, input.UserDistinguishedName, input.GroupDistinguishedName);
else
throw new Exception($"AddUserToGroups LDAP error: {ex.Message}");
throw new Exception($"AddUserToGroups LDAP error: {ex.Message}");
}
catch (Exception ex)
{
Expand All @@ -57,4 +62,40 @@ public static Result AddUserToGroups([PropertyTab] Input input, [PropertyTab] Co
conn.Disconnect();
}
}

private static bool UserExistsInGroup(LdapConnection connection, string userDn, string groupDn, CancellationToken cancellationToken)
{
// Search for the user's groups
ILdapSearchResults searchResults = connection.Search(
groupDn,
LdapConnection.ScopeSub,
"(objectClass=*)",
null,
false);

// Check if the user is a member of the specified group
while (searchResults.HasMore())
{
cancellationToken.ThrowIfCancellationRequested();
LdapEntry entry;
try
{
entry = searchResults.Next();
}
catch (LdapException)
{
continue;
}

if (entry != null)
{
LdapAttribute memberAttr = entry.GetAttribute("member");
var currentMembers = memberAttr.StringValueArray;
if (currentMembers.Where(e => e == userDn).Any())
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ public class Input
/// </summary>
/// <example>UserExistsAction.Throw</example>
[DefaultValue(UserExistsAction.Throw)]
public UserExistsAction UserExistsAction { get; set; }
public UserExistsAction UserExistsAction { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<Authors>Frends</Authors>
<Copyright>Frends</Copyright>
<Company>Frends</Company>
Expand All @@ -20,8 +20,16 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(MSBuildProjectName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.6.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>
</Project>
Loading

0 comments on commit 21e42e0

Please sign in to comment.