mirror of https://github.com/abpframework/abp.git
237 changed files with 3326 additions and 215 deletions
@ -0,0 +1,30 @@ |
|||||
|
# ABP dokumentace |
||||
|
|
||||
|
ABP je **open source aplikační framework** se zaměřením na vývoj webových aplikací založených na ASP.NET Core, zároveň ho však lze využít i k vývoji jiných typů aplikací. |
||||
|
|
||||
|
K procházení dokumentace využijte navigační nabídky vlevo. |
||||
|
|
||||
|
## Stav projektu |
||||
|
|
||||
|
ABP je **novou generací** open source frameworku [ASP.NET Boilerplate](https://aspnetboilerplate.com/). V současné době je v ranné fázi a není připraven k ostrému nasazení. Na dokumentaci se stále pracuje a ještě zdaleka není dokončena. |
||||
|
|
||||
|
Pro aplikace na krátkodobé a ostré nasazení je doporučeno použít [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework, který je bohatý na funkce, vyspělý, aktivně udržovaný a aktuální. |
||||
|
|
||||
|
## Začínáme |
||||
|
|
||||
|
Nejsnazší cestou jak začít nový projekt s ABP je užití startovací šablony: |
||||
|
|
||||
|
* [ASP.NET Core MVC Šablona](Getting-Started-AspNetCore-MVC-Template.md) |
||||
|
|
||||
|
Pokud chcete začít od nuly (s prázdným projektem) tak manuálně nainstalujte ABP Framework s pomocí následujících tutoriálů: |
||||
|
|
||||
|
* [Konzolová Aplikace](Getting-Started-Console-Application.md) |
||||
|
* [ASP.NET Core Web Aplikace](Getting-Started-AspNetCore-Application.md) |
||||
|
|
||||
|
## Zdrojový kód |
||||
|
|
||||
|
ABP je hostovaný na GitHub. Zobrazit [zdrojový kód](https://github.com/abpframework/abp). |
||||
|
|
||||
|
## Chcete přispět? |
||||
|
|
||||
|
ABP je komunitně řízený open source projekt. Podívejte se na [průvodce pro přispěvatele](Contribution/Index.md) pokud chcete být součástí tohoto projektu. |
||||
@ -0,0 +1,292 @@ |
|||||
|
{ |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Začínáme", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Ze startovacích šablon", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "ASP.NET Core MVC", |
||||
|
"path": "Getting-Started-AspNetCore-MVC-Template.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Z prázdných projektů", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "S ASP.NET Core Web aplikací", |
||||
|
"path": "Getting-Started-AspNetCore-Application.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "S konzolovou aplikací", |
||||
|
"path": "Getting-Started-Console-Application.md" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Tutoriály", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Vývoj aplikace", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "S ASP.NET Core MVC", |
||||
|
"path": "Tutorials/AspNetCore-Mvc/Part-I.md" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "CLI", |
||||
|
"path": "CLI.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Základy", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Vkládání závislostí", |
||||
|
"path": "Dependency-Injection.md", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "AutoFac integrace", |
||||
|
"path": "Autofac-Integration.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Virtuální systém souborů", |
||||
|
"path": "Virtual-File-System.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Lokalizace", |
||||
|
"path": "Localization.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Zpracování výjimek", |
||||
|
"path": "Exception-Handling.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Validace" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Autorizace" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Ukládání do mezipaměti" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Audit" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Správa nastavení" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Události", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Event bus (místní)" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Distribuovaný event bus", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "RabbitMQ integrace" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Služby", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Serializace objektu" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Serializace JSON" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Emailování" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "GUIDy" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Vláknování" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Časování" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Multitenance", |
||||
|
"path": "Multi-Tenancy.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Vývoj modulů", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Základy", |
||||
|
"path": "Module-Development-Basics.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Zásuvné moduly" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Nejlepší praktiky", |
||||
|
"path": "Best-Practices/Index.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Domain driven design", |
||||
|
"path": "Domain-Driven-Design.md", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Doménová vrstva", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Entity & agregované kořeny", |
||||
|
"path": "Entities.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Hodnotové objekty" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Repozitáře", |
||||
|
"path": "Repositories.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Doménové služby" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Specifikace" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Aplikační vrstva", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Aplikační služby", |
||||
|
"path": "Application-Services.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Objekty přenosu dat" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Jednotka práce" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "ASP.NET Core", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "API", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Automatické API řadiče", |
||||
|
"path": "AspNetCore/Auto-API-Controllers.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Dynamičtí C# API klienti", |
||||
|
"path": "AspNetCore/Dynamic-CSharp-API-Clients.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Uživatelské rozhraní", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Správa klientských balíčků", |
||||
|
"path": "AspNetCore/Client-Side-Package-Management.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Svazování & minifikace", |
||||
|
"path": "AspNetCore/Bundling-Minification.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Tag pomocníci", |
||||
|
"path": "Tag-Helpers.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Motivy", |
||||
|
"path": "AspNetCore/Theming.md" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Přístup k datům", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Entity Framework Core integrace", |
||||
|
"path": "Entity-Framework-Core.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "MongoDB integrace", |
||||
|
"path": "MongoDB.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Pozadí", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Úkony na pozadí", |
||||
|
"path": "Background-Jobs.md", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Hangfire integrace", |
||||
|
"path": "Background-Jobs-Hangfire.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "RabbitMQ integrace", |
||||
|
"path": "Background-Jobs-RabbitMq.md" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Vzorky", |
||||
|
"items": [ |
||||
|
{ |
||||
|
"text": "Mikroslužby demo", |
||||
|
"path": "Samples/Microservice-Demo.md" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Moduly aplikace", |
||||
|
"path": "Modules/Index.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Architektura mikroslužby", |
||||
|
"path": "Microservice-Architecture.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Testování" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Noční sestavení", |
||||
|
"path": "Nightly-Builds.md" |
||||
|
}, |
||||
|
{ |
||||
|
"text": "Průvodce pro přispěvatele", |
||||
|
"path": "Contribution/Index.md" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
## Dynamic JavaScript API Clients |
||||
|
|
||||
|
TODO |
||||
@ -1,6 +1,160 @@ |
|||||
# MVC Module Startup Template |
# MVC Module Startup Template |
||||
|
|
||||
TODO |
This template can be used to create a **reusable [application module](../Modules/Index.md)** based on the [module development best practices & conventions](../Best-Practices/Index.md). It is also suitable for creating **microservices** (with or without UI). |
||||
|
|
||||
|
## How to Start With? |
||||
|
|
||||
|
You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can directly create & download from the [Get Started](https://abp.io/get-started) page. CLI approach is used here. |
||||
|
|
||||
|
First, install the ABP CLI if you haven't installed before: |
||||
|
|
||||
|
```bash |
||||
|
dotnet tool install -g Volo.Abp.Cli |
||||
|
``` |
||||
|
|
||||
|
Then use the `abp new` command in an empty folder to create a new solution: |
||||
|
|
||||
|
```bash |
||||
|
abp new Acme.IssueManagement -t mvc-module |
||||
|
``` |
||||
|
|
||||
|
- `Acme.IssueManagement` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. |
||||
|
|
||||
|
### Without User Interface |
||||
|
|
||||
|
The template comes with a UI by default. You can use `--no-ui` option to not include the UI layer. |
||||
|
|
||||
|
````bash |
||||
|
abp new Acme.IssueManagement -t mvc-module --no-ui |
||||
|
```` |
||||
|
|
||||
|
## Solution Structure |
||||
|
|
||||
|
Based on the options you've specified, you will get a slightly different solution structure. If you don't specify any option, you will have a solution like shown below: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Projects are organized as `src`, `test` and `host` folders: |
||||
|
|
||||
|
* `src` folder contains the actual module which is layered based on [DDD](../Domain-Driven-Design.md) principles. |
||||
|
* `test` folder contains unit & integration tests. |
||||
|
* `host` folder contains applications with different configurations to demonstrate how to host the module in an application. These are not a part of the module, but useful on development. |
||||
|
|
||||
|
The diagram below shows the layers & project dependencies of the module: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Each section below will explain the related project & its dependencies. |
||||
|
|
||||
|
### .Domain.Shared Project |
||||
|
|
||||
|
This project contains constants, enums and other objects these are actually a part of the domain layer, but needed to be used by all layers/projects in the solution. |
||||
|
|
||||
|
An `IssueType` enum and an `IssueConts` class (which may have some constant fields for the `Issue` entity, like `MaxTitleLength`) are good candidates for this project. |
||||
|
|
||||
|
- This project has no dependency to other projects in the solution. All other projects depend on this directly or indirectly. |
||||
|
|
||||
|
### .Domain Project |
||||
|
|
||||
|
This is the domain layer of the solution. It mainly contains [entities, aggregate roots](../Entities.md), [domain services](../Domain-Services.md), [value types](../Value-Types.md), [repository interfaces](../Repositories.md) and other domain objects. |
||||
|
|
||||
|
An `Issue` entity, an `IssueManager` domain service and an `IIssueRepository` interface are good candidates for this project. |
||||
|
|
||||
|
- Depends on the `.Domain.Shared` because it uses constants, enums and other objects defined in that project. |
||||
|
|
||||
|
### .Application.Contracts Project |
||||
|
|
||||
|
This project mainly contains [application service](../Application-Services.md) **interfaces** and [Data Transfer Objects](../Data-Transfer-Objects.md) (DTO) of the application layer. It does exists to separate interface & implementation of the application layer. In this way, the interface project can be shared to the clients as a contract package. |
||||
|
|
||||
|
An `IIssueAppService` interface and an `IssueCreationDto` class are good candidates for this project. |
||||
|
|
||||
|
- Depends on the `.Domain.Shared` because it may use constants, enums and other shared objects of this project in the application service interfaces and DTOs. |
||||
|
|
||||
|
### .Application Project |
||||
|
|
||||
|
This project contains the [application service](../Application-Services.md) **implementations** of the interfaces defined in the `.Application.Contracts` project. |
||||
|
|
||||
|
An `IssueAppService` class is a good candidate for this project. |
||||
|
|
||||
|
- Depends on the `.Application.Contracts` project to be able to implement the interfaces and use the DTOs. |
||||
|
- Depends on the `.Domain` project to be able to use domain objects (entities, repository interfaces... etc.) to perform the application logic. |
||||
|
|
||||
|
### .EntityFrameworkCore Project |
||||
|
|
||||
|
This is the integration project for EF Core. It defines the `DbContext` and implements repository interfaces defined in the `.Domain` project. |
||||
|
|
||||
|
- Depends on the `.Domain` project to be able to reference to entities and repository interfaces. |
||||
|
|
||||
|
> You can delete this project if you don't want to support EF Core for your module. |
||||
|
|
||||
|
### .MongoDB Project |
||||
|
|
||||
|
This is the integration project for MongoDB. |
||||
|
|
||||
|
- Depends on the `.Domain` project to be able to reference to entities and repository interfaces. |
||||
|
|
||||
|
> You can delete this project if you don't want to support MongoDB for your module. |
||||
|
|
||||
|
### Test Projects |
||||
|
|
||||
|
The solution has multiple test projects, one for each layer: |
||||
|
|
||||
|
- `.Domain.Tests` is used to test the domain layer. |
||||
|
- `.Application.Tests` is used to test the application layer. |
||||
|
- `.EntityFrameworkCore.Tests` is used to test EF Core configuration and custom repositories. |
||||
|
- `.MongoDB.Tests` is used to test EF Core configuration and custom repositories. |
||||
|
- `.TestBase` is a base (shared) project for all tests. |
||||
|
|
||||
|
In addition, `.HttpApi.Client.ConsoleTestApp` is a console application (not an automated test project) which demonstrate the usage of HTTP APIs from a Dotnet application. |
||||
|
|
||||
|
Test projects are prepared for integration testing; |
||||
|
|
||||
|
- It is fully integrated to ABP framework and all services in your application. |
||||
|
- It uses SQLite in-memory database for EF Core. For MongoDB, it uses the [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library. |
||||
|
- Authorization is disabled, so any application service can be easily used in tests. |
||||
|
|
||||
|
You can still create unit tests for your classes which will be harder to write (because you will need to prepare mock/fake objects), but faster to run (because it only tests a single class and skips all initialization process). |
||||
|
|
||||
|
> Domain & Application tests are using EF Core. If you remove EF Core integration or you want to use MongoDB for testing these layers, you should manually change project references & module dependencies. |
||||
|
|
||||
|
### Host Projects |
||||
|
|
||||
|
The solution has a few host applications to run your module on development. Host applications are used to run your module in a fully configured application. It is useful on development. Host applications includes some other modules in addition to the module being developed: |
||||
|
|
||||
|
* |
||||
|
|
||||
|
Host applications support two types of scenarios. |
||||
|
|
||||
|
#### Single (Unified) Application Scenario |
||||
|
|
||||
|
If your module has a UI, then `.Web.Unified` application is used to host the UI and API on a single point. It has its own `appsettings.json` file (that includes the database connection string) and EF Core database migrations. |
||||
|
|
||||
|
For the `.Web.Unified` application, there is a single database, named `YourProjectName_Unified` (like *IssueManagement_Unified* for this sample). |
||||
|
|
||||
|
> If you've selected the `--no-ui` option, this project will not be in your solution. |
||||
|
|
||||
|
##### How to Run? |
||||
|
|
||||
|
Set it as the startup project, run `Update-Database` command for the EF Core from Package Manager Console and run your application. Default username is `admin` and password is `1q2w3E*`. |
||||
|
|
||||
|
#### Separated Deployment & Databases Scenario |
||||
|
|
||||
|
In this scenario, there are three applications; |
||||
|
|
||||
|
* `.IdentityServer` application is an authentication server used by other applications. It has its own `appsettings.json` that contains database connection and other configurations. |
||||
|
* `.HttpApi.Host` hosts the HTTP API of the module. It has its own `appsettings.json` that contains database connections and other configurations. |
||||
|
* `.Web.Host` host the UI of the module. This project contains an `appsettings.json` file, but it does not have a connection string because it never connects to the database. Instead, it mainly contains endpoint of the remote API server and the authentication server. |
||||
|
|
||||
|
The diagram below shows the relation of the applications: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
`.Web.Host` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the `.IdentityServer`. Then uses the access token to call the `.HttpApi.Host`. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. |
||||
|
|
||||
|
##### How to Run? |
||||
|
|
||||
|
You should run the application with the given order: |
||||
|
|
||||
|
- First, run the `.IdentityServer` since other applications depends on it. |
||||
|
- Then run the `.HttpApi.Host` since it is used by the `.Web.Host` application. |
||||
|
- Finally, you can run the `.Web.Host` project and login to the application using `admin` as the username and `1q2w3E*` as the password. |
||||
@ -0,0 +1,3 @@ |
|||||
|
## Value Objects |
||||
|
|
||||
|
TODO |
||||
@ -1,3 +0,0 @@ |
|||||
## Value Types |
|
||||
|
|
||||
TODO |
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,3 @@ |
|||||
|
## Dynamic JavaScript API Clients |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,3 @@ |
|||||
|
## Value Objects |
||||
|
|
||||
|
TODO |
||||
@ -1,3 +0,0 @@ |
|||||
## Value Types |
|
||||
|
|
||||
TODO |
|
||||
|
Before Width: | Height: | Size: 24 KiB |
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"GivenTenantIsNotAvailable": "Podany tenant jest niedostępny: {0}", |
||||
|
"Tenant": "Tenant", |
||||
|
"Switch": "zmień", |
||||
|
"Name": "Nazwa", |
||||
|
"SwitchTenantHint": "Pozostaw pole nazwy puste, aby przełączyć się na stronę hosta.", |
||||
|
"NotSelected": "Nie wybrano" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"GivenTenantIsNotAvailable": "Người thuê không có sẵn: {0}", |
||||
|
"Tenant": "Người thuê", |
||||
|
"Switch": "Chuyển đổi", |
||||
|
"Name": "Tên", |
||||
|
"SwitchTenantHint": "Để trống trường tên để chuyển sang phía máy chủ.", |
||||
|
"NotSelected": "Không được chọn" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FreezeUi |
||||
|
{ |
||||
|
[DependsOn(typeof(CoreScriptContributor))] |
||||
|
public class FreezeUiScriptContributor : BundleContributor |
||||
|
{ |
||||
|
public override void ConfigureBundle(BundleConfigurationContext context) |
||||
|
{ |
||||
|
context.Files.AddIfNotContains("/libs/freeze-ui/freeze-ui.min.css"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FreezeUi |
||||
|
{ |
||||
|
[DependsOn(typeof(BootstrapStyleContributor))] |
||||
|
public class FreezeUiStyleContributor : BundleContributor |
||||
|
{ |
||||
|
public override void ConfigureBundle(BundleConfigurationContext context) |
||||
|
{ |
||||
|
context.Files.AddIfNotContains("/libs/freeze-ui/freeze-ui.min.css"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
var abp = abp || {}; |
||||
|
(function ($) { |
||||
|
if (!window.FreezeUI || !$) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
/* MESSAGE **************************************************/ |
||||
|
/*Package from https://alexradulescu.github.io/freeze-ui/*/ |
||||
|
|
||||
|
abp.ui = abp.ui || {}; |
||||
|
|
||||
|
/* UI BLOCK */ |
||||
|
|
||||
|
abp.ui.block = function (elm) { |
||||
|
if (elm) { |
||||
|
window.FreezeUI({ selector: elm }); |
||||
|
} else { |
||||
|
window.FreezeUI(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
abp.ui.unblock = function () { |
||||
|
window.UnFreezeUI(); |
||||
|
}; |
||||
|
|
||||
|
/* UI BUSY */ |
||||
|
|
||||
|
abp.ui.setBusy = function (element, text, freezeDelay) { |
||||
|
var opt = { text : text ? text : ' ' }; |
||||
|
|
||||
|
if (element) { |
||||
|
opt.element = element; |
||||
|
} |
||||
|
|
||||
|
if (freezeDelay) { |
||||
|
opt.freezeDelay = freezeDelay; |
||||
|
} |
||||
|
|
||||
|
window.FreezeUI(opt); |
||||
|
}; |
||||
|
|
||||
|
abp.ui.clearBusy = function () { |
||||
|
window.UnFreezeUI(); |
||||
|
}; |
||||
|
|
||||
|
})(jQuery); |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssemblyName>Volo.Abp.Ldap</AssemblyName> |
||||
|
<PackageId>Volo.Abp.Ldap</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="2.3.8" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.Ldap |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpAutofacModule) |
||||
|
)] |
||||
|
public class AbpLdapModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
Configure<LdapOptions>(configuration.GetSection("LDAP")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.Ldap.Exceptions |
||||
|
{ |
||||
|
public class OrganizationNotExistException : BusinessException |
||||
|
{ |
||||
|
public OrganizationNotExistException(string distinguishedName) |
||||
|
: base("LDAP:000001", $"the organization distinguished named {distinguishedName} does not exist.") |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Ldap.Modeling; |
||||
|
|
||||
|
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
|
||||
|
///
|
||||
|
/// </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); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Authenticate
|
||||
|
/// </summary>
|
||||
|
/// <param name="userDomainName">E.g administrator@yourdomain.com.cn </param>
|
||||
|
/// <param name="password"></param>
|
||||
|
/// <returns></returns>
|
||||
|
bool Authenticate(string userDomainName, string password); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.Ldap |
||||
|
{ |
||||
|
public class LdapCredentials |
||||
|
{ |
||||
|
public string DomainUserName { get; set; } |
||||
|
|
||||
|
public string Password { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
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})"; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,332 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Novell.Directory.Ldap; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
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 LdapOptions _ldapOptions; |
||||
|
|
||||
|
private readonly string[] _attributes = |
||||
|
{ |
||||
|
"objectCategory", "objectClass", "cn", "name", "distinguishedName", |
||||
|
"ou", |
||||
|
"sAMAccountName", "userPrincipalName", "telephoneNumber", "mail" |
||||
|
}; |
||||
|
|
||||
|
public LdapManager(IOptions<LdapOptions> ldapSettingsOptions) |
||||
|
{ |
||||
|
_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) |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
/// <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) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using (GetConnection(userDomainName, password)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
private ILdapConnection GetConnection(string bindUserName = null, string bindUserPassword = null) |
||||
|
{ |
||||
|
// 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); |
||||
|
} |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
namespace Volo.Abp.Ldap |
||||
|
{ |
||||
|
public class LdapOptions |
||||
|
{ |
||||
|
public string ServerHost { get; set; } |
||||
|
|
||||
|
public int ServerPort { get; set; } |
||||
|
|
||||
|
public bool UseSsl { get; set; } |
||||
|
|
||||
|
public string SearchBase { get; set; } |
||||
|
|
||||
|
public string DomainName { get; set; } |
||||
|
|
||||
|
public string DomainDistinguishedName { get; set; } |
||||
|
|
||||
|
public LdapCredentials Credentials { get; set; } |
||||
|
|
||||
|
public LdapOptions() |
||||
|
{ |
||||
|
Credentials = new LdapCredentials(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Ldap.Modeling |
||||
|
{ |
||||
|
public interface ILdapOrganization : ILdapEntry |
||||
|
{ |
||||
|
string OrganizationUnit { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,171 @@ |
|||||
|
# 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,20 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"'{0}' and '{1}' do not match.": "'{0}' i '{1}' nie są takie same.", |
||||
|
"The {0} field is not a valid credit card number.": "Pole {0} nie jest poprawnym numerem karty kredytowej.", |
||||
|
"{0} is not valid.": "{0} jest niepoprawne.", |
||||
|
"The {0} field is not a valid e-mail address.": "Pole {0} nie jest poprawnym adresem e-mail.", |
||||
|
"The {0} field only accepts files with the following extensions: {1}": "Polr {0} przyjmuje pliki tylko z następującymi rozszerzeniami: {1}", |
||||
|
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Pole {0} musi być łańcuchem znaków lub talicą o maksymalnej długości '{1}'.", |
||||
|
"The field {0} must be a string or array type with a minimum length of '{1}'.": "Pole {0} musi być łańcuchem znaków lub talicą o minimalnej długości '{1}'.", |
||||
|
"The {0} field is not a valid phone number.": "Pole {0} nie jest poprawnym numerem telefonu.", |
||||
|
"The field {0} must be between {1} and {2}.": "Pole {0} musi być pomiędzy {1} i {2}.", |
||||
|
"The field {0} must match the regular expression '{1}'.": "Pole {0} musi pasować do wyrażenia regularnego '{1}'.", |
||||
|
"The {0} field is required.": "Pole {0} jest wymagane.", |
||||
|
"The field {0} must be a string with a maximum length of {1}.": "Pole {0} musi być łańcuchem znaków o maksymalnej długości {1}.", |
||||
|
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Pole {0} musi być łańcuchem znaków o minimalnej długości {2} i maksymalnej długości {1}.", |
||||
|
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Pole {0} nie jest prawidłowym, w pełni kwalifikowanym adresem URL http, https lub ftp.", |
||||
|
"The field {0} is invalid.": "Pole {0} jest niepoprawne." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"'{0}' and '{1}' do not match.": "'{0}' và '{1}' không khớp.", |
||||
|
"The {0} field is not a valid credit card number.": "Trường {0} không phải là một số thẻ tín dụng hợp lệ.", |
||||
|
"{0} is not valid.": "{0} không hợp lệ.", |
||||
|
"The {0} field is not a valid e-mail address.": "Trường {0} không phải là một địa chỉ email hợp lệ.", |
||||
|
"The {0} field only accepts files with the following extensions: {1}": "Trường {0} chỉ chấp nhận các tập tin có phần mở rộng như sau: {1}", |
||||
|
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Trường {0} phải là một chuỗi hoặc một mảng với độ dài tối đa là '{1}'.", |
||||
|
"The {0} field is not a valid phone number.": "Trường {0} không phải là một số điện thoại hợp lệ", |
||||
|
"The field {0} must be between {1} and {2}.": "Trường {0} phải ở giữa {1} and {2}.", |
||||
|
"The field {0} must match the regular expression '{1}'.": "Trường {0} phải khớp với biểu thức chính quy '{1}'.", |
||||
|
"The {0} field is required.": "Trường {0} là bắt buộc.", |
||||
|
"The field {0} must be a string with a maximum length of {1}.": "Trường {0} phải là một chuỗi với độ dài tối đa là {1}.", |
||||
|
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Trường {0} phải là một chuỗi với độ dài tối thiểu {2} và tối đa là {1}.", |
||||
|
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Trường {0} không phải là một http, https, hoặc ftp URL đủ điều kiện hợp lệ.", |
||||
|
"The field {0} is invalid.": "Trường {0} không có hiệu lực" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"Menu:Administration": "Administracja" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"Menu:Administration": "Quản trị" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"InternalServerErrorMessage": "Błąd wewnętrzny serwera podczas przetwarzania żądania!", |
||||
|
"ValidationErrorMessage": "Twoje żądanie jest niepoprawnie!", |
||||
|
"ValidationNarrativeErrorMessageTitle": "Wykryto następujące błędy podczas walidacji.", |
||||
|
"DefaultErrorMessage": "Wystąpił błąd!", |
||||
|
"DefaultErrorMessageDetail": "Treść błędu nie została wysłana z serwera.", |
||||
|
"DefaultErrorMessage401": "Nie jesteś zalogowany!", |
||||
|
"DefaultErrorMessage401Detail": "Musisz się zalogować, aby wykonać tą operację.", |
||||
|
"DefaultErrorMessage403": "Nie jesteś zautentykowany!", |
||||
|
"DefaultErrorMessage403Detail": "Nie masz uprawnień do wykonania tej operacji!", |
||||
|
"DefaultErrorMessage404": "Nie znaleziono zasobu!", |
||||
|
"DefaultErrorMessage404Detail": "Nie znaleziono zasobu z żądania na serwerze!", |
||||
|
"EntityNotFoundErrorMessage": "Nie istnieje encja {0} z id = {1}!", |
||||
|
"Error": "Błąd", |
||||
|
"AreYouSure": "Czy jesteś pewien?", |
||||
|
"Cancel": "Anuluj", |
||||
|
"Yes": "Tak", |
||||
|
"No": "Nie", |
||||
|
"Close": "Zamknij", |
||||
|
"Save": "Zapisz", |
||||
|
"SavingWithThreeDot": "Zapisywanie...", |
||||
|
"Actions": "Akcje", |
||||
|
"Delete": "Usuń", |
||||
|
"Edit": "Edytuj", |
||||
|
"Refresh": "Odśwież", |
||||
|
"ProcessingWithThreeDot": "Procesowanie...", |
||||
|
"LoadingWithThreeDot": "Ładowanie...", |
||||
|
"Welcome": "Witaj", |
||||
|
"Login": "Zaloguj", |
||||
|
"Register": "Zarejestruj", |
||||
|
"Logout": "Wyloguj", |
||||
|
"Submit": "Wyślij", |
||||
|
"Back": "Cofnij", |
||||
|
"PagerSearch": "Szukaj", |
||||
|
"PagerNext": "Następna", |
||||
|
"PagerPrevious": "Poprzednia", |
||||
|
"PagerFirst": "Pierwsza", |
||||
|
"PagerLast": "Ostatnia", |
||||
|
"PagerInfo": "Pokaż od _START_ do _END_ z _TOTAL_ rekordów", |
||||
|
"PagerInfoEmpty": "Pokaż od 0 do 0 z 0 rekordów", |
||||
|
"PagerInfoFiltered": "(filtruj od _MAX_ wszystkich rekordów)", |
||||
|
"NoDataAvailableInDatatable": "Nie znaleziono danych w tabeli", |
||||
|
"PagerShowMenuEntries": "Pokaż _MENU_ rekordów", |
||||
|
"DatatableActionDropdownDefaultText": "Akcje", |
||||
|
"ChangePassword": "Zmień hasło", |
||||
|
"PersonalInfo": "Mój profil", |
||||
|
"AreYouSureYouWantToCancelEditingWarningMessage": "Masz niezapisane zmiany." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"InternalServerErrorMessage": "Có một lỗi nội bộ xảy ra trong quá trình thực hiện yêu cầu của bạn!", |
||||
|
"ValidationErrorMessage": "Yêu cầu của bạn không hợp lệ!", |
||||
|
"ValidationNarrativeErrorMessageTitle": "Các lỗi sau đây đã được phát hiện trong quá trình xác nhận", |
||||
|
"DefaultErrorMessage": "Một lỗi đã xảy ra", |
||||
|
"DefaultErrorMessageDetail": "Chi tiết lỗi không được gửi bởi máy chủ", |
||||
|
"DefaultErrorMessage401": "Bạn chưa được xác thực", |
||||
|
"DefaultErrorMessage401Detail": "Bạn cần đăng nhập để thực hiện thao tác này.", |
||||
|
"DefaultErrorMessage403": "Bạn không được phép!", |
||||
|
"DefaultErrorMessage403Detail": "Bạn không được phép thực hiện thao tác này!", |
||||
|
"DefaultErrorMessage404": "Tài nguyên không tìm thấy!", |
||||
|
"DefaultErrorMessage404Detail": "Tài nguyên được yêu cầu không được tìm thấy trên máy chủ!", |
||||
|
"EntityNotFoundErrorMessage": "Không có thực thể nào {0} với id = {1}!", |
||||
|
"Error": "Lỗi", |
||||
|
"AreYouSure": "Bạn có chắc không ?", |
||||
|
"Cancel": "Hủy bỏ", |
||||
|
"Yes": "Đồng ý", |
||||
|
"No": "Không", |
||||
|
"Close": "Đóng", |
||||
|
"Save": "Lưu", |
||||
|
"SavingWithThreeDot": "Đang lưu...", |
||||
|
"Actions": "Hành động", |
||||
|
"Delete": "Xóa", |
||||
|
"Edit": "Sửa", |
||||
|
"Refresh": "Làm mới", |
||||
|
"ProcessingWithThreeDot": "Đang xử lý...", |
||||
|
"LoadingWithThreeDot": "Đang tải...", |
||||
|
"Welcome": "Chào mừng bạn", |
||||
|
"Login": "Đăng nhập", |
||||
|
"Register": "Đăng ký", |
||||
|
"Logout": "Đăng xuất", |
||||
|
"Submit": "Gửi", |
||||
|
"Back": "Quay lại", |
||||
|
"PagerSearch": "Tìm kiếm", |
||||
|
"PagerNext": "Trang kế", |
||||
|
"PagerPrevious": "Trang trước", |
||||
|
"PagerFirst": "Trang đầu", |
||||
|
"PagerLast": "Trang cuối", |
||||
|
"PagerInfo": "Hiển thị từ _START_ đến _END_ trong _TOTAL_ mục", |
||||
|
"PagerInfoEmpty": "Hiển thị từ 0 đến 0 trong 0 mục", |
||||
|
"PagerInfoFiltered": "(được lọc từ tổng số _MAX_ mục)", |
||||
|
"NoDataAvailableInDatatable": "Không có dữ liệu trong bảng", |
||||
|
"PagerShowMenuEntries": "Hiển thị _MENU_ mục", |
||||
|
"DatatableActionDropdownDefaultText": "Hành động", |
||||
|
"ChangePassword": "Đổi mật khẩu", |
||||
|
"PersonalInfo": "Hồ sơ của tôi", |
||||
|
"AreYouSureYouWantToCancelEditingWarningMessage": "Bạn có những thay đổi chưa được lưu" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"BirthDate": "Data urodzenia", |
||||
|
"Value1": "Wartość jeden" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"BirthDate": "Ngày sinh", |
||||
|
"Value1": "Giá trị thứ nhất" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "pl", |
||||
|
"texts": { |
||||
|
"hello": "witaj" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "vi", |
||||
|
"texts": { |
||||
|
"hello": "xin chào" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp2.2</TargetFramework> |
||||
|
<LangVersion>latest</LangVersion> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.Ldap\Volo.Abp.Ldap.csproj" /> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,70 @@ |
|||||
|
using System; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Modularity; |
||||
|
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<LdapOptions>(settings => |
||||
|
{ |
||||
|
settings.ServerHost = "192.168.101.54"; |
||||
|
settings.ServerPort = 636; |
||||
|
settings.UseSsl = true; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
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))"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,245 @@ |
|||||
|
using System; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Ldap |
||||
|
{ |
||||
|
|
||||
|
public class LdapManager_Tests : AbpIntegratedTest<LdapManager_Tests.TestModule> |
||||
|
{ |
||||
|
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() |
||||
|
{ |
||||
|
var result = _ldapManager.GetOrganization("NonExistentNameA"); |
||||
|
|
||||
|
result.ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[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(); |
||||
|
} |
||||
|
|
||||
|
[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<LdapOptions>(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"; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue