Skip to content

Commit d69bede

Browse files
Merge pull request #35 from FrendsPlatform/FSPES-2
Improved LDAP error handling by validating bind status and checking s…
2 parents 96bee50 + a670092 commit d69bede

File tree

6 files changed

+137
-3
lines changed

6 files changed

+137
-3
lines changed

.github/workflows/SearchObjects_build_and_test_on_main.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ jobs:
1313
uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_main.yml@main
1414
with:
1515
workdir: Frends.LDAP.SearchObjects
16-
prebuild_command: docker run -d -i --rm -p 10389:10389 dwimberger/ldap-ad-it
16+
prebuild_command: |
17+
docker run -d -i --rm --name ldap1 -p 10389:10389 dwimberger/ldap-ad-it
18+
docker run -d -i --rm --name ldap2 -p 20389:389 \
19+
-e LDAP_ORGANISATION="Test Org" \
20+
-e LDAP_DOMAIN="example.org" \
21+
-e LDAP_ADMIN_PASSWORD="admin" \
22+
osixia/openldap:1.5.0
23+
24+
echo "Waiting for osixia LDAP to be ready..."
25+
for i in {1..30}; do
26+
if docker logs ldap2 2>&1 | grep -q "slapd starting"; then
27+
echo "osixia LDAP is ready."
28+
break
29+
fi
30+
echo "Still waiting for osixia LDAP ($i)..."
31+
sleep 1
32+
done
33+
34+
echo "Prebuild finished."
1735
secrets:
1836
badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}

.github/workflows/SearchObjects_build_and_test_on_push.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,25 @@ jobs:
1313
uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_test.yml@main
1414
with:
1515
workdir: Frends.LDAP.SearchObjects
16-
prebuild_command: docker run -d -i --rm -p 10389:10389 dwimberger/ldap-ad-it
16+
prebuild_command: |
17+
docker run -d -i --rm --name ldap1 -p 10389:10389 dwimberger/ldap-ad-it
18+
docker run -d -i --rm --name ldap2 -p 20389:389 \
19+
-e LDAP_ORGANISATION="Test Org" \
20+
-e LDAP_DOMAIN="example.org" \
21+
-e LDAP_ADMIN_PASSWORD="admin" \
22+
osixia/openldap:1.5.0
23+
24+
echo "Waiting for osixia LDAP to be ready..."
25+
for i in {1..30}; do
26+
if docker logs ldap2 2>&1 | grep -q "slapd starting"; then
27+
echo "osixia LDAP is ready."
28+
break
29+
fi
30+
echo "Still waiting for osixia LDAP ($i)..."
31+
sleep 1
32+
done
33+
34+
echo "Prebuild finished."
1735
secrets:
1836
badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }}
1937
test_feed_api_key: ${{ secrets.TASKS_TEST_FEED_API_KEY }}

Frends.LDAP.SearchObjects/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [4.1.0] - 2025-06-17
4+
### Added
5+
- Improved LDAP error handling by validating bind status and checking search response codes.
6+
- Block negative values for MsLimit
7+
38
## [4.0.0] - 2025-04-04
49
### Added
510
- [Breaking] - Parameter for PageSize to control how many entries are fetched per page during an LDAP search.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using Frends.LDAP.SearchObjects.Definitions;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Frends.LDAP.SearchObjects.Tests;
10+
11+
[TestClass]
12+
public class UnitTestAnonymousBind
13+
{
14+
/*
15+
Create a simple LDAP server to docker for anonymous bind testing
16+
docker run --rm -p 20389:389 -e LDAP_ORGANISATION="Test Org" -e LDAP_DOMAIN="example.org" -e LDAP_ADMIN_PASSWORD="admin" osixia/openldap:1.5.0
17+
*/
18+
19+
private readonly string? _host = "127.0.0.1";
20+
private readonly int _port = 20389;
21+
private readonly string? _user = "cn=admin,dc=example,dc=org";
22+
private readonly string? _pw = "admin";
23+
private readonly string _path = "dc=example,dc=org";
24+
25+
Connection? connection;
26+
27+
[TestInitialize]
28+
public void Setup()
29+
{
30+
connection = new()
31+
{
32+
Host = _host,
33+
User = _user,
34+
Password = _pw,
35+
SecureSocketLayer = false,
36+
Port = _port,
37+
TLS = false,
38+
LDAPProtocolVersion = LDAPVersion.V3
39+
};
40+
}
41+
42+
[TestMethod]
43+
public void Search_AnonymousBind_ShouldFailAndReturnError()
44+
{
45+
var badConnection = new Connection
46+
{
47+
Host = _host,
48+
Port = _port,
49+
SecureSocketLayer = false,
50+
TLS = false,
51+
LDAPProtocolVersion = LDAPVersion.V3,
52+
AnonymousBind = true,
53+
ThrowExceptionOnError = false
54+
};
55+
56+
var badInput = new Input
57+
{
58+
SearchBase = _path,
59+
Scope = Scopes.ScopeSub,
60+
Filter = "(objectClass=*)",
61+
PageSize = 2,
62+
MaxResults = 5
63+
};
64+
65+
var result = LDAP.SearchObjects(badInput, badConnection, CancellationToken.None);
66+
67+
Assert.IsFalse(result.Success, "Expected Success to be false when anonymous bind fails.");
68+
Assert.IsTrue(!string.IsNullOrEmpty(result.Error), "Expected an error message.");
69+
Assert.IsTrue(result.Error.Contains("bind"), "Expected error message to mention bind.");
70+
}
71+
}

Frends.LDAP.SearchObjects/Frends.LDAP.SearchObjects/Frends.LDAP.SearchObjects.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFrameworks>net6.0</TargetFrameworks>
5-
<Version>4.0.0</Version>
5+
<Version>4.1.0</Version>
66
<Authors>Frends</Authors>
77
<Copyright>Frends</Copyright>
88
<Company>Frends</Company>

Frends.LDAP.SearchObjects/Frends.LDAP.SearchObjects/SearchObjects.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public static Result SearchObjects([PropertyTab] Input input, [PropertyTab] Conn
3535
if (string.IsNullOrEmpty(connection.Password) && !connection.AnonymousBind)
3636
throw new Exception("Password is missing.");
3737

38+
if (input.MsLimit < 0)
39+
throw new ArgumentException("MsLimit must be a non-negative value (0 for no limit).");
40+
3841
LdapConnectionOptions ldco = new LdapConnectionOptions();
3942

4043
var encoding = GetEncoding(input.ContentEncoding, input.ContentEncodingString, input.EnableBom);
@@ -91,6 +94,13 @@ public static Result SearchObjects([PropertyTab] Input input, [PropertyTab] Conn
9194

9295
byte[] cookie = null;
9396

97+
if (!conn.Bound)
98+
{
99+
if (connection.ThrowExceptionOnError)
100+
throw new LdapException("LDAP bind failed: connection is not bound.");
101+
return new Result(false, $"LdapException: LDAP bind failed: connection is not bound.", null);
102+
}
103+
94104
do
95105
{
96106
if (input.PageSize > 0)
@@ -150,6 +160,18 @@ private static void ProcessSearchResults(LdapSearchQueue queue, List<SearchResul
150160
}
151161
else if (message is LdapResponse ldapResponse)
152162
{
163+
if (ldapResponse.ResultCode != LdapException.Success &&
164+
ldapResponse.ResultCode != LdapException.SizeLimitExceeded &&
165+
ldapResponse.ResultCode != LdapException.TimeLimitExceeded &&
166+
ldapResponse.ResultCode != LdapException.LdapPartialResults)
167+
{
168+
throw new LdapException(
169+
$"LDAP search failed with error: {ldapResponse.ErrorMessage}",
170+
ldapResponse.ResultCode,
171+
ldapResponse.MatchedDn
172+
);
173+
}
174+
153175
var controls = ldapResponse.Controls;
154176
if (controls != null)
155177
{

0 commit comments

Comments
 (0)