Merge pull request #4984 from abpframework/maliming/ldap

Make Volo.Abp.Ldap support multi-tenancy.
pull/5011/head
Halil İbrahim Kalkan 5 years ago committed by GitHub
commit 7a94bc42ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,11 +15,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="2.3.8" />
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<Content Remove="Volo\Abp\Ldap\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\Ldap\Localization\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.Options;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
namespace Volo.Abp.Ldap
{
public class AbpAbpLdapOptionsFactory : AbpOptionsFactory<AbpLdapOptions>
{
protected ISettingProvider SettingProvider { get; }
public AbpAbpLdapOptionsFactory(
IEnumerable<IConfigureOptions<AbpLdapOptions>> setups,
IEnumerable<IPostConfigureOptions<AbpLdapOptions>> postConfigures,
ISettingProvider settingProvider)
: base(setups, postConfigures)
{
SettingProvider = settingProvider;
}
public override AbpLdapOptions Create(string name)
{
var options = base.Create(name);
AsyncHelper.RunSync(() => OverrideOptionsAsync(options));
return options;
}
protected virtual async Task OverrideOptionsAsync(AbpLdapOptions options)
{
options.ServerHost = await GetSettingOrDefaultValue(LdapSettingNames.ServerHost, options.ServerHost);
options.ServerPort = await SettingProvider.GetAsync(LdapSettingNames.ServerPort, options.ServerPort);
options.UserName = await GetSettingOrDefaultValue(LdapSettingNames.UserName, options.UserName);
options.Password = await GetSettingOrDefaultValue(LdapSettingNames.Password, options.Password);
}
protected virtual async Task<string> GetSettingOrDefaultValue(string name, string defaultValue)
{
var value = await SettingProvider.GetOrNullAsync(name);
return value.IsNullOrWhiteSpace() ? defaultValue : value;
}
}
}

@ -1,18 +1,44 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.Ldap.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Ldap
{
[DependsOn(
typeof(AbpAutofacModule)
)]
typeof(AbpSettingsModule),
typeof(AbpVirtualFileSystemModule),
typeof(AbpLocalizationModule))]
public class AbpLdapModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Replace(ServiceDescriptor.Transient<IOptionsFactory<AbpLdapOptions>, AbpAbpLdapOptionsFactory>());
context.Services.Replace(ServiceDescriptor.Scoped<IOptions<AbpLdapOptions>, OptionsManager<AbpLdapOptions>>());
var configuration = context.Services.GetConfiguration();
Configure<AbpLdapOptions>(configuration.GetSection("LDAP"));
var ldapConfiguration = configuration["Ldap"];
if (!ldapConfiguration.IsNullOrEmpty())
{
Configure<AbpLdapOptions>(configuration.GetSection("Ldap"));
}
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpLdapModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<LdapResource>("en")
.AddVirtualJson("/Volo/Abp/Ldap/Localization");
});
}
}
}
}

@ -6,19 +6,8 @@
public int ServerPort { get; set; }
public bool UseSsl { get; set; }
public string UserName { get; set; }
public string SearchBase { get; set; }
public string DomainName { get; set; }
public string DomainDistinguishedName { get; set; }
public LdapCredentials Credentials { get; set; }
public AbpLdapOptions()
{
Credentials = new LdapCredentials();
}
public string Password { get; set; }
}
}
}

@ -1,11 +0,0 @@
namespace Volo.Abp.Ldap.Exceptions
{
public class OrganizationNotExistException : BusinessException
{
public OrganizationNotExistException(string distinguishedName)
: base("LDAP:000001", $"the organization distinguished named {distinguishedName} does not exist.")
{
}
}
}

@ -1,72 +1,17 @@
using System.Collections.Generic;
using Volo.Abp.Ldap.Modeling;
namespace Volo.Abp.Ldap
namespace Volo.Abp.Ldap
{
public interface ILdapManager
{
/// <summary>
/// query the specified organizations.
///
/// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null
/// filter: (&(name=*)(objectClass=organizationalUnit)) when name is null
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
IList<LdapOrganization> GetOrganizations(string name = null);
/// <summary>
/// query the specified organization.
///
/// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null
///
/// Authenticate with default username/password
/// </summary>
/// <param name="distinguishedName"></param>
/// <returns></returns>
LdapOrganization GetOrganization(string distinguishedName);
void AddSubOrganization(string organizationName, LdapOrganization parentOrganization);
void AddSubOrganization(string organizationName, string parentDistinguishedName);
/// <summary>
/// query the specified users.
///
/// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null
/// filter: (&(name=*)(objectCategory=person)(objectClass=user)) when name is null
///
/// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null
/// filter: (&(displayName=*)(objectCategory=person)(objectClass=user)) when displayName is null
///
/// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null
/// filter: (&(cn=*)(objectCategory=person)(objectClass=user)) when commonName is null
///
/// </summary>
/// <param name="name"></param>
/// <param name="displayName"></param>
/// <param name="commonName"></param>
/// <returns></returns>
IList<LdapUser> GetUsers(string name = null, string displayName = null, string commonName = null);
/// <summary>
/// query the specified User.
///
/// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null
///
/// </summary>
/// <param name="distinguishedName"></param>
/// <returns></returns>
LdapUser GetUser(string distinguishedName);
void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization);
void AddUserToOrganization(string userName, string password, string parentDistinguishedName);
bool Authenticate();
/// <summary>
/// Authenticate
/// Authenticate with specified username/password
/// </summary>
/// <param name="userDomainName">E.g administrator@yourdomain.com.cn </param>
/// <param name="password"></param>
/// <returns></returns>
bool Authenticate(string userDomainName, string password);
bool Authenticate(string username, string password);
}
}
}

@ -1,9 +0,0 @@
namespace Volo.Abp.Ldap
{
public class LdapCredentials
{
public string DomainUserName { get; set; }
public string Password { get; set; }
}
}

@ -1,34 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.Ldap
{
public static class LdapHelps
{
public static string BuildCondition(string name, string value)
{
return string.IsNullOrWhiteSpace(value) ? "" : $"({name}={value})";
}
public static string BuildFilter(Dictionary<string,string> conditions)
{
if (null == conditions )
{
conditions = new Dictionary<string, string>();
}
if (conditions.Keys.Count == 0)
{
conditions.Add("objectClass", "*"); // add default condition
}
var subFilter = string.Empty;
foreach (var keyValuePair in conditions)
{
subFilter += BuildCondition(keyValuePair.Key, keyValuePair.Value);
}
return $"(&{subFilter})";
}
}
}

@ -1,343 +1,61 @@
using System;
using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Ldap.Exceptions;
using Volo.Abp.Ldap.Modeling;
namespace Volo.Abp.Ldap
{
public class LdapManager : ILdapManager, ITransientDependency
{
private readonly string _searchBase;
private readonly AbpLdapOptions _ldapOptions;
private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory;
public ILogger<LdapManager> Logger { get; set; }
protected AbpLdapOptions LdapOptions { get; }
private readonly string[] _attributes =
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions)
{
"objectCategory", "objectClass", "cn", "name", "distinguishedName",
"ou",
"sAMAccountName", "userPrincipalName", "telephoneNumber", "mail"
};
LdapOptions = ldapSettingsOptions.Value;
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory)
{
_hybridServiceScopeFactory = hybridServiceScopeFactory;
_ldapOptions = ldapSettingsOptions.Value;
_searchBase = _ldapOptions.SearchBase;
}
#region Organization
/// <summary>
/// query the specified organizations.
///
/// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null
/// filter: (&(objectClass=organizationalUnit)) when name is null
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public IList<LdapOrganization> GetOrganizations(string name = null)
{
var conditions = new Dictionary<string, string>
{
{"name", name},
{"objectClass", "organizationalUnit"},
};
return Query<LdapOrganization>(_searchBase, conditions);
}
/// <summary>
/// query the specified organization.
///
/// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null
///
/// </summary>
/// <param name="distinguishedName"></param>
/// <returns></returns>
public LdapOrganization GetOrganization(string distinguishedName)
{
distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName));
var conditions = new Dictionary<string, string>
{
{"distinguishedName", distinguishedName},
{"objectClass", "organizationalUnit"},
};
return QueryOne<LdapOrganization>(_searchBase, conditions);
}
public void AddSubOrganization(string organizationName, LdapOrganization parentOrganization)
{
organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName));
var dn = $"OU={organizationName},{parentOrganization.DistinguishedName}";
var attributeSet = new LdapAttributeSet
{
new LdapAttribute("objectCategory", $"CN=Organizational-Unit,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"),
new LdapAttribute("objectClass", new[] {"top", "organizationalUnit"}),
new LdapAttribute("name", organizationName),
};
var newEntry = new LdapEntry(dn, attributeSet);
using (var ldapConnection = GetConnection())
{
ldapConnection.Add(newEntry);
}
Logger = NullLogger<LdapManager>.Instance;
}
public void AddSubOrganization(string organizationName, string parentDistinguishedName)
public virtual bool Authenticate()
{
organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName));
parentDistinguishedName =
Check.NotNullOrWhiteSpace(parentDistinguishedName, nameof(parentDistinguishedName));
var parentOrganization = GetOrganization(parentDistinguishedName);
if (null == parentOrganization)
{
throw new OrganizationNotExistException(parentDistinguishedName);
}
AddSubOrganization(organizationName, parentOrganization);
}
#endregion
#region User
/// <summary>
/// query the specified users.
///
/// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null
/// filter: (&(objectCategory=person)(objectClass=user)) when name is null
///
/// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null
/// filter: (&(objectCategory=person)(objectClass=user)) when displayName is null
///
/// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null
/// filter: (&(objectCategory=person)(objectClass=user)) when commonName is null
///
/// </summary>
/// <param name="name"></param>
/// <param name="displayName"></param>
/// <param name="commonName"></param>
/// <returns></returns>
public IList<LdapUser> GetUsers(string name = null, string displayName = null, string commonName = null)
{
var conditions = new Dictionary<string, string>
{
{"objectCategory", "person"},
{"objectClass", "user"},
{"name", name},
{"displayName", displayName},
{"cn", commonName},
};
return Query<LdapUser>(_searchBase, conditions);
return Authenticate(LdapOptions.UserName, LdapOptions.Password);
}
/// <summary>
/// query the specified User.
///
/// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null
///
/// </summary>
/// <param name="distinguishedName"></param>
/// <returns></returns>
public LdapUser GetUser(string distinguishedName)
{
distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName));
var conditions = new Dictionary<string, string>
{
{"objectCategory", "person"},
{"objectClass", "user"},
{"distinguishedName", distinguishedName},
};
return QueryOne<LdapUser>(_searchBase, conditions);
}
public void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization)
{
var dn = $"CN={userName},{parentOrganization.DistinguishedName}";
var mail = $"{userName}@{_ldapOptions.DomainName}";
sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\""));
var attributeSet = new LdapAttributeSet
{
new LdapAttribute("instanceType", "4"),
new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"),
new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}),
new LdapAttribute("name", userName),
new LdapAttribute("cn", userName),
new LdapAttribute("sAMAccountName", userName),
new LdapAttribute("userPrincipalName", userName),
new LdapAttribute("sn", userName),
new LdapAttribute("displayName", userName),
new LdapAttribute("unicodePwd", encodedBytes),
new LdapAttribute("userAccountControl", "512"),
new LdapAttribute("mail", mail),
};
var newEntry = new LdapEntry(dn, attributeSet);
using (var ldapConnection = GetConnection())
{
ldapConnection.Add(newEntry);
}
}
public void AddUserToOrganization(string userName, string password, string parentDistinguishedName)
{
var dn = $"CN={userName},{parentDistinguishedName}";
var mail = $"{userName}@{_ldapOptions.DomainName}";
sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\""));
var attributeSet = new LdapAttributeSet
{
new LdapAttribute("instanceType", "4"),
new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"),
new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}),
new LdapAttribute("name", userName),
new LdapAttribute("cn", userName),
new LdapAttribute("sAMAccountName", userName),
new LdapAttribute("userPrincipalName", userName),
new LdapAttribute("sn", userName),
new LdapAttribute("displayName", userName),
new LdapAttribute("unicodePwd", encodedBytes),
new LdapAttribute("userAccountControl", "512"),
new LdapAttribute("mail", mail),
};
var newEntry = new LdapEntry(dn, attributeSet);
using (var ldapConnection = GetConnection())
{
ldapConnection.Add(newEntry);
}
}
#endregion
#region Authenticate
/// <summary>
/// Authenticate
/// </summary>
/// <param name="userDomainName">E.g administrator@yourdomain.com.cn </param>
/// <param name="password"></param>
/// <returns></returns>
public bool Authenticate(string userDomainName, string password)
public bool Authenticate(string username, string password)
{
try
{
using (GetConnection(userDomainName, password))
{
return true;
}
var conn = CreateLdapConnection();
AuthenticateLdapConnection(conn, username, password);
return true;
}
catch (Exception ex)
{
using (var scope = _hybridServiceScopeFactory.CreateScope())
{
scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(ex);
}
Logger.LogException(ex);
return false;
}
}
#endregion
private ILdapConnection GetConnection(string bindUserName = null, string bindUserPassword = null)
protected virtual ILdapConnection CreateLdapConnection()
{
// bindUserName/bindUserPassword only be used when authenticate
bindUserName = bindUserName ?? _ldapOptions.Credentials.DomainUserName;
bindUserPassword = bindUserPassword ?? _ldapOptions.Credentials.Password;
var ldapConnection = new LdapConnection() { SecureSocketLayer = _ldapOptions.UseSsl };
if (_ldapOptions.UseSsl)
{
ldapConnection.UserDefinedServerCertValidationDelegate += (sender, certificate, chain, sslPolicyErrors) => true;
}
ldapConnection.Connect(_ldapOptions.ServerHost, _ldapOptions.ServerPort);
if (_ldapOptions.UseSsl)
{
ldapConnection.Bind(LdapConnection.Ldap_V3, bindUserName, bindUserPassword);
}
else
{
ldapConnection.Bind(bindUserName, bindUserPassword);
}
var ldapConnection = new LdapConnection();
ConfigureLdapConnection(ldapConnection);
ldapConnection.Connect(LdapOptions.ServerHost, LdapOptions.ServerPort);
return ldapConnection;
}
private IList<T> Query<T>(string searchBase, Dictionary<string, string> conditions) where T : class, ILdapEntry
{
var filter = LdapHelps.BuildFilter(conditions);
var result = new List<T>();
using (var ldapConnection = GetConnection())
{
var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter,
_attributes, false, null, null);
LdapMessage message;
while ((message = search.getResponse()) != null)
{
if (!(message is LdapSearchResult searchResultMessage))
{
continue;
}
var entry = searchResultMessage.Entry;
if (typeof(T) == typeof(LdapOrganization))
{
result.Add(new LdapOrganization(entry.getAttributeSet()) as T);
}
protected virtual void ConfigureLdapConnection(ILdapConnection connection)
{
if (typeof(T) == typeof(LdapUser))
{
result.Add(new LdapUser(entry.getAttributeSet()) as T);
}
}
}
return result;
}
private T QueryOne<T>(string searchBase, Dictionary<string, string> conditions) where T : class, ILdapEntry
protected virtual void AuthenticateLdapConnection(ILdapConnection connection, string username, string password)
{
var filter = LdapHelps.BuildFilter(conditions);
using (var ldapConnection = GetConnection())
{
var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter,
_attributes, false, null, null);
LdapMessage message;
while ((message = search.getResponse()) != null)
{
if (!(message is LdapSearchResult searchResultMessage))
{
continue;
}
var entry = searchResultMessage.Entry;
if (typeof(T) == typeof(LdapOrganization))
{
return new LdapOrganization(entry.getAttributeSet()) as T;
}
if (typeof(T) == typeof(LdapUser))
{
return new LdapUser(entry.getAttributeSet()) as T;
}
return null;
}
}
return null;
connection.Bind(username, password);
}
}
}
}

@ -0,0 +1,13 @@
namespace Volo.Abp.Ldap
{
public static class LdapSettingNames
{
public const string ServerHost = "Abp.Ldap.ServerHost";
public const string ServerPort = "Abp.Ldap.ServerPort";
public const string UserName = "Abp.Ldap.UserName";
public const string Password = "Abp.Ldap.Password";
}
}

@ -0,0 +1,44 @@
using Volo.Abp.Ldap.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
namespace Volo.Abp.Ldap
{
public class LdapSettingProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition(
LdapSettingNames.ServerHost,
"",
L("DisplayName:Abp.Ldap.ServerHost"),
L("Description:Abp.Ldap.ServerHost")),
new SettingDefinition(
LdapSettingNames.ServerPort,
"389",
L("DisplayName:Abp.Ldap.ServerPort"),
L("Description:Abp.Ldap.ServerPort")),
new SettingDefinition(
LdapSettingNames.UserName,
"",
L("DisplayName:Abp.Ldap.UserName"),
L("Description:Abp.Ldap.UserName")),
new SettingDefinition(
LdapSettingNames.Password,
"",
L("DisplayName:Abp.Ldap.Password"),
L("Description:Abp.Ldap.Password"),
isEncrypted: true)
);
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<LdapResource>(name);
}
}
}

@ -0,0 +1,10 @@
using Volo.Abp.Localization;
namespace Volo.Abp.Ldap.Localization
{
[LocalizationResourceName("AbpLdap")]
public class LdapResource
{
}
}

@ -0,0 +1,16 @@
{
"culture": "en",
"texts": {
"DisplayName:Abp.Ldap.ServerHost": "Server host",
"Description:Abp.Ldap.ServerHost": "Server host",
"DisplayName:Abp.Ldap.ServerPort": "Server port",
"Description:Abp.Ldap.ServerPort": "Server port",
"DisplayName:Abp.Ldap.UserName": "Username",
"Description:Abp.Ldap.UserName": "Username",
"DisplayName:Abp.Ldap.Password": "Password",
"Description:Abp.Ldap.Password": "Password"
}
}

@ -0,0 +1,13 @@
{
"culture": "tr",
"texts": {
"DisplayName:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı",
"Description:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı",
"DisplayName:Abp.Ldap.ServerPort": "Sunucu portu",
"Description:Abp.Ldap.ServerPort": "Sunucu portu",
"DisplayName:Abp.Ldap.UserName": "Kullanıcı adı",
"Description:Abp.Ldap.UserName": "Kullanıcı adı",
"DisplayName:Abp.Ldap.Password": "parola",
"Description:Abp.Ldap.Password": "parola"
}
}

@ -0,0 +1,13 @@
{
"culture": "zh-Hans",
"texts": {
"DisplayName:Abp.Ldap.ServerHost": "服务器主机",
"Description:Abp.Ldap.ServerHost": "服务器主机",
"DisplayName:Abp.Ldap.ServerPort": "服务器端口",
"Description:Abp.Ldap.ServerPort": "服务器端口",
"DisplayName:Abp.Ldap.UserName": "用户名",
"Description:Abp.Ldap.UserName": "用户名",
"DisplayName:Abp.Ldap.Password": "密码",
"Description:Abp.Ldap.Password": "密码"
}
}

@ -0,0 +1,13 @@
{
"culture": "zh-Hant",
"texts": {
"DisplayName:Abp.Ldap.ServerHost": "服務器主機",
"Description:Abp.Ldap.ServerHost": "服務器主機",
"DisplayName:Abp.Ldap.ServerPort": "服務器端口",
"Description:Abp.Ldap.ServerPort": "服務器端口",
"DisplayName:Abp.Ldap.UserName": "用戶名",
"Description:Abp.Ldap.UserName": "用戶名",
"DisplayName:Abp.Ldap.Password": "密碼",
"Description:Abp.Ldap.Password": "密碼"
}
}

@ -1,11 +0,0 @@
namespace Volo.Abp.Ldap.Modeling
{
public interface ILdapEntry
{
string ObjectCategory { get; set; }
string[] ObjectClass { get; set; }
string Name { get; set; }
string DistinguishedName { get; set; }
string CommonName { get; set; }
}
}

@ -1,7 +0,0 @@
namespace Volo.Abp.Ldap.Modeling
{
public interface ILdapOrganization : ILdapEntry
{
string OrganizationUnit { get; set; }
}
}

@ -1,11 +0,0 @@
namespace Volo.Abp.Ldap.Modeling
{
public interface ILdapUser : ILdapEntry
{
string SamAccountName { get; set; }
string UserPrincipalName { get; set; }
string DisplayName { get; set; }
string Email { get; set; }
string Phone { get; set; }
}
}

@ -1,24 +0,0 @@
using Novell.Directory.Ldap;
namespace Volo.Abp.Ldap.Modeling
{
public abstract class LdapEntryBase : ILdapEntry
{
public string ObjectCategory { get; set; }
public string[] ObjectClass { get; set; }
public string Name { get; set; }
public string CommonName { get; set; }
public string DistinguishedName { get; set; }
protected LdapEntryBase() { }
protected LdapEntryBase(LdapAttributeSet attributeSet)
{
ObjectCategory = attributeSet.getAttribute("objectCategory")?.StringValue;
ObjectClass = attributeSet.getAttribute("objectClass")?.StringValueArray;
Name = attributeSet.getAttribute("name")?.StringValue;
CommonName = attributeSet.getAttribute("cn")?.StringValue;
DistinguishedName = attributeSet.getAttribute("distinguishedName")?.StringValue;
}
}
}

@ -1,17 +0,0 @@
using Novell.Directory.Ldap;
namespace Volo.Abp.Ldap.Modeling
{
public class LdapOrganization : LdapEntryBase, ILdapOrganization
{
public string OrganizationUnit { get; set; }
public LdapOrganization() { }
public LdapOrganization(LdapAttributeSet attributeSet)
: base(attributeSet)
{
OrganizationUnit = attributeSet.getAttribute("ou")?.StringValue;
}
}
}

@ -1,25 +0,0 @@
using Novell.Directory.Ldap;
namespace Volo.Abp.Ldap.Modeling
{
public class LdapUser : LdapEntryBase, ILdapUser
{
public string SamAccountName { get; set; }
public string UserPrincipalName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public LdapUser() { }
public LdapUser( LdapAttributeSet attributeSet)
: base(attributeSet)
{
SamAccountName = attributeSet.getAttribute("sAMAccountName")?.StringValue;
UserPrincipalName = attributeSet.getAttribute("userPrincipalName")?.StringValue;
DisplayName = attributeSet.getAttribute("displayName")?.StringValue;
Email = attributeSet.getAttribute("mail")?.StringValue;
Phone = attributeSet.getAttribute("telephoneNumber")?.StringValue;
}
}
}

@ -1,171 +0,0 @@
# Volo.Abp.Ldap
# Only Authenticate(not read/write AD)
## Configure
add section in `appsettings.json`
### use SSL
```json
"LDAP": {
"ServerHost": "192.168.101.54",
"ServerPort": 636,
"UseSsl": true
}
```
### not use SSL
```json
"LDAP": {
"ServerHost": "192.168.101.54",
"ServerPort": 389,
"UseSsl": false
}
```
## Authenticate
Injecting `ILdapManager` into a class. For example:
```csharp
public class TaxAppService : ApplicationService
{
private readonly ILdapManager _ldapManager;
public TaxAppService(ILdapManager ldapManager)
{
_ldapManager = ldapManager;
}
public void Authenticate(string userName, string password)
{
var result = _ldapManager.Authenticate(userName, password);
}
}
```
- `userName` must be full domain name. E.g abc@abc.com
# Read/Write AD
## Configure
### use SSL
```json
"LDAP": {
"ServerHost": "192.168.101.54",
"ServerPort": 636,
"UseSsl": true,
"Credentials": {
"DomainUserName": "administrator@yourdomain.com.cn",
"Password": "yH.20190528"
},
"SearchBase": "DC=yourdomain,DC=com,DC=cn",
"DomainName": "yourdomain.com.cn",
"DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn"
}
```
### not use SSL
```json
"LDAP": {
"ServerHost": "192.168.101.54",
"ServerPort": 389,
"UseSsl": false,
"Credentials": {
"DomainUserName": "administrator@yourdomain.com.cn",
"Password": "yH.20190528"
},
"SearchBase": "DC=yourdomain,DC=com,DC=cn",
"DomainName": "yourdomain.com.cn",
"DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn"
}
```
- `Credentials:DomainUserName` a administrator of AD.
- `Credentials:Password` the password for the administrator.
- `SearchBase`: where search from AD.
- `DomainName`: name of you domain. no need `www`.
- `DomainDistinguishedName`: distinguished name of root domain.
## Query Organizations
```cs
// query all organizations
// filter: (&(objectClass=organizationalUnit))
_ldapManager.GetOrganizations();
// query organizations by name
// filter: (&(name=abc)(objectClass=organizationalUnit))
_ldapManager.GetOrganizations("abc");
```
## Query Organization
```csharp
// query organization by distinguished name
// filter: (&(distinguishedName=abc)(objectClass=organizationalUnit))
_ldapManager.GetOrganization("abc");
```
## Add Organization
```csharp
// use LdapOrganization
_ldapManager.AddSubOrganization("nameA", parentOrganization);
// or use OrganizationDistinguishedName
_ldapManager.AddSubOrganization("nameA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn");
```
## Query Users
```cs
// query all users
// filter: (&(objectCategory=person)(objectClass=user))
_ldapManager.GetUsers();
// query organizations by name
// filter: (&(name=abc)(objectCategory=person)(objectClass=user))
_ldapManager.GetUsers(name : "abc");
// query organizations by displayName
// filter: (&(displayName=abc)(objectCategory=person)(objectClass=user))
_ldapManager.GetUsers(displayName : "abc");
// query organization by commonName
// filter: (&(cn=abc)(objectCategory=person)(objectClass=user))
_ldapManager.GetUsers(commonName : "abc");
```
## Query User
```csharp
// query a user by distinguished name
// filter: (&(distinguishedName=abc)(objectCategory=person)(objectClass=user))
_ldapManager.GetUser("abc");
```
## Add User
```csharp
// use LdapOrganization
_ldapManager.AddUserToOrganization("nameA", "passwordA", parentOrganization);
// or use OrganizationDistinguishedName
_ldapManager.AddUserToOrganization("nameA", "passwordA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn");
```
# More
See [unit test](../../test/Volo.Abp.Ldap.Tests)

@ -9,10 +9,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Ldap\Volo.Abp.Ldap.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,24 @@
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace Volo.Abp.Ldap
{
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLdapModule),
typeof(AbpTestBaseModule)
)]
public class AbpLdapTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLdapOptions>(options =>
{
options.ServerHost = "192.168.0.3";
options.ServerPort = 389;
options.UserName = "cn=admin,dc=abp,dc=io";
options.Password = "123qwe";
});
}
}
}

@ -1,71 +0,0 @@
using System;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Ldap
{
public class Authenticate_Tests : AbpIntegratedTest<Authenticate_Tests.TestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
private readonly ILdapManager _ldapManager;
private readonly LdapTestData _testData;
public Authenticate_Tests()
{
// ReSharper disable once VirtualMemberCallInConstructor
_testData = GetRequiredService<LdapTestData>();
_ldapManager = GetRequiredService<ILdapManager>();
}
[Fact(Skip = "need environment AD ")]
public void Authenticate()
{
var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword);
result.ShouldBeTrue();
}
[Fact(Skip = "need environment AD ")]
public void Authenticate_With_Wrong_Password()
{
var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA");
result.ShouldBeFalse();
}
[DependsOn(typeof(AbpLdapModule))]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// not use ssl
// "LDAP": {
// "ServerHost": "192.168.101.54",
// "ServerPort": 389,
// "UseSSL": false
// }
// use ssl
// "LDAP": {
// "ServerHost": "192.168.101.54",
// "ServerPort": 636,
// "UseSSL": true
// }
Configure<AbpLdapOptions>(settings =>
{
settings.ServerHost = "192.168.101.54";
settings.ServerPort = 636;
settings.UseSsl = true;
});
}
}
}
}

@ -1,84 +0,0 @@
using System.Collections.Generic;
using Shouldly;
using Xunit;
namespace Volo.Abp.Ldap
{
public class LdapHelps_Tests
{
[Fact]
public void BuildCondition_With_Value()
{
// act
var res = LdapHelps.BuildCondition("objectClass", "testNameA");
// assert
res.ShouldBe("(objectClass=testNameA)");
}
[Fact]
public void BuildCondition_With_Null_Value()
{
// act
var res = LdapHelps.BuildCondition("objectClass", null);
// assert
res.ShouldBeEmpty();
}
[Fact]
public void BuildCondition_With_Empty_Value()
{
// act
var res = LdapHelps.BuildCondition("objectClass", "");
// assert
res.ShouldBeEmpty();
}
[Fact]
public void BuildCondition_With_WhiteSpace_Value()
{
// act
var res = LdapHelps.BuildCondition("objectClass", " ");
// assert
res.ShouldBeEmpty();
}
[Fact]
public void BuildFilter_With_Null_Condition()
{
// act
var res = LdapHelps.BuildFilter(null);
// assert
res.ShouldBe("(&(objectClass=*))");
}
[Fact]
public void BuildFilter_With_Empty_Condition()
{
// act
var res = LdapHelps.BuildFilter(new Dictionary<string, string>());
// assert
res.ShouldBe("(&(objectClass=*))");
}
[Fact]
public void BuildFilter_With_Condition()
{
// act
var conditions = new Dictionary<string, string>
{
{"objectClass", "testClassA"}, {"objectCategory", "testCategoryA"}, {"name", null}
};
var res = LdapHelps.BuildFilter(conditions);
// assert
res.ShouldBe("(&(objectClass=testClassA)(objectCategory=testCategoryA))");
}
}
}

@ -1,246 +1,31 @@
using System;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Ldap
{
public class LdapManager_Tests : AbpIntegratedTest<LdapManager_Tests.TestModule>
public class LdapManager_Tests : AbpIntegratedTest<AbpLdapTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
private readonly ILdapManager _ldapManager;
private readonly LdapTestData _testData;
public LdapManager_Tests()
{
// ReSharper disable once VirtualMemberCallInConstructor
_testData = GetRequiredService<LdapTestData>();
_ldapManager = GetRequiredService<ILdapManager>();
}
[Fact(Skip = "need environment AD ")]
public void GetOrganizations_With_Empty_Condition()
{
var result = _ldapManager.GetOrganizations();
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == _testData.DomainControllersName);
result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName);
}
[Fact(Skip = "need environment AD ")]
public void GetOrganizations_With_Name()
{
var result = _ldapManager.GetOrganizations(_testData.DomainControllersName);
result.ShouldNotBeNull();
result.ShouldHaveSingleItem();
result.ShouldContain(e => e.Name == _testData.DomainControllersName);
result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName);
}
[Fact(Skip = "need environment AD ")]
public void GetOrganizations_With_Non_Existent_Name()
{
var result = _ldapManager.GetOrganizations("NonExistentNameA");
result.ShouldNotBeNull();
result.ShouldBeEmpty();
}
[Fact(Skip = "need environment AD ")]
public void GetOrganization()
{
var result = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName);
result.ShouldNotBeNull();
result.Name.ShouldBe(_testData.DomainControllersName);
}
[Fact(Skip = "need environment AD ")]
public void GetOrganization_With_Non_Existent_DistinguishedName()
{
var result = _ldapManager.GetOrganization("NonExistentNameA");
result.ShouldBeNull();
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_Empty_Condition()
{
var result = _ldapManager.GetUsers();
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == _testData.AdministratorName);
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_Name()
{
var result = _ldapManager.GetUsers(name: _testData.AdministratorName);
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == _testData.AdministratorName);
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_Non_Existent_Name()
{
var result = _ldapManager.GetUsers(name: "NonExistentNameA");
result.ShouldNotBeNull();
result.ShouldBeEmpty();
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_CommonName()
{
var result = _ldapManager.GetUsers(commonName: _testData.AdministratorName);
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == _testData.AdministratorName);
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_Non_Existent_CommonName()
{
var result = _ldapManager.GetUsers(commonName: "NonExistentNameA");
result.ShouldNotBeNull();
result.ShouldBeEmpty();
}
[Fact(Skip = "need environment AD ")]
public void GetUsers_With_DisplayName()
{
var result = _ldapManager.GetUsers(displayName: _testData.AdministratorName);
result.ShouldNotBeNull();
// the administrator in AD. not have display name by default.
result.ShouldBeEmpty();
}
[Fact(Skip = "need environment AD ")]
public void GetUser()
{
var result = _ldapManager.GetUser(_testData.AdministratorDistinguishedName);
result.ShouldNotBeNull();
result.Name.ShouldBe(_testData.AdministratorName);
}
[Fact(Skip = "need environment AD ")]
public void GetUser_With_Non_Existent_DistinguishedName()
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
var result = _ldapManager.GetOrganization("NonExistentNameA");
result.ShouldBeNull();
options.UseAutofac();
}
[Fact(Skip = "need environment AD ")]
[Fact(Skip = "Required Ldap environment")]
public void Authenticate()
{
var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword);
result.ShouldBeTrue();
}
[Fact(Skip = "need environment AD ")]
public void Authenticate_With_Wrong_Password()
{
var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA");
result.ShouldBeFalse();
}
[Fact(Skip = "need environment AD ")]
public void AddSubOrganization()
{
var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName);
var randomName = $"Test_{DateTime.Now.Ticks}";
_ldapManager.AddSubOrganization(randomName, parentOrganization);
var result = _ldapManager.GetOrganizations(randomName);
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == randomName);
}
[Fact(Skip = "need environment AD ")]
public void AddSubOrganization_With_DistinguishedName()
{
var randomName = $"Test_{DateTime.Now.Ticks}";
_ldapManager.AddSubOrganization(randomName, _testData.DomainControllersDistinguishedName);
var result = _ldapManager.GetOrganizations(randomName);
result.ShouldNotBeNull();
result.ShouldContain(e => e.Name == randomName);
}
[Fact(Skip = "need environment AD ")]
public void AddOrganizationUser()
{
var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName);
var randomName = $"Test_{DateTime.Now:yyMMddHHmmss}";
_ldapManager.AddUserToOrganization(randomName, _testData.AdministratorPassword, parentOrganization);
var result = _ldapManager.GetUsers(randomName);
result.ShouldNotBeNull();
result.ShouldContain(e=>e.Name == randomName);
_ldapManager.Authenticate().ShouldBe(true);
_ldapManager.Authenticate("cn=abp,dc=abp,dc=io", "123qwe").ShouldBe(true);
_ldapManager.Authenticate("NoExists", "123qwe").ShouldBe(false);
}
[DependsOn(typeof(AbpLdapModule))]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// not use ssl
// "LDAP": {
// "ServerHost": "192.168.101.54",
// "ServerPort": 389,
// "UseSSL": false,
// "Credentials": {
// "DomainUserName": "administrator@yourdomain.com.cn",
// "Password": "yH.20190528"
// },
// "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn",
// "DomainName": "yourdomain.com.cn",
// "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn"
// }
// use ssl
// "LDAP": {
// "ServerHost": "192.168.101.54",
// "ServerPort": 636,
// "UseSSL": true,
// "Credentials": {
// "DomainUserName": "administrator@yourdomain.com.cn",
// "Password": "yH.20190528"
// },
// "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn",
// "DomainName": "yourdomain.com.cn",
// "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn"
// }
Configure<AbpLdapOptions>(settings =>
{
settings.ServerHost = "192.168.101.54";
settings.ServerPort = 636;
settings.UseSsl = true;
settings.Credentials.DomainUserName = "administrator@yourdomain.com.cn";
settings.Credentials.Password = "yH.20190528";
settings.SearchBase = "DC=yourdomain,DC=com,DC=cn";
settings.DomainName = "yourdomain.com.cn";
settings.DomainDistinguishedName = "DC=yourdomain,DC=com,DC=cn";
});
}
}
}
}
}

@ -0,0 +1,21 @@
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Ldap
{
public class LdapOptions_Tests : AbpIntegratedTest<AbpLdapTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
[Fact]
public void Should_Resolve_AbpAbpLdapOptionsFactory()
{
GetRequiredService<IOptionsFactory<AbpLdapOptions>>().ShouldBeOfType(typeof(AbpAbpLdapOptionsFactory));
}
}
}

@ -1,27 +0,0 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Ldap
{
public class LdapTestData : ISingletonDependency
{
public string AdministratorName { get; } = "Administrator";
public string AdministratorPassword { get; } = "yH.20190528";
public string AdministratorDistinguishedName { get; } = "CN=Administrator,CN=Users,DC=yourdomain,DC=com,DC=cn";
public string AdministratorDomainName { get; } = "Administrator@yourdomain.com.cn";
public string DomainControllersName = "Domain Controllers";
public string DomainControllersDistinguishedName = "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn";
public string RootDistinguishedName { get; } = "DC=yourdomain,DC=com,DC=cn";
public string Organization001Name { get; } = "Test_A";
public string Test001Name { get; } = "test001";
public string Test001Password { get; } = "yH.20190528";
public string Test001Email { get; } = "test001@yourdomain.com.cn";
public string Test002Name { get; } = "test002";
public string Test002Password { get; } = "yH.20190528";
public string Test002WrongPassword { get; } = "yH.20190529";
}
}
Loading…
Cancel
Save