From 4284fa679ba482fc24e0f53107b953aacbcf8ff2 Mon Sep 17 00:00:00 2001 From: Viko Bastidas Date: Thu, 4 Jun 2026 23:12:15 -0400 Subject: [PATCH 1/2] .NET 10 upgrade --- .editorconfig | 93 +++++++++++++++++++ .github/workflows/ci.yml | 47 ++++++++++ ...per Libraries.sln => Bitai.Ldap.Helper.sln | 4 +- README.md | 8 +- .../Bitai.LDAPHelper.Demo.csproj | 18 ++-- .../Bitai.LDAPHelper.DTO.csproj | 10 +- src/Bitai.LDAPHelper/AccountManager.cs | 44 ++++----- src/Bitai.LDAPHelper/Authenticator.cs | 22 ++--- src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj | 10 +- .../Bitai.LDAPHelper.Tests.Mocks.csproj | 8 +- .../AccountManagerAdapterTests.cs | 6 +- .../Bitai.LDAPHelper.Tests.csproj | 23 +++-- 12 files changed, 221 insertions(+), 72 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml rename LDAP Helper Libraries.sln => Bitai.Ldap.Helper.sln (99%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..95482f8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,93 @@ +root = true + +# ========================================================= +# GLOBAL DEFAULTS (apply to all files) +# ========================================================= +[*] +indent_style = space +indent_size = 4 +tab_width = 4 +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# ========================================================= +# C# (Visual Studio, .NET APIs, microservices) +# ========================================================= +[*.cs] +indent_style = space +indent_size = 4 +dotnet_sort_system_directives_first = true +csharp_new_line_before_open_brace = all + +# ========================================================= +# Java (Spring Boot microservices, shared libs) +# ========================================================= +[*.java] +indent_style = space +indent_size = 4 + +# ========================================================= +# Python (ML scripts, utilities, automation) +# ========================================================= +[*.py] +indent_style = space +indent_size = 4 +max_line_length = 120 + +# ========================================================= +# JavaScript / TypeScript (React, Angular) +# ========================================================= +[*.js] +indent_size = 2 + +[*.jsx] +indent_size = 2 + +[*.ts] +indent_size = 2 + +[*.tsx] +indent_size = 2 + +# ========================================================= +# HTML / CSS / SCSS (UI templates) +# ========================================================= +[*.html] +indent_size = 2 + +[*.css] +indent_size = 2 + +[*.scss] +indent_size = 2 + +# ========================================================= +# JSON / YAML / XML (configs, pipelines, manifests) +# ========================================================= +[*.json] +indent_size = 2 + +[*.yml] +indent_size = 2 + +[*.yaml] +indent_size = 2 + +[*.xml] +indent_size = 2 + +# ========================================================= +# Dockerfiles +# ========================================================= +[Dockerfile] +indent_style = space +indent_size = 4 + +# ========================================================= +# Shell scripts +# ========================================================= +[*.sh] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1f99a5a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-test: + runs-on: ubuntu-latest + env: + DOTNET_NOLOGO: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + cache: true + cache-dependency-path: | + **/*.csproj + **/*.fsproj + **/*.props + **/*.targets + **/nuget.config + global.json + Directory.Packages.props + + - name: dotnet info + run: dotnet --info + + - name: Restore + run: dotnet restore Bitai.Ldap.Helper.sln + + - name: Build + run: dotnet build Bitai.Ldap.Helper.sln --configuration Release --no-restore + + - name: Test + run: dotnet test Bitai.Ldap.Helper.sln --configuration Release --no-build --no-restore --verbosity normal diff --git a/LDAP Helper Libraries.sln b/Bitai.Ldap.Helper.sln similarity index 99% rename from LDAP Helper Libraries.sln rename to Bitai.Ldap.Helper.sln index 16c2481..2e4c7c0 100644 --- a/LDAP Helper Libraries.sln +++ b/Bitai.Ldap.Helper.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33213.308 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11822.322 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bitai.LDAPHelper.DTO", "src\Bitai.LDAPHelper.DTO\Bitai.LDAPHelper.DTO.csproj", "{2386A126-F237-43FD-9A7C-FDF6718ACAD7}" EndProject diff --git a/README.md b/README.md index 4aa6a98..130327c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bitai.LDAPHelper ![Logo](resources/hierarchy_32.png) -A high-performance **.NET 8.0** library wrapping **Novell.Directory.Ldap.NETStandard** functionality to interact with LDAP-compliant Directory Services (such as Microsoft Active Directory). It simplifies operations like searching, authenticating users, and creating, modifying, disabling, or deleting accounts. +A high-performance library wrapping **Novell.Directory.Ldap.NETStandard** functionality to interact with LDAP-compliant Directory Services (such as Microsoft Active Directory). It simplifies operations like searching, authenticating users, and creating, modifying, disabling, or deleting accounts. This library is a key component of the [Bitai.LDAPWebApi](https://github.com/bitai-cs/LDAPWebApi) solution ecosystem. @@ -12,7 +12,7 @@ The library is organized into specialized helper classes targeting specific dire ### 🛡️ 1. Authentication (`Authenticator`) Enables quick and secure validation of user credentials on the LDAP/AD server. -- Supports both **User Distinguished Name (DN)** (`LDAPDistinguishedNameCredential`) and **Domain Account Name** (`LDAPDomainAccountCredential`) credential structures. +- Supports both **User Distinguished Name (DN)** (`LDAPDistinguishedNameCredential`) and **Domain Username** (`LDAPDomainAccountCredential`) credential structures. - Provides a simple authentication bind check (`AuthenticateAsync(credential)`). - Provides a comprehensive, pre-validated authentication workflow that first searches the user entry in Active Directory to ensure uniqueness and validity before attempting the bind (`AuthenticateAsync(credential, searchLimits, searchCredential)`). @@ -20,7 +20,7 @@ Enables quick and secure validation of user credentials on the LDAP/AD server. Simplifies user provisioning and lifecycle management within Active Directory. - **Create User Accounts** (`CreateUserAccountForMsAD`): Provision new Active Directory entries (`LDAPMsADUserAccount`) with extensive attribute mappings (such as UPN, sAMAccountName, unicodePwd, department, memberOf, object classes, and user control flags). - **Set/Change Passwords** (`SetUserAccountPasswordForMsAD`): Safe password replacement utilizing secure Unicode encoding. It can optionally test immediate post-update authentication. -- **Disable Accounts** (`DisableUserAccountForMsAD`): Securely disables accounts by updating the `userAccountControl` attribute with the `ACCOUNTDISABLE` flag, dynamically preserving all other existing account flags to prevent unintended configuration loss. +- **Disable Accounts** (`DisableUserAccountForMsAD`): Securely disables accounts by updating the `userAccountControl` attribute with the `ACCOUNTDISABLE` flag, dynamically preserving all other existing account name flags to prevent unintended configuration loss. - **Remove Accounts** (`RemoveUserAccountForMsAD`): Permanently deletes user entries from the directory service. ### 🔍 3. Directory Searching (`Searcher`) @@ -49,7 +49,7 @@ The solution `LDAP Helper Libraries.sln` consists of the following projects: ## ⚙️ Requirements & Dependencies -- **.NET 8.0 SDK** or higher. +- **.NET 10 SDK** or higher. - **Novell.Directory.Ldap.NETStandard** (v4.0.0+) package dependency. --- diff --git a/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj b/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj index 784525c..7189504 100644 --- a/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj +++ b/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj @@ -1,7 +1,7 @@  Exe - net8.0 + net10.0 Bitai.LDAPHelper.Demo Viko Bastidas (BITAI) BITAI @@ -10,17 +10,17 @@ © 2026 BITAI. All rights reserved. Bitai.Bitai.LDAPHelper.Demo hierarchy_32.png - 8.6.0 - 8.6.0.0 - 8.6.0.0 - .NET 8 ready + 10.0.0 + 10.0.0 + 10.0.0 + .NET 10 ready ldap-ad-identity-auth-openid-oauth-security - - - - + + + + diff --git a/src/Bitai.LDAPHelper.DTO/Bitai.LDAPHelper.DTO.csproj b/src/Bitai.LDAPHelper.DTO/Bitai.LDAPHelper.DTO.csproj index e268b80..a77f921 100644 --- a/src/Bitai.LDAPHelper.DTO/Bitai.LDAPHelper.DTO.csproj +++ b/src/Bitai.LDAPHelper.DTO/Bitai.LDAPHelper.DTO.csproj @@ -1,10 +1,10 @@ - net8.0 - 8.6.0.0 - 8.6.0.0 - 8.6.0 + net10.0 + 10.0.0 + 10.0.0 + 10.0.0 Bitai.LDAPHelper.DTO true Viko Bastidas (BITAI) @@ -21,7 +21,7 @@ GitHub ldap, authentication, directory, helper README.md - .NET 8 ready + .NET 10 ready diff --git a/src/Bitai.LDAPHelper/AccountManager.cs b/src/Bitai.LDAPHelper/AccountManager.cs index f22809f..7b4abd1 100644 --- a/src/Bitai.LDAPHelper/AccountManager.cs +++ b/src/Bitai.LDAPHelper/AccountManager.cs @@ -30,11 +30,11 @@ public void InitializeMissingMsADUserAccountDN(LDAPMsADUserAccount userAccount) } /// - /// Create a user account in MS Active Directory service + /// Create a username in MS Active Directory service /// https://www.rlmueller.net/Name_Attributes.htm /// /// - /// DN of the container in which the user account will be created. + /// DN of the container in which the username will be created. /// Optional tag to mark the request and/or response. /// A Task of public async Task CreateUserAccountForMsAD(LDAPMsADUserAccount newUserAccount, string requestLabel = null) @@ -58,7 +58,7 @@ public async Task CreateUserAccountForMsAD(LDAP throw new InvalidOperationException($"{nameof(newUserAccount.ObjectClass)} is required."); #endregion - //Generate user account DistinguishedName LDAP attribute + //Generate username DistinguishedName LDAP attribute InitializeMissingMsADUserAccountDN(newUserAccount); using (var ldapConnection = await GetLdapConnection(this.ConnectionInfo, this.DomainAccountCredential)) { @@ -113,18 +113,18 @@ public async Task CreateUserAccountForMsAD(LDAP } #endregion - //Add new user account entry to the directory + //Add new username entry to the directory await ldapConnection.AddEntryAsync(newUserAccount.DistinguishedName, attributeSet); } return new LDAPCreateMsADUserAccountResult(newUserAccount.SecureClone(), requestLabel) { - OperationMessage = $"User account created at {newUserAccount.DistinguishedName} with {EntryAttribute.sAMAccountName.ToString()}: {newUserAccount.SAMAccountName}" + OperationMessage = $"Account name created at {newUserAccount.DistinguishedName} with {EntryAttribute.sAMAccountName.ToString()}: {newUserAccount.SAMAccountName}" }; } catch (Exception ex) { - return new LDAPCreateMsADUserAccountResult("Error creating user account.", ex, requestLabel) + return new LDAPCreateMsADUserAccountResult("Error creating username.", ex, requestLabel) { UserAccount = newUserAccount.SecureClone() }; @@ -132,18 +132,18 @@ public async Task CreateUserAccountForMsAD(LDAP } /// - /// Set a password for a user account in MS Active Directory service. This method will verify the authenticity of the user account by its distinguished name before trying to set the password. If the user account is not valid, the operation will not be attempted and an error will be returned. + /// Set a password for a username in MS Active Directory service. This method will verify the authenticity of the username by its distinguished name before trying to set the password. If the username is not valid, the operation will not be attempted and an error will be returned. /// /// /// Optional tag to mark the request and/or response. - /// True if the account will be tested to be able to authenticate with the new password. False if only the password will be assigned, authentication will not be tested. + /// True if the account name will be tested to verify authentication with the new password. False if the password will simply be assigned and authentication will not be tested. /// public async Task SetUserAccountPasswordForMsAD(DTO.LDAPDistinguishedNameCredential credential, string requestLabel = null, bool postUpdateTestAuthentication = true) { try { if (string.IsNullOrEmpty(credential.DistinguishedName)) - throw new ArgumentNullException("The distinguished name of the user account is required."); + throw new ArgumentNullException("The distinguished name of the username is required."); if (string.IsNullOrEmpty(credential.Password)) throw new ArgumentNullException($"The password to be assigned is required."); @@ -203,9 +203,9 @@ DTO.LDAPPasswordUpdateResult createSuccessfulResult(string label, string name) { } /// - /// Remove a user account from MS Active Directory service. This method will verify the authenticity of the user account by its distinguished name before trying to remove it. If the user account is not valid, the operation will not be attempted and an error will be returned. + /// Remove a username from MS Active Directory service. This method will verify the authenticity of the username by its distinguished name before trying to remove it. If the username is not valid, the operation will not be attempted and an error will be returned. /// - /// User account distinguished name + /// Distinguished name of the username /// Optional tag to mark the request and/or response. /// public async Task DisableUserAccountForMsAD(string distinguishedName, string requestLabel) @@ -218,11 +218,11 @@ public async Task DisableUserAccountForMs var entry = await verifyUserAccountAuthenticity(distinguishedName, requestLabel); using (var ldapConnection = await GetLdapConnection(this.ConnectionInfo, this.DomainAccountCredential)) { - //To disable a user account in MS AD, the userAccountControl attribute needs to be set with the appropriate flags. The flag for disabling an account is ACCOUNTDISABLE (0x0002). However, when setting the userAccountControl attribute, it is important to preserve the existing flags that are set for the account, and only add the ACCOUNTDISABLE flag without removing any of the existing flags. This is because other flags may be set for the account that are necessary for its proper functioning, and removing them could cause unintended consequences. Therefore, when disabling a user account, you should retrieve the current value of the userAccountControl attribute, add the ACCOUNTDISABLE flag to it, and then update the attribute with the new value that includes both the existing flags and the ACCOUNTDISABLE flag. + //To disable an account name in MS AD, the userAccountControl attribute needs to be set with the appropriate flags. The flag for disabling an account name is ACCOUNTDISABLE (0x0002). However, when setting the userAccountControl attribute, it is important to preserve the existing flags that are set for the account, and only add the ACCOUNTDISABLE flag without removing any of the existing flags. This is because other flags may be set for the account name that are necessary for its proper functioning, and removing them could cause unintended consequences. Therefore, when disabling a username, you should retrieve the current value of the userAccountControl attribute, add the ACCOUNTDISABLE flag to it, and then update the attribute with the new value that includes both the existing flags and the ACCOUNTDISABLE flag. UserAccountControlFlagsForMsAD userAccountControlFlags = UserAccountControlFlagsForMsAD.NORMAL_ACCOUNT | UserAccountControlFlagsForMsAD.ACCOUNTDISABLE; //var userAccountControlAttribute = new LdapAttribute(DTO.EntryAttribute.userAccountControl.ToString(), ((int)userAccountControlFlags).ToString()); - //Create modification request to disable the account by setting the userAccountControl attribute with the appropriate flags + //Create modification request to disable the account name by setting the userAccountControl attribute with the appropriate flags var modification = ldapConnection.CreateModification(LdapModificationType.Replace, EntryAttribute.userAccountControl.ToString(), ((int)userAccountControlFlags).ToString()); //var userAccountControlModification = new LdapModification(LdapModification.Replace, userAccountControlAttribute); @@ -234,7 +234,7 @@ public async Task DisableUserAccountForMs return new DTO.LDAPDisableUserAccountOperationResult(requestLabel) { - OperationMessage = $"User account {entry.samAccountName} has been disabled." + OperationMessage = $"Username {entry.samAccountName} has been disabled." }; } catch (Bitai.LDAPHelper.EntryNotFoundException ex) { @@ -242,14 +242,14 @@ public async Task DisableUserAccountForMs } catch (Exception ex) { - return new LDAPDisableUserAccountOperationResult($"Error trying to disable user account with DN: {distinguishedName}", ex, requestLabel); + return new LDAPDisableUserAccountOperationResult($"Error trying to disable username with DN: {distinguishedName}", ex, requestLabel); } } /// - /// Remove a user account in MS Active Directory service. This operation will permanently delete the user account entry from the directory, so it should be used with caution. + /// Remove a username in MS Active Directory service. This operation will permanently delete the username entry from the directory, so it should be used with caution. /// - /// User account distinguished name. + /// Distinguished name of the username /// Optional tag to mark the request and/or response. /// public async Task RemoveUserAccountForMsAD(string distinguishedName, string requestLabel = null) @@ -257,20 +257,20 @@ public async Task RemoveUserAccountForMsAD(stri try { if (string.IsNullOrEmpty(distinguishedName)) - throw new ArgumentNullException("The DN of the account to be removed must be provided."); + throw new ArgumentNullException("The distinguished name of the username to remove must be provided."); var entry = await verifyUserAccountAuthenticity(distinguishedName, requestLabel); using (var ldapConnection = await GetLdapConnection(this.ConnectionInfo, this.DomainAccountCredential)) { - //To remove a user account from MS AD, the user account entry needs to be deleted from the directory. This operation will permanently delete the user account entry, so it should be used with caution. + //To remove a username from MS AD, the username entry needs to be deleted from the directory. This operation will permanently delete the username entry, so it should be used with caution. await ldapConnection.DeleteEntryAsync(entry.distinguishedName); //await ldapConnection.DeleteAsync(entry.distinguishedName); } return new LDAPRemoveMsADUserAccountResult(requestLabel) { - OperationMessage = $"The user account {entry.samAccountName} has been successfully removed." + OperationMessage = $"The username {entry.samAccountName} has been successfully removed." }; } catch (Bitai.LDAPHelper.EntryNotFoundException ex) { @@ -278,7 +278,7 @@ public async Task RemoveUserAccountForMsAD(stri } catch (Exception ex) { - return new LDAPRemoveMsADUserAccountResult($"Error trying to remove user account with DN: {distinguishedName}", ex, requestLabel); + return new LDAPRemoveMsADUserAccountResult($"Error trying to remove username with DN: {distinguishedName}", ex, requestLabel); } } @@ -307,7 +307,7 @@ private async Task verifyUserAccountAuthenticity(string distinguished var entry = searchResult.Entries.Single(); if (!entry.objectClass.Contains("user")) - throw new InvalidOperationException($"DN {distinguishedName} is not a user account."); + throw new InvalidOperationException($"DN {distinguishedName} is not a username."); return entry; } diff --git a/src/Bitai.LDAPHelper/Authenticator.cs b/src/Bitai.LDAPHelper/Authenticator.cs index 0a5d4c9..6e7f3bb 100644 --- a/src/Bitai.LDAPHelper/Authenticator.cs +++ b/src/Bitai.LDAPHelper/Authenticator.cs @@ -1,4 +1,4 @@ -using Bitai.LDAPHelper.LdapAdapters; +using Bitai.LDAPHelper.LdapAdapters; using Bitai.LDAPHelper.DTO; using Bitai.LDAPHelper.QueryFilters; using System; @@ -42,7 +42,7 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte else { if (searchResult.Entries.Count() == 0) { authenticationResult = new LDAPDomainAccountAuthenticationResult(credential.SecureClone(), false, requestLabel); - authenticationResult.SetSuccessfulOperation($"The domain user account {credential.DomainName}\\{credential.AccountName} could not be found."); + authenticationResult.SetSuccessfulOperation($"The domain username {credential.DomainName}\\{credential.AccountName} could not be found."); return authenticationResult; } @@ -64,7 +64,7 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte authenticationResult = new DTO.LDAPDomainAccountAuthenticationResult(credential.SecureClone(), authenticated.Value, requestLabel); if (authenticated.Value) - authenticationResult.SetSuccessfulOperation($"The domain user account {credential.DomainName}\\{credential.AccountName} has been successfully authenticated."); + authenticationResult.SetSuccessfulOperation($"The domain username {credential.DomainName}\\{credential.AccountName} has been successfully authenticated."); else authenticationResult.SetSuccessfulOperation("The password is wrong"); @@ -93,9 +93,9 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte var result = new DTO.LDAPDomainAccountAuthenticationResult(credential.SecureClone(), authenticated.Value, requestLabel); if (authenticated.Value) - result.SetSuccessfulOperation($"The domain user account {credential.DomainAccountName} has been successfully authenticated."); + result.SetSuccessfulOperation($"The domain username {credential.DomainAccountName} has been successfully authenticated."); else - result.SetSuccessfulOperation("Wrong user account and/or password."); + result.SetSuccessfulOperation("Wrong username and/or password."); return result; } @@ -130,7 +130,7 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte else { if (searchResult.Entries.Count() == 0) { authenticationResult = new LDAPDistinguishedNameAuthenticationResult(credential.SecureClone(), false, requestLabel); - authenticationResult.SetSuccessfulOperation($"The account {credential.DistinguishedName} could not be found."); + authenticationResult.SetSuccessfulOperation($"Unable to locate the account's DN: {credential.DistinguishedName}"); return authenticationResult; } @@ -152,9 +152,9 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte authenticationResult = new DTO.LDAPDistinguishedNameAuthenticationResult(credential.SecureClone(), authenticated.Value, requestLabel); if (authenticated.Value) - authenticationResult.SetSuccessfulOperation($"The account {credential.DistinguishedName} has been successfully authenticated."); + authenticationResult.SetSuccessfulOperation($"The account with DN: {credential.DistinguishedName} has been successfully authenticated."); else - authenticationResult.SetSuccessfulOperation("The password is wrong"); + authenticationResult.SetSuccessfulOperation("Authentication failed: incorrect password."); return authenticationResult; } @@ -176,9 +176,9 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte var result = new DTO.LDAPDistinguishedNameAuthenticationResult(credential.SecureClone(), authenticated.Value, requestLabel); if (authenticated.Value) - result.SetSuccessfulOperation($"The account {credential.DistinguishedName} has been successfully authenticated."); + result.SetSuccessfulOperation($"The account with DN: {credential.DistinguishedName} has been successfully authenticated."); else - result.SetSuccessfulOperation("Wrong account and/or password."); + result.SetSuccessfulOperation("Authentication failed: incorrect account name or password."); return result; } @@ -188,4 +188,4 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte } #endregion } -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj b/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj index 8679c0f..833e4ce 100644 --- a/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj +++ b/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj @@ -1,16 +1,16 @@ - net8.0 + net10.0 Viko Bastidas (BITAI) BITAI LDAP Services Wrappers Library to wrap Novell.Directory.Ldap.NETStandard functionality to make LDAP common queries to search accounts and objects in a Directory Service. © 2026 BITAI. All rights reserved. - 8.6.0 - 8.6.0.0 + 10.0.0 + 10.0.0 hierarchy_32.png - 8.6.0.0 + 10.0.0 true Bitai.LDAPHelper Bitai.LDAPHelper @@ -20,7 +20,7 @@ ldap;authentication;authorization;directory;helper;oauth;openid;ad;security;identity LICENSE.md Bitai.LDAPHelper - .NET 8 ready + .NET 10 ready README.md diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj b/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj index e76fde5..f554382 100644 --- a/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj @@ -1,12 +1,12 @@  - net8.0 + net10.0 enable enable - 8.6.0 - 8.6.0.0 - 8.6.0.0 + 10.0.0 + 10.0.0 + 10.0.0 Viko Bastidas (BITAI) © 2026 BITAI. All rights reserved. https://github.com/bitai-cs/LDAPHelper.git diff --git a/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs b/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs index d36efe3..947c6ce 100644 --- a/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs +++ b/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs @@ -1,4 +1,4 @@ -using Bitai.LDAPHelper.DTO; +using Bitai.LDAPHelper.DTO; using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; namespace Bitai.LDAPHelper.Tests @@ -35,7 +35,7 @@ public async Task CreateUserAccountForMsAD_ReturnsSuccess() { // Assert Assert.True(result.IsSuccessfulOperation); - Assert.Contains("user account created at", result.OperationMessage.ToLower()); + Assert.Contains("account nameX created at", result.OperationMessage.ToLower()); } [Fact] @@ -263,4 +263,4 @@ public async Task RemoveUserAccountForMsAD_AccountNotFound_ReturnsSuccess() { Assert.Contains("does not exist", result.OperationMessage.ToLower()); } } -} \ No newline at end of file +} diff --git a/tests/Bitai.LDAPHelper.Tests/Bitai.LDAPHelper.Tests.csproj b/tests/Bitai.LDAPHelper.Tests/Bitai.LDAPHelper.Tests.csproj index d36e0d3..0b9177b 100644 --- a/tests/Bitai.LDAPHelper.Tests/Bitai.LDAPHelper.Tests.csproj +++ b/tests/Bitai.LDAPHelper.Tests/Bitai.LDAPHelper.Tests.csproj @@ -1,22 +1,31 @@ - net8.0 + net10.0 enable enable false true - 8.6.0.0 - 8.6.0.0 + 10.0.0 + 10.0.0 + 10.0.0 + 5981b6a0-6b9e-439d-8324-a0ef8bfd0f11 - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 5284e58ede5f2016a96ef7000731ccd2d9e5abf6 Mon Sep 17 00:00:00 2001 From: Viko Bastidas Date: Thu, 4 Jun 2026 23:45:27 -0400 Subject: [PATCH 2/2] .NET 10 Upgrade: Fix tests & library messages --- README.md | 6 +++--- demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs | 14 +++++++------- .../LDAPDistinguishedNameCredential.cs | 4 ++-- .../LDAPDomainAccountCredential.cs | 14 +++++++------- src/Bitai.LDAPHelper/AccountManager.cs | 8 ++++---- src/Bitai.LDAPHelper/Authenticator.cs | 2 +- .../AccountManagerAdapterTests.cs | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 130327c..fa967d7 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Enables quick and secure validation of user credentials on the LDAP/AD server. ### 👤 2. Account Management (`AccountManager`) Simplifies user provisioning and lifecycle management within Active Directory. -- **Create User Accounts** (`CreateUserAccountForMsAD`): Provision new Active Directory entries (`LDAPMsADUserAccount`) with extensive attribute mappings (such as UPN, sAMAccountName, unicodePwd, department, memberOf, object classes, and user control flags). +- **Create MS AD User Accounts** (`CreateUserAccountForMsAD`): Provision new Active Directory entries (`LDAPMsADUserAccount`) with extensive attribute mappings (such as UPN, sAMAccountName, unicodePwd, department, memberOf, object classes, and user control flags). - **Set/Change Passwords** (`SetUserAccountPasswordForMsAD`): Safe password replacement utilizing secure Unicode encoding. It can optionally test immediate post-update authentication. -- **Disable Accounts** (`DisableUserAccountForMsAD`): Securely disables accounts by updating the `userAccountControl` attribute with the `ACCOUNTDISABLE` flag, dynamically preserving all other existing account name flags to prevent unintended configuration loss. -- **Remove Accounts** (`RemoveUserAccountForMsAD`): Permanently deletes user entries from the directory service. +- **Disable MS AD Accounts** (`DisableUserAccountForMsAD`): Securely disables accounts by updating the `userAccountControl` attribute with the `ACCOUNTDISABLE` flag, dynamically preserving all other existing account flags to prevent unintended configuration loss. +- **Remove MS AD Accounts** (`RemoveUserAccountForMsAD`): Permanently deletes user entries from the directory service. ### 🔍 3. Directory Searching (`Searcher`) Facilitates fast, flexible searching using highly customizable LDAP search configurations. diff --git a/demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs b/demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs index ad1f854..168821c 100644 --- a/demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs +++ b/demo/Bitai.LDAPHelper.Demo/Program.DemoMethods.cs @@ -23,7 +23,7 @@ public static async Task Demo_Authenticator_Authenticate_Simple(DemoContext cont var domainAccountCredentialParts = domainAccountCredential.Split(new char[] { '\\' }); - Log.Information("Authenticating account name..."); + Log.Information("Authenticating domain username..."); var authenticationResult = await authenticator.AuthenticateAsync( new LDAPDomainAccountCredential(domainAccountCredentialParts[0], domainAccountCredentialParts[1], accountPassword), context.RequestLabel); @@ -33,9 +33,9 @@ public static async Task Demo_Authenticator_Authenticate_Simple(DemoContext cont Log.Information("{@model}", authenticationResult); if (authenticationResult.IsAuthenticated) - Log.Information("Account name authenticated."); + Log.Information("Username authenticated."); else - Log.Warning("Account name NOT authenticated."); + Log.Warning("Username NOT authenticated."); } else { @@ -58,7 +58,7 @@ public static async Task Demo_Authenticator_Authenticate_WithAccountValidation(D var domainAccountCredentialParts = domainAccountCredential.Split(new char[] { '\\' }); - Log.Information("Authenticating account name with account validation..."); + Log.Information("Authenticating username with account validation..."); var authenticationResult = await authenticator.AuthenticateAsync( new LDAPDomainAccountCredential(domainAccountCredentialParts[0], domainAccountCredentialParts[1], accountPassword), context.GetSearchLimits(), @@ -70,9 +70,9 @@ public static async Task Demo_Authenticator_Authenticate_WithAccountValidation(D Log.Information("{@model}", authenticationResult); if (authenticationResult.IsAuthenticated) - Log.Information("Account name authenticated."); + Log.Information("Username authenticated."); else - Log.Warning("Account name NOT authenticated."); + Log.Warning("Username NOT authenticated."); } else { @@ -488,4 +488,4 @@ public static async Task Demo_GroupMembershipValidator_CheckGroupMembership( } #endregion -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper.DTO/LDAPDistinguishedNameCredential.cs b/src/Bitai.LDAPHelper.DTO/LDAPDistinguishedNameCredential.cs index c33d969..3e60aeb 100644 --- a/src/Bitai.LDAPHelper.DTO/LDAPDistinguishedNameCredential.cs +++ b/src/Bitai.LDAPHelper.DTO/LDAPDistinguishedNameCredential.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,7 +32,7 @@ public LDAPDistinguishedNameCredential() public LDAPDistinguishedNameCredential(string distinguishedName, string password) { if (string.IsNullOrEmpty(distinguishedName)) - throw new InvalidOperationException("The account name must be specified."); + throw new InvalidOperationException("A distinguished name is required: the account’s DN must be specified."); DistinguishedName = distinguishedName; Password = password; diff --git a/src/Bitai.LDAPHelper.DTO/LDAPDomainAccountCredential.cs b/src/Bitai.LDAPHelper.DTO/LDAPDomainAccountCredential.cs index d2541dc..a2626d3 100644 --- a/src/Bitai.LDAPHelper.DTO/LDAPDomainAccountCredential.cs +++ b/src/Bitai.LDAPHelper.DTO/LDAPDomainAccountCredential.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -28,21 +28,21 @@ public LDAPDomainAccountCredential() /// /// Constructor /// - /// Domain name. - /// Account name. + /// Domain name + /// Username /// Account password. /// Constructor exception. - public LDAPDomainAccountCredential(string domainName, string accountName, string domainAccountPassword) + public LDAPDomainAccountCredential(string domainName, string username, string domainAccountPassword) { if (string.IsNullOrEmpty(domainName)) throw new InvalidOperationException("The domain name must be specified."); - if (string.IsNullOrEmpty(accountName)) - throw new InvalidOperationException("The account name must be specified."); + if (string.IsNullOrEmpty(username)) + throw new InvalidOperationException("The username must be specified."); DomainName = domainName; - AccountName = accountName; + AccountName = username; DomainAccountPassword = domainAccountPassword; } diff --git a/src/Bitai.LDAPHelper/AccountManager.cs b/src/Bitai.LDAPHelper/AccountManager.cs index 7b4abd1..7657a0e 100644 --- a/src/Bitai.LDAPHelper/AccountManager.cs +++ b/src/Bitai.LDAPHelper/AccountManager.cs @@ -119,7 +119,7 @@ public async Task CreateUserAccountForMsAD(LDAP return new LDAPCreateMsADUserAccountResult(newUserAccount.SecureClone(), requestLabel) { - OperationMessage = $"Account name created at {newUserAccount.DistinguishedName} with {EntryAttribute.sAMAccountName.ToString()}: {newUserAccount.SAMAccountName}" + OperationMessage = $"MS AD user account created at {newUserAccount.DistinguishedName} with {EntryAttribute.sAMAccountName.ToString()}: {newUserAccount.SAMAccountName}" }; } catch (Exception ex) @@ -136,7 +136,7 @@ public async Task CreateUserAccountForMsAD(LDAP /// /// /// Optional tag to mark the request and/or response. - /// True if the account name will be tested to verify authentication with the new password. False if the password will simply be assigned and authentication will not be tested. + /// True if the MS AD user account will be tested to verify authentication with the new password. False if the password will simply be assigned and authentication will not be tested. /// public async Task SetUserAccountPasswordForMsAD(DTO.LDAPDistinguishedNameCredential credential, string requestLabel = null, bool postUpdateTestAuthentication = true) { @@ -218,11 +218,11 @@ public async Task DisableUserAccountForMs var entry = await verifyUserAccountAuthenticity(distinguishedName, requestLabel); using (var ldapConnection = await GetLdapConnection(this.ConnectionInfo, this.DomainAccountCredential)) { - //To disable an account name in MS AD, the userAccountControl attribute needs to be set with the appropriate flags. The flag for disabling an account name is ACCOUNTDISABLE (0x0002). However, when setting the userAccountControl attribute, it is important to preserve the existing flags that are set for the account, and only add the ACCOUNTDISABLE flag without removing any of the existing flags. This is because other flags may be set for the account name that are necessary for its proper functioning, and removing them could cause unintended consequences. Therefore, when disabling a username, you should retrieve the current value of the userAccountControl attribute, add the ACCOUNTDISABLE flag to it, and then update the attribute with the new value that includes both the existing flags and the ACCOUNTDISABLE flag. + //To disable a MS AD user account, the userAccountControl attribute needs to be set with the appropriate flags. The flag for disabling an account is ACCOUNTDISABLE (0x0002). However, when setting the userAccountControl attribute, it is important to preserve the existing flags that are set for the account, and only add the ACCOUNTDISABLE flag without removing any of the existing flags. This is because other flags may be set for the account that are necessary for its proper functioning, and removing them could cause unintended consequences. Therefore, when disabling a username, you should retrieve the current value of the userAccountControl attribute, add the ACCOUNTDISABLE flag to it, and then update the attribute with the new value that includes both the existing flags and the ACCOUNTDISABLE flag. UserAccountControlFlagsForMsAD userAccountControlFlags = UserAccountControlFlagsForMsAD.NORMAL_ACCOUNT | UserAccountControlFlagsForMsAD.ACCOUNTDISABLE; //var userAccountControlAttribute = new LdapAttribute(DTO.EntryAttribute.userAccountControl.ToString(), ((int)userAccountControlFlags).ToString()); - //Create modification request to disable the account name by setting the userAccountControl attribute with the appropriate flags + //Create modification request to disable the account by setting the userAccountControl attribute with the appropriate flags var modification = ldapConnection.CreateModification(LdapModificationType.Replace, EntryAttribute.userAccountControl.ToString(), ((int)userAccountControlFlags).ToString()); //var userAccountControlModification = new LdapModification(LdapModification.Replace, userAccountControlAttribute); diff --git a/src/Bitai.LDAPHelper/Authenticator.cs b/src/Bitai.LDAPHelper/Authenticator.cs index 6e7f3bb..6d2e64c 100644 --- a/src/Bitai.LDAPHelper/Authenticator.cs +++ b/src/Bitai.LDAPHelper/Authenticator.cs @@ -178,7 +178,7 @@ public Authenticator(ConnectionInfo connectionInfo, ILdapConnectionFactoryAdapte if (authenticated.Value) result.SetSuccessfulOperation($"The account with DN: {credential.DistinguishedName} has been successfully authenticated."); else - result.SetSuccessfulOperation("Authentication failed: incorrect account name or password."); + result.SetSuccessfulOperation("Authentication failed: incorrect username or password."); return result; } diff --git a/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs b/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs index 947c6ce..fbe8f50 100644 --- a/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs +++ b/tests/Bitai.LDAPHelper.Tests/AccountManagerAdapterTests.cs @@ -35,7 +35,7 @@ public async Task CreateUserAccountForMsAD_ReturnsSuccess() { // Assert Assert.True(result.IsSuccessfulOperation); - Assert.Contains("account nameX created at", result.OperationMessage.ToLower()); + Assert.Contains("ms ad user account created at", result.OperationMessage.ToLower()); } [Fact]