mirror of https://github.com/abpframework/abp.git
95 changed files with 2670 additions and 1314 deletions
@ -0,0 +1,276 @@ |
|||||
|
# Implementing Passwordless Authentication in ASP.NET Core Identity |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
To allow a user login with a magic URL, you need to implement a custom token provider. In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering a password. |
||||
|
|
||||
|
### Source Code |
||||
|
|
||||
|
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). |
||||
|
|
||||
|
## Creating the Solution |
||||
|
|
||||
|
Before starting to the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No). |
||||
|
|
||||
|
## Step-1 |
||||
|
|
||||
|
Create a class named **PasswordlessLoginProvider** in your ***.Web** project: |
||||
|
|
||||
|
```csharp |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
|
||||
|
namespace PasswordlessAuthentication.Web |
||||
|
{ |
||||
|
public class PasswordlessLoginProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser> |
||||
|
where TUser : class |
||||
|
{ |
||||
|
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user) |
||||
|
{ |
||||
|
return Task.FromResult(false); |
||||
|
} |
||||
|
|
||||
|
//We need to override this method as well. |
||||
|
public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager, TUser user) |
||||
|
{ |
||||
|
var userId = await manager.GetUserIdAsync(user); |
||||
|
|
||||
|
return "PasswordlessLogin:" + purpose + ":" + userId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
## Step-2 |
||||
|
|
||||
|
Create **IdentityBuilderExtensions.cs** in your ***.Web** project. We will use this extension method in the `ConfigureServices`. |
||||
|
|
||||
|
```csharp |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
|
||||
|
namespace PasswordlessAuthentication.Web |
||||
|
{ |
||||
|
public static class IdentityBuilderExtensions |
||||
|
{ |
||||
|
public static IdentityBuilder AddPasswordlessLoginProvider(this IdentityBuilder builder) |
||||
|
{ |
||||
|
var userType = builder.UserType; |
||||
|
var totpProvider = typeof(PasswordlessLoginProvider<>).MakeGenericType(userType); |
||||
|
return builder.AddTokenProvider("PasswordlessLoginProvider", totpProvider); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
## Step-3 |
||||
|
|
||||
|
Add the token provider to the `Identity` middleware. To do this, find the module class (eg: `PasswordlessAuthenticationWebModule.cs` in here) in your ***.Web** project and add the below into the `ConfigureServices()` method. |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
//... |
||||
|
context.Services |
||||
|
.GetObject<IdentityBuilder>() |
||||
|
.AddDefaultTokenProviders() |
||||
|
.AddPasswordlessLoginProvider(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Step-4 |
||||
|
|
||||
|
We need to create a user interface to be able to generate the magic login link. To do this quickly, open your existing **Index.cshtml.cs** in your ***.Web** project. It's under `Pages` folder. And copy-paste the below content. |
||||
|
|
||||
|
**Index.cshtml.cs** |
||||
|
|
||||
|
```csharp |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Identity; |
||||
|
|
||||
|
namespace PasswordlessAuthentication.Web.Pages |
||||
|
{ |
||||
|
public class IndexModel : PasswordlessAuthenticationPageModel |
||||
|
{ |
||||
|
protected IdentityUserManager UserManager { get; } |
||||
|
|
||||
|
private readonly IIdentityUserRepository _userRepository; |
||||
|
|
||||
|
public string PasswordlessLoginUrl { get; set; } |
||||
|
|
||||
|
public string Email { get; set; } |
||||
|
|
||||
|
public IndexModel(IdentityUserManager userManager, IIdentityUserRepository userRepository) |
||||
|
{ |
||||
|
UserManager = userManager; |
||||
|
_userRepository = userRepository; |
||||
|
} |
||||
|
|
||||
|
public ActionResult OnGet() |
||||
|
{ |
||||
|
if (!CurrentUser.IsAuthenticated) |
||||
|
{ |
||||
|
return Redirect("/Account/Login"); |
||||
|
} |
||||
|
|
||||
|
return Page(); |
||||
|
} |
||||
|
|
||||
|
//added for passwordless authentication |
||||
|
public async Task<IActionResult> OnPostGeneratePasswordlessTokenAsync() |
||||
|
{ |
||||
|
var adminUser = await _userRepository.FindByNormalizedUserNameAsync("admin"); |
||||
|
|
||||
|
var token = await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider", |
||||
|
"passwordless-auth"); |
||||
|
|
||||
|
PasswordlessLoginUrl = Url.Action("Login", "Passwordless", |
||||
|
new {token = token, userId = adminUser.Id.ToString()}, Request.Scheme); |
||||
|
|
||||
|
return Page(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
We added `OnPostGeneratePasswordlessTokenAsync()` action to generate the link. We will generate a link for the **admin** user. Therefore, we injected `IIdentityUserRepository` to get admin user Id. Using the `UserManager.GenerateUserTokenAsync()` method, we generated a token. After that, we created the URL with the admin user Id and the token. Now we will show the `PasswordlessLoginUrl` on the page. |
||||
|
|
||||
|
## Step-5 |
||||
|
|
||||
|
Create a class named **PasswordlessAuthenticationMenus** under `Menus` folder in your ***.Web** project. And set the content as below. |
||||
|
|
||||
|
```csharp |
||||
|
namespace PasswordlessAuthentication.Web.Menus |
||||
|
{ |
||||
|
public class PasswordlessAuthenticationMenus |
||||
|
{ |
||||
|
public const string GroupName = "PasswordlessAuthentication"; |
||||
|
|
||||
|
public const string Home = GroupName + ".Home"; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Step-6 |
||||
|
|
||||
|
Open your **Index.cshtml** and set the content as below. We added a form that posts to `GeneratePasswordlessToken` action in the razor page. And it will set the `PasswordlessLoginUrl` field. |
||||
|
|
||||
|
```html |
||||
|
@page |
||||
|
@inject IHtmlLocalizer<PasswordlessAuthenticationResource> L |
||||
|
@using Microsoft.AspNetCore.Mvc.Localization |
||||
|
@using PasswordlessAuthentication.Localization |
||||
|
@using PasswordlessAuthentication.Web.Menus |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Layout |
||||
|
@model PasswordlessAuthentication.Web.Pages.IndexModel |
||||
|
|
||||
|
@{ |
||||
|
ViewBag.PageTitle = "Home"; |
||||
|
} |
||||
|
@inject IPageLayout PageLayout; |
||||
|
@{ |
||||
|
PageLayout.Content.Title = L["Home"].Value; |
||||
|
PageLayout.Content.BreadCrumb.Add(L["Menu:Home"].Value); |
||||
|
PageLayout.Content.MenuItemName = PasswordlessAuthenticationMenus.Home; |
||||
|
} |
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-body> |
||||
|
<form asp-page-handler="GeneratePasswordlessToken" method="post"> |
||||
|
|
||||
|
<abp-button button-type="Dark" type="submit">Generate passwordless token link</abp-button> |
||||
|
|
||||
|
@if (Model.PasswordlessLoginUrl != null) |
||||
|
{ |
||||
|
<abp-card class="mt-3 p-3"> |
||||
|
[@Model.PasswordlessLoginUrl](/en/commercial/latest/how-to/@Model.PasswordlessLoginUrl) |
||||
|
</abp-card> |
||||
|
} |
||||
|
|
||||
|
</form> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
``` |
||||
|
## Step-7 |
||||
|
|
||||
|
We implemented token generation infrastructure, now it's time validate the token and let the user in. To do this create a folder named `Controllers` in your ***.Web** project and create a controller, named **PasswordlessController** inside it: |
||||
|
|
||||
|
```csharp |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Security.Claims; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace PasswordlessAuthentication.Web.Controllers |
||||
|
{ |
||||
|
public class PasswordlessController : AbpController |
||||
|
{ |
||||
|
protected IdentityUserManager UserManager { get; } |
||||
|
|
||||
|
public PasswordlessController(IdentityUserManager userManager) |
||||
|
{ |
||||
|
UserManager = userManager; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IActionResult> Login(string token, string userId) |
||||
|
{ |
||||
|
var user = await UserManager.FindByIdAsync(userId); |
||||
|
|
||||
|
var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token); |
||||
|
if (!isValid) |
||||
|
{ |
||||
|
throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " + userId); |
||||
|
} |
||||
|
|
||||
|
await UserManager.UpdateSecurityStampAsync(user); |
||||
|
|
||||
|
var roles = await UserManager.GetRolesAsync(user); |
||||
|
|
||||
|
var principal = new ClaimsPrincipal( |
||||
|
new ClaimsIdentity(CreateClaims(user, roles), IdentityConstants.ApplicationScheme) |
||||
|
); |
||||
|
|
||||
|
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, principal); |
||||
|
|
||||
|
return Redirect("/"); |
||||
|
} |
||||
|
|
||||
|
private static IEnumerable<Claim> CreateClaims(IUser user, IEnumerable<string> roles) |
||||
|
{ |
||||
|
var claims = new List<Claim> |
||||
|
{ |
||||
|
new Claim("sub", user.Id.ToString()), |
||||
|
new Claim(AbpClaimTypes.UserId, user.Id.ToString()), |
||||
|
new Claim(AbpClaimTypes.Email, user.Email), |
||||
|
new Claim(AbpClaimTypes.UserName, user.UserName), |
||||
|
new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString().ToLower()), |
||||
|
}; |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(user.PhoneNumber)) |
||||
|
{ |
||||
|
claims.Add(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber)); |
||||
|
} |
||||
|
|
||||
|
foreach (var role in roles) |
||||
|
{ |
||||
|
claims.Add(new Claim(AbpClaimTypes.Role, role)); |
||||
|
} |
||||
|
|
||||
|
return claims; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we create claims of the user then call `HttpContext.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL. |
||||
|
|
||||
|
That's all! We created a passwordless login with 7 steps. |
||||
|
|
||||
|
## Source Code |
||||
|
|
||||
|
The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). |
||||
|
After Width: | Height: | Size: 6.3 KiB |
@ -1,18 +1,22 @@ |
|||||
using Microsoft.Extensions.DependencyInjection; |
using System; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
using Volo.Abp.AspNetCore.VirtualFileSystem; |
using Volo.Abp.AspNetCore.VirtualFileSystem; |
||||
|
|
||||
namespace Microsoft.AspNetCore.Builder |
namespace Microsoft.AspNetCore.Builder |
||||
{ |
{ |
||||
public static class VirtualFileSystemApplicationBuilderExtensions |
public static class VirtualFileSystemApplicationBuilderExtensions |
||||
{ |
{ |
||||
public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app) |
public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app, Action<StaticFileOptions> configure = null) |
||||
{ |
{ |
||||
return app.UseStaticFiles( |
var staticFileOptions = new StaticFileOptions |
||||
new StaticFileOptions |
{ |
||||
{ |
FileProvider = app.ApplicationServices.GetRequiredService<IWebContentFileProvider>(), |
||||
FileProvider = app.ApplicationServices.GetRequiredService<IWebContentFileProvider>() |
ContentTypeProvider = app.ApplicationServices.GetRequiredService<AbpFileExtensionContentTypeProvider>() |
||||
} |
}; |
||||
); |
|
||||
|
configure?.Invoke(staticFileOptions); |
||||
|
|
||||
|
return app.UseStaticFiles(staticFileOptions); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,39 @@ |
|||||
|
using Microsoft.AspNetCore.StaticFiles; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.VirtualFileSystem |
||||
|
{ |
||||
|
public class AbpFileExtensionContentTypeProvider : IContentTypeProvider, ITransientDependency |
||||
|
{ |
||||
|
protected AbpAspNetCoreContentOptions Options { get; } |
||||
|
|
||||
|
public AbpFileExtensionContentTypeProvider(IOptions<AbpAspNetCoreContentOptions> abpAspNetCoreContentOptions) |
||||
|
{ |
||||
|
Options = abpAspNetCoreContentOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public bool TryGetContentType(string subpath, out string contentType) |
||||
|
{ |
||||
|
var extension = GetExtension(subpath); |
||||
|
if (extension == null) |
||||
|
{ |
||||
|
contentType = null; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return Options.ContentTypeMaps.TryGetValue(extension, out contentType); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetExtension(string path) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(path)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var index = path.LastIndexOf('.'); |
||||
|
return index < 0 ? null : path.Substring(index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
|
||||
|
namespace Volo.Abp.Domain.Entities |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public abstract class BasicAggregateRoot : Entity, |
||||
|
IAggregateRoot, |
||||
|
IGeneratesDomainEvents |
||||
|
{ |
||||
|
private readonly ICollection<object> _distributedEvents = new Collection<object>(); |
||||
|
private readonly ICollection<object> _localEvents = new Collection<object>(); |
||||
|
|
||||
|
public virtual IEnumerable<object> GetLocalEvents() |
||||
|
{ |
||||
|
return _localEvents; |
||||
|
} |
||||
|
|
||||
|
public virtual IEnumerable<object> GetDistributedEvents() |
||||
|
{ |
||||
|
return _distributedEvents; |
||||
|
} |
||||
|
|
||||
|
public virtual void ClearLocalEvents() |
||||
|
{ |
||||
|
_localEvents.Clear(); |
||||
|
} |
||||
|
|
||||
|
public virtual void ClearDistributedEvents() |
||||
|
{ |
||||
|
_distributedEvents.Clear(); |
||||
|
} |
||||
|
|
||||
|
protected virtual void AddLocalEvent(object eventData) |
||||
|
{ |
||||
|
_localEvents.Add(eventData); |
||||
|
} |
||||
|
|
||||
|
protected virtual void AddDistributedEvent(object eventData) |
||||
|
{ |
||||
|
_distributedEvents.Add(eventData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Serializable] |
||||
|
public abstract class BasicAggregateRoot<TKey> : Entity<TKey>, |
||||
|
IAggregateRoot<TKey>, |
||||
|
IGeneratesDomainEvents |
||||
|
{ |
||||
|
private readonly ICollection<object> _distributedEvents = new Collection<object>(); |
||||
|
private readonly ICollection<object> _localEvents = new Collection<object>(); |
||||
|
|
||||
|
protected BasicAggregateRoot() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected BasicAggregateRoot(TKey id) |
||||
|
: base(id) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public virtual IEnumerable<object> GetLocalEvents() |
||||
|
{ |
||||
|
return _localEvents; |
||||
|
} |
||||
|
|
||||
|
public virtual IEnumerable<object> GetDistributedEvents() |
||||
|
{ |
||||
|
return _distributedEvents; |
||||
|
} |
||||
|
|
||||
|
public virtual void ClearLocalEvents() |
||||
|
{ |
||||
|
_localEvents.Clear(); |
||||
|
} |
||||
|
|
||||
|
public virtual void ClearDistributedEvents() |
||||
|
{ |
||||
|
_distributedEvents.Clear(); |
||||
|
} |
||||
|
|
||||
|
protected virtual void AddLocalEvent(object eventData) |
||||
|
{ |
||||
|
_localEvents.Add(eventData); |
||||
|
} |
||||
|
|
||||
|
protected virtual void AddDistributedEvent(object eventData) |
||||
|
{ |
||||
|
_distributedEvents.Add(eventData); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 System; |
||||
using Volo.Abp.Autofac; |
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.Modularity; |
||||
|
using Volo.Abp.Settings; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
namespace Volo.Abp.Ldap |
namespace Volo.Abp.Ldap |
||||
{ |
{ |
||||
[DependsOn( |
[DependsOn( |
||||
typeof(AbpAutofacModule) |
typeof(AbpSettingsModule), |
||||
)] |
typeof(AbpVirtualFileSystemModule), |
||||
|
typeof(AbpLocalizationModule))] |
||||
public class AbpLdapModule : AbpModule |
public class AbpLdapModule : AbpModule |
||||
{ |
{ |
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
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(); |
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"); |
||||
|
}); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; |
namespace Volo.Abp.Ldap |
||||
using Volo.Abp.Ldap.Modeling; |
|
||||
|
|
||||
namespace Volo.Abp.Ldap |
|
||||
{ |
{ |
||||
public interface ILdapManager |
public interface ILdapManager |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// query the specified organizations.
|
/// Authenticate with default username/password
|
||||
///
|
|
||||
/// 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
|
|
||||
///
|
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="distinguishedName"></param>
|
|
||||
/// <returns></returns>
|
/// <returns></returns>
|
||||
LdapOrganization GetOrganization(string distinguishedName); |
bool Authenticate(); |
||||
|
|
||||
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); |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Authenticate
|
/// Authenticate with specified username/password
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="userDomainName">E.g administrator@yourdomain.com.cn </param>
|
|
||||
/// <param name="password"></param>
|
|
||||
/// <returns></returns>
|
/// <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 System; |
||||
using Microsoft.Extensions.Options; |
using Microsoft.Extensions.Options; |
||||
using Novell.Directory.Ldap; |
using Novell.Directory.Ldap; |
||||
using System.Collections.Generic; |
using Microsoft.Extensions.Logging; |
||||
using System.Text; |
using Microsoft.Extensions.Logging.Abstractions; |
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
using Volo.Abp.ExceptionHandling; |
|
||||
using Volo.Abp.Ldap.Exceptions; |
|
||||
using Volo.Abp.Ldap.Modeling; |
|
||||
|
|
||||
namespace Volo.Abp.Ldap |
namespace Volo.Abp.Ldap |
||||
{ |
{ |
||||
public class LdapManager : ILdapManager, ITransientDependency |
public class LdapManager : ILdapManager, ITransientDependency |
||||
{ |
{ |
||||
private readonly string _searchBase; |
public ILogger<LdapManager> Logger { get; set; } |
||||
private readonly AbpLdapOptions _ldapOptions; |
protected AbpLdapOptions LdapOptions { get; } |
||||
private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory; |
|
||||
|
|
||||
private readonly string[] _attributes = |
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions) |
||||
{ |
{ |
||||
"objectCategory", "objectClass", "cn", "name", "distinguishedName", |
LdapOptions = ldapSettingsOptions.Value; |
||||
"ou", |
|
||||
"sAMAccountName", "userPrincipalName", "telephoneNumber", "mail" |
|
||||
}; |
|
||||
|
|
||||
public LdapManager(IOptions<AbpLdapOptions> ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory) |
Logger = NullLogger<LdapManager>.Instance; |
||||
{ |
|
||||
_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); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
public void AddSubOrganization(string organizationName, string parentDistinguishedName) |
public virtual bool Authenticate() |
||||
{ |
{ |
||||
organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName)); |
return Authenticate(LdapOptions.UserName, LdapOptions.Password); |
||||
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); |
|
||||
} |
} |
||||
|
|
||||
/// <summary>
|
public bool Authenticate(string username, string password) |
||||
/// 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) |
|
||||
{ |
{ |
||||
try |
try |
||||
{ |
{ |
||||
using (GetConnection(userDomainName, password)) |
var conn = CreateLdapConnection(); |
||||
{ |
AuthenticateLdapConnection(conn, username, password); |
||||
return true; |
return true; |
||||
} |
|
||||
} |
} |
||||
catch (Exception ex) |
catch (Exception ex) |
||||
{ |
{ |
||||
using (var scope = _hybridServiceScopeFactory.CreateScope()) |
Logger.LogException(ex); |
||||
{ |
|
||||
scope.ServiceProvider |
|
||||
.GetRequiredService<IExceptionNotifier>() |
|
||||
.NotifyAsync(ex); |
|
||||
} |
|
||||
|
|
||||
return false; |
return false; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
#endregion
|
protected virtual ILdapConnection CreateLdapConnection() |
||||
|
|
||||
private ILdapConnection GetConnection(string bindUserName = null, string bindUserPassword = null) |
|
||||
{ |
{ |
||||
// bindUserName/bindUserPassword only be used when authenticate
|
var ldapConnection = new LdapConnection(); |
||||
bindUserName = bindUserName ?? _ldapOptions.Credentials.DomainUserName; |
ConfigureLdapConnection(ldapConnection); |
||||
bindUserPassword = bindUserPassword ?? _ldapOptions.Credentials.Password; |
ldapConnection.Connect(LdapOptions.ServerHost, LdapOptions.ServerPort); |
||||
|
|
||||
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); |
|
||||
} |
|
||||
return ldapConnection; |
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; |
protected virtual void ConfigureLdapConnection(ILdapConnection connection) |
||||
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); |
|
||||
} |
|
||||
|
|
||||
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); |
connection.Bind(username, password); |
||||
|
|
||||
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; |
|
||||
} |
} |
||||
|
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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) |
|
||||
@ -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 System; |
||||
using Shouldly; |
using Shouldly; |
||||
using Volo.Abp.Modularity; |
|
||||
using Volo.Abp.Testing; |
using Volo.Abp.Testing; |
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace Volo.Abp.Ldap |
namespace Volo.Abp.Ldap |
||||
{ |
{ |
||||
|
public class LdapManager_Tests : AbpIntegratedTest<AbpLdapTestModule> |
||||
public class LdapManager_Tests : AbpIntegratedTest<LdapManager_Tests.TestModule> |
|
||||
{ |
{ |
||||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|
||||
{ |
|
||||
options.UseAutofac(); |
|
||||
} |
|
||||
|
|
||||
private readonly ILdapManager _ldapManager; |
private readonly ILdapManager _ldapManager; |
||||
private readonly LdapTestData _testData; |
|
||||
|
|
||||
public LdapManager_Tests() |
public LdapManager_Tests() |
||||
{ |
{ |
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
|
||||
_testData = GetRequiredService<LdapTestData>(); |
|
||||
_ldapManager = GetRequiredService<ILdapManager>(); |
_ldapManager = GetRequiredService<ILdapManager>(); |
||||
} |
} |
||||
|
|
||||
[Fact(Skip = "need environment AD ")] |
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
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() |
|
||||
{ |
{ |
||||
var result = _ldapManager.GetOrganization("NonExistentNameA"); |
options.UseAutofac(); |
||||
|
|
||||
result.ShouldBeNull(); |
|
||||
} |
} |
||||
|
|
||||
[Fact(Skip = "need environment AD ")] |
[Fact(Skip = "Required Ldap environment")] |
||||
public void Authenticate() |
public void Authenticate() |
||||
{ |
{ |
||||
var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword); |
_ldapManager.Authenticate().ShouldBe(true); |
||||
|
_ldapManager.Authenticate("cn=abp,dc=abp,dc=io", "123qwe").ShouldBe(true); |
||||
result.ShouldBeTrue(); |
_ldapManager.Authenticate("NoExists", "123qwe").ShouldBe(false); |
||||
} |
|
||||
|
|
||||
[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); |
|
||||
} |
} |
||||
|
|
||||
[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"; |
|
||||
} |
|
||||
} |
|
||||
4
modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs → modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs
4
modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs → modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs
0
modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs → modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs
0
modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs → modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs
File diff suppressed because it is too large
@ -0,0 +1,23 @@ |
|||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations |
||||
|
{ |
||||
|
public partial class IdentityUserIsExternal : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.AddColumn<bool>( |
||||
|
name: "IsExternal", |
||||
|
table: "AbpUsers", |
||||
|
nullable: false, |
||||
|
defaultValue: false); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropColumn( |
||||
|
name: "IsExternal", |
||||
|
table: "AbpUsers"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,34 +1,41 @@ |
|||||
using System.Threading; |
using System; |
||||
|
using System.Threading; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Microsoft.Extensions.Hosting; |
using Microsoft.Extensions.Hosting; |
||||
using Serilog; |
|
||||
using Volo.Abp; |
using Volo.Abp; |
||||
|
|
||||
namespace MyCompanyName.MyProjectName |
namespace MyCompanyName.MyProjectName |
||||
{ |
{ |
||||
public class MyProjectNameHostedService : IHostedService |
public class MyProjectNameHostedService : IHostedService |
||||
{ |
{ |
||||
public Task StartAsync(CancellationToken cancellationToken) |
private readonly IAbpApplicationWithExternalServiceProvider _application; |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
private readonly HelloWorldService _helloWorldService; |
||||
|
|
||||
|
public MyProjectNameHostedService( |
||||
|
IAbpApplicationWithExternalServiceProvider application, |
||||
|
IServiceProvider serviceProvider, |
||||
|
HelloWorldService helloWorldService) |
||||
{ |
{ |
||||
using (var application = AbpApplicationFactory.Create<MyProjectNameModule>(options => |
_application = application; |
||||
{ |
_serviceProvider = serviceProvider; |
||||
options.UseAutofac(); //Autofac integration
|
_helloWorldService = helloWorldService; |
||||
options.Services.AddLogging(c => c.AddSerilog()); |
} |
||||
})) |
|
||||
{ |
|
||||
application.Initialize(); |
|
||||
|
|
||||
//Resolve a service and use it
|
public Task StartAsync(CancellationToken cancellationToken) |
||||
var helloWorldService = application.ServiceProvider.GetService<HelloWorldService>(); |
{ |
||||
helloWorldService.SayHello(); |
_application.Initialize(_serviceProvider); |
||||
|
|
||||
application.Shutdown(); |
_helloWorldService.SayHello(); |
||||
} |
|
||||
|
|
||||
return Task.CompletedTask; |
return Task.CompletedTask; |
||||
} |
} |
||||
|
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
public Task StopAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
_application.Shutdown(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue