diff --git a/build-all.ps1 b/build-all.ps1 index 1ebcc3be8a..89d2cf20b3 100644 --- a/build-all.ps1 +++ b/build-all.ps1 @@ -10,6 +10,7 @@ $solutionPaths = ( "modules/permission-management", "modules/setting-management", "modules/identity", + "modules/identityserver", "modules/tenant-management", "modules/account", "modules/docs", diff --git a/build-test-all.ps1 b/build-test-all.ps1 index 19aad0c90f..3027a4b76a 100644 --- a/build-test-all.ps1 +++ b/build-test-all.ps1 @@ -10,6 +10,7 @@ $solutionsPaths = ( "modules/permission-management", "modules/setting-management", "modules/identity", + "modules/identityserver", "modules/tenant-management", "modules/account", "modules/docs", @@ -54,6 +55,7 @@ $testProjectPaths = ( "modules/blogging/test/Volo.Blogging.EntityFrameworkCore.Tests", "modules/identity/test/Volo.Abp.Identity.Domain.Tests", "modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests", + "modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests", "modules/identity/test/Volo.Abp.Identity.MongoDB.Tests", "modules/identity/test/Volo.Abp.Identity.Application.Tests", "modules/permission-management/test/Volo.Abp.PermissionManagement.Tests", diff --git a/docs/Dependency-Injection.md b/docs/Dependency-Injection.md index 5839b71dc0..30834d4939 100644 --- a/docs/Dependency-Injection.md +++ b/docs/Dependency-Injection.md @@ -4,7 +4,7 @@ ABP's Dependency Injection system is developed based on Microsoft's module class. Example: +Since ABP is a modular framework, every module defines it's own services and registers via dependency injection in it's own seperate module class. Example: ````C# public class BlogModule : AbpModule @@ -18,7 +18,7 @@ public class BlogModule : AbpModule ### Conventional Registration -ABP introduces conventional service registration. You should do nothing to register services by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method. +ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method. ````C# public class BlogModule : AbpModule @@ -87,11 +87,11 @@ public class TaxCalculator : ITransientDependency } ```` -``TaxCalculator`` is automatically registered with transient lifetime since it implements ``ITransientDependency``. +``TaxCalculator`` is automatically registered with a transient lifetime since it implements ``ITransientDependency``. #### Dependency Attribute -Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has given properties: +Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has the following properties: * ``Lifetime``: Lifetime of the registration: ``Singleton``, ``Transient`` or ``Scoped``. * ``TryRegister``: Set ``true`` to register the service only it's not registered before. Uses TryAdd... extension methods of IServiceCollection. @@ -108,7 +108,7 @@ public class TaxCalculator ```` -``Dependency`` attribute has higher priority then dependency interfaces if it defines the ``Lifetime`` property. +``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property. #### ExposeServices Attribute @@ -126,7 +126,7 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie #### Exposed Services by Convention -If you do not specify which services to expose, ABP expose services by convention. If you think the ``TaxCalculator`` defined above: +If you do not specify which services to expose, ABP expose services by convention. So taking the ``TaxCalculator`` defined above: * The class itself is exposed by default. That means you can inject it by ``TaxCalculator`` class. * Default interfaces are exposed by default. Default interfaces are determined by naming convention. In this example, ``ICalculator`` and ``ITaxCalculator`` are default interfaces of ``TaxCalculator``, but ``ICanCalculate`` is not. @@ -164,11 +164,11 @@ public class BlogModule : AbpModule ### Injecting Dependencies -There are three common ways of using a service that is registered before. +There are three common ways of using a service that has already been registered. #### Contructor Injection -This is the most common way of injecting a service into a class. Example: +This is the most common way of injecting a service into a class. For example: ````C# public class TaxAppService : ApplicationService @@ -187,13 +187,13 @@ public class TaxAppService : ApplicationService } ```` -``TaxAppService`` gets ``ITaxCalculator`` in it's constructor. Dependency injection system automatically provides the requested service on the runtime. +``TaxAppService`` gets ``ITaxCalculator`` in it's constructor. The dependency injection system automatically provides the requested service at runtime. Constructor injection is preffered way of injecting dependencies to a class. In that way, the class can not be constructed unless all constructor-injected dependencies are provided. Thus, the class explicitly declares it's required services. #### Property Injection -Property injection is not supported by Microsoft Dependency Injection library. However, ABP integates to 3rd-party DI providers (Autofac, for example) to make property injection possible. Example: +Property injection is not supported by Microsoft Dependency Injection library. However, ABP can integrate with 3rd-party DI providers (Autofac, for example) to make property injection possible. Example: ````C# public class MyService : ITransientDependency @@ -212,15 +212,15 @@ public class MyService : ITransientDependency } ```` -For a property-injection dependency, you declare a public property with public setter. Thus, DI framework can set it after creating your class. +For a property-injection dependency, you declare a public property with public setter. This allows the DI framework to set it after creating your class. Property injected dependencies are generally considered as **optional** dependencies. That means the service can properly work without them. ``Logger`` is such a dependency, ``MyService`` can continue to work without logging. To make the dependency properly optional, we generally set a default/fallback value to the dependency. In this sample, NullLogger is used as fallback. Thus, ``MyService`` can work but does not write logs if DI framework or you don't set Logger property after creating ``MyService``. -One restriction of property injection is that you can not use the dependency in your constructor, since it's set after the object consturction. +One restriction of property injection is that you cannot use the dependency in your constructor, since it's set after the object construction. -Property injection is also useful when you want to design a base class that has some common services injected by default. If you would use constructor injection, all derived classes should also inject depended services into their constructors which makes development harder. However, be carefully using property injection for non-optional services since it makes hard to see requirements of a class. +Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class. #### Resolve Service from IServiceProvider @@ -246,7 +246,7 @@ public class MyService : ITransientDependency #### Releasing/Disposing Services -If you used constructor or property injection, you never need to release services. However, if you resolved service from ``IServiceProvider``, you may need to care about releasing services in some cases. +If you used constructor or property injection, you don't need to be concerned about releasing a service's resources. However, if you have resolved a service from ``IServiceProvider``, you might, in some cases, need to take care about releasing the services. ASP.NET Core releases all services in the end of current HTTP request, even if you directly resolved from ``IServiceProvider`` (assuming you injected IServiceProvider). But, there are several cases where you may want to release/dispose manually resolved services: @@ -254,7 +254,7 @@ ASP.NET Core releases all services in the end of current HTTP request, even if y * You only have a reference to the root service provider. * You may want to immediately release & dispose services (for example, you may creating too many services with big memory usage and don't want to overuse memory). -In any way, you can use such a code block to safely and immediately release services: +In any case, you can use such a 'using' code block to safely and immediately release services: ````C# using (var scope = _serviceProvider.CreateScope()) @@ -268,4 +268,4 @@ Both services are released when the created scope is disposed (at the end of the ### See Also -* [ASP.NET Core Dependency Injection Best Practices, Tips & Tricks](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) \ No newline at end of file +* [ASP.NET Core Dependency Injection Best Practices, Tips & Tricks](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) diff --git a/docs/Entities.md b/docs/Entities.md index 56eb88315a..d6c9301e5a 100644 --- a/docs/Entities.md +++ b/docs/Entities.md @@ -69,12 +69,12 @@ Notice that you also need to define keys of the entity in your **object-to-relat > Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See repository documentation (TODO: link) for more. -ABP does not force you to use aggregate roots, you can only use the `Entity` class as defined before. However, if you want to implement DDD and want to create aggregate root classes, there are some best practices you may want to consider: +ABP does not force you to use aggregate roots, you can in fact use the `Entity` class as defined before. However, if you want to implement DDD and want to create aggregate root classes, there are some best practices you may want to consider: -* An aggregate root is responsible to preserve it's own integrity. This is also true for all entities, but aggregate root has responsibility for it's sub entities too. So, the aggregate root always be in a valid state. -* An aggregate root can be referenced by it's Id. Do not reference it by navigation property. +* An aggregate root is responsible to preserve it's own integrity. This is also true for all entities, but aggregate root has responsibility for it's sub entities too. So, the aggregate root must always be in a valid state. +* An aggregate root can be referenced by it's Id. Do not reference it by it's navigation property. * An aggregate root is treated as a single unit. It's retrieved and updated as a single unit. It's generally considered as a transaction boundary. -* Work with sub-entities over the aggregate root, do not modify them independently. +* Work with sub-entities over the aggregate root- do not modify them independently. #### Aggregate Example @@ -158,19 +158,19 @@ public class OrderLine : Entity } ```` -> If you do not want derive your aggregate root from the base `AggregateRoot` class, you can directly implement `IAggregateRoot` interface. +> If you do not want to derive your aggregate root from the base `AggregateRoot` class, you can directly implement the `IAggregateRoot` interface. `Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderLine` and ` ProductId`). -While this example may not implement all best practices of an aggregate root, it follows some good practices: +While this example may not implement all the best practices of an aggregate root, it still follows good practices: -* `Order` has a public constructor that takes **minimal requirements** to construct an `Order` instance. So, it's not possible to create an order without an id and reference number. The **protected/private** constructor is only necessary to **deserialize** object while reading from a data source. -* `OrderLine` constructor is internal, so it only allows to be created by the domain layer. It's used inside of `Order.AddProduct` method. +* `Order` has a public constructor that takes **minimal requirements** to construct an `Order` instance. So, it's not possible to create an order without an id and reference number. The **protected/private** constructor is only necessary to **deserialize** the object while reading from a data source. +* `OrderLine` constructor is internal, so it is only allowed to be created by the domain layer. It's used inside of the `Order.AddProduct` method. * `Order.AddProduct` implements the business rule to add a product to an order. -* All properties have `protected` setters. This is to prevent entity from arbitrary changes from outside of the entity. For instance, it would be dangerous to set `TotalItemCount` without adding a new product to the order. It's value is maintained by the `AddProduct` method. +* All properties have `protected` setters. This is to prevent the entity from arbitrary changes from outside of the entity. For exmple, it would be dangerous to set `TotalItemCount` without adding a new product to the order. It's value is maintained by the `AddProduct` method. -ABP does not force you to apply any DDD rule or pattern. However, it tries to make it possible and easier when you want to apply. The documentation also follows this principle. +ABP does not force you to apply any DDD rule or patterns. However, it tries to make it possible and easier when you do want to apply them. The documentation also follows the same principle. #### Aggregate Roots with Composite Keys -While it's not common (and not suggested) for aggregate roots, it's possible to define composite keys just as defined for entities above. Use non-generic `AggregateRoot` base class in that case. \ No newline at end of file +While it's not common (and not suggested) for aggregate roots, it isin fact possible to define composite keys in the same way as defined for the mentioned entities above. Use non-generic `AggregateRoot` base class in that case. diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/Tutorials/AspNetCore-Mvc/Part-I.md index f9ef4be716..faf8b14b8c 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-I.md @@ -14,11 +14,11 @@ You can download the **source code** of the application [from here](https://gith ### Creating the Project -Go to the [startup template page](https://abp.io/Templates) and download a new project named `Acme.BookStore`, create database and run the application by following the [template document](../../Getting-Started-AspNetCore-MVC-Template.md). +Go to the [startup template page](https://abp.io/Templates) and download a new project named `Acme.BookStore`, create the database and run the application by following the [template document](../../Getting-Started-AspNetCore-MVC-Template.md). ### Solution Structure -This is the layered solution structure created from the startup template: +This is the how the layered solution structure looks after it's created from the startup template: ![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution.png) @@ -50,10 +50,10 @@ namespace Acme.BookStore } ```` -* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for details and best practices. +* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for more details and best practices. * `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class. * `Guid` is the **primary key type** of the `Book` entity. -* Used **data annotation attributes** in this code for EF Core mappings. You could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead. +* Used **data annotation attributes** in this code for EF Core mappings. Alternatively you could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead. #### BookType Enum @@ -79,7 +79,7 @@ namespace Acme.BookStore #### Add Book Entity to Your DbContext -EF Core requires to relate entities with your DbContext. The easiest way is to add a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below: +EF Core requires you to relate entities with your DbContext. The easiest way to do this is to add a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below: ````C# public class BookStoreDbContext : AbpDbContext @@ -91,7 +91,7 @@ public class BookStoreDbContext : AbpDbContext #### Add New Migration & Update the Database -Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore` as the **default project** and execute the following command: +The Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore` as the **default project** and execute the following command: ![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration.png) @@ -136,10 +136,10 @@ namespace Acme.BookStore } ```` -* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for details. -* `BookDto` is used to transfer a book data to the presentation layer to show a book information on the UI. -* `BookDto` is derived from the `AuditedEntityDto` which has audit properties just like the `Book` defined above. -* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. Thus, you can automatically convert `Book` objects to `BookDto` objects (instead of manually copy all properties). +* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details. +* `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI. +* `BookDto` is derived from the `AuditedEntityDto` which has audit properties just like the `Book` class defined above. +* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. In this way, you get automatic convertion of `Book` objects to `BookDto` objects (instead of manually copy all properties). #### CreateUpdateBookDto @@ -173,7 +173,7 @@ namespace Acme.BookStore * This DTO class is used to get book information from the user interface while creating or updating a book. * It defines data annotation attributes (like `[Required]`) to define validations for the properties. DTOs are automatically validated by ABP. -* Each property has a `[Display]` property which set the label text on UI forms for the related input (it's also integrated to the localization system). The same DTO will be used as View Model. That's why it defines that attribute. You may find incorrect to use DTOs as View Models. You could use a separated view model class, but we thought it's practical and makes the sample project less complex. +* Each property has a `[Display]` property which set the label text on UI forms for the related input (it's also integrated to the localization system). The same DTO will be used as a View Model. That's why it defines that attribute. You may be inclined to think it's incorrect to use DTOs as View Models. There is nothing stopping you from using a separated view model class, but we thought it's practical and makes the sample project less complex. #### IBookAppService @@ -199,7 +199,7 @@ namespace Acme.BookStore } ```` -* Defining interfaces for application services is not required by the framework. However, it's suggested as a good practice. +* Defining interfaces for application services is not required by the framework. However, it's suggested as best practice. * `IAsyncCrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods. * There are some variations of the `IAsyncCrudAppService` where you can use a single DTO or separated DTOs for each method. @@ -229,13 +229,13 @@ namespace Acme.BookStore } ```` -* `BookAppService` is derived from `AsyncCrudAppService<...>` which implements all CRUD methods defined above. +* `BookAppService` is derived from `AsyncCrudAppService<...>` which implements all the CRUD methods defined above. * `BookAppService` injects `IRepository` which is the default repository created for the `Book` entity. ABP automatically creates repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md). -* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. Startup template uses the [AutoMapper](http://automapper.org/) library as object mapping provider. You defined mappings using the `AutoMapFrom` and the `AutoMapTo` attributes above. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details. +* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as object mapping provider. You defined mappings using the `AutoMapFrom` and the `AutoMapTo` attributes above. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details. ### Auto API Controllers -You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus, browser or 3rd-party clients can call via AJAX. +You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. ABP can **automagically** configures your application services as MVC API Controllers by convention. @@ -255,7 +255,7 @@ ABP **dynamically** creates JavaScript **proxies** for all API endpoints. So, yo #### Testing in the Browser Developer Console -You can just test the JavaScript proxy using your favorite browser's **Developer Console** now. Run the application again, open your browser's **developer tools** (shortcut: F12), switch to the **Console** tab, type the following code and press enter: +You can easily test the JavaScript proxy using your favorite browser's **Developer Console** now. Run the application again, open your browser's **developer tools** (shortcut: F12), switch to the **Console** tab, type the following code and press enter: ````js acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); @@ -267,7 +267,7 @@ acme.bookStore.book.getList({}).done(function (result) { console.log(result); }) * `{}` argument is used to send an empty object to the `GetListAsync` method which normally expects an object of type `PagedAndSortedResultRequestDto` which is used to send paging and sorting options to the server. * `getList` function returns a `promise`. So, you can pass a callback to the `done` (or `then`) function to get the result from the server. -Running this code produces such an output: +Running this code produces the following output: ![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png) @@ -387,7 +387,7 @@ Change the `Pages/Books/Index.cshtml` as following: ```` * `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning** for example. See the [bundling & minification document](../../AspNetCore/Bundling-Minification.md) for details. -* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are many tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help of the intellisense and compile time type checking. See the [tag helpers document](../../AspNetCore/Tag-Helpers.md). +* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are many tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help of the intellisense and compile time type checking. See the [tag helpers document](../../AspNetCore/Tag-Helpers.md). * You can **localize** the column names in the localization file as you did for the menu items above. ##### Add a Script File @@ -414,7 +414,7 @@ $(function () { ```` * `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to Datatable's format. -* `abp.libs.datatables.normalizeConfiguration` is another helper function. It's not required to use it, but it simplifies the datatables configuration by providing conventional values for missing options. +* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirment to use it, but it simplifies the datatables configuration by providing conventional values for missing options. * `acme.bookStore.book.getList` is the function to get list of books (you have seen it before). * See [Datatable's documentation](https://datatables.net/manual/) for more configuration options. @@ -424,4 +424,4 @@ The final UI is shown below: ### Next Part -See the [next part](Part-II.md) of this tutorial. \ No newline at end of file +See the [next part](Part-II.md) of this tutorial. diff --git a/modules/blogging/README.md b/modules/blogging/README.md index 2f99c67894..9eda941757 100644 --- a/modules/blogging/README.md +++ b/modules/blogging/README.md @@ -1,2 +1,3 @@ -# abp-blog -ABP Blogging Module +# Blogging Module + +This module is used for ABP blog: https://abp.io/blog/abp/ diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs new file mode 100644 index 0000000000..5581c53638 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimTypeConsts.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Identity +{ + public class IdentityClaimTypeConsts + { + public const int MaxNameLength = 128; + + public const int MaxRegexLength = 512; + + public const int MaxRegexDescriptionLength = 128; + + public const int MaxDescriptionLength = 256; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimValueType.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimValueType.cs new file mode 100644 index 0000000000..69d40a4607 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityClaimValueType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Identity +{ + public enum IdentityClaimValueType + { + String, + Int, + Boolean, + DateTime + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs new file mode 100644 index 0000000000..bfc62de497 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIDentityClaimTypeRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Identity +{ + public interface IIdentityClaimTypeRepository : IBasicRepository + { + Task DoesNameExist(string name, Guid? claimTypeId = null); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdenityClaimTypeManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdenityClaimTypeManager.cs new file mode 100644 index 0000000000..a75bb566a7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdenityClaimTypeManager.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.Abp.Guids; + +namespace Volo.Abp.Identity +{ + public class IdenityClaimTypeManager : IDomainService + { + private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository; + private readonly IGuidGenerator _guidGenerator; + + public IdenityClaimTypeManager(IIdentityClaimTypeRepository identityClaimTypeRepository, IGuidGenerator guidGenerator) + { + _identityClaimTypeRepository = identityClaimTypeRepository; + _guidGenerator = guidGenerator; + } + + public async Task GetAsync(Guid id) + { + return await _identityClaimTypeRepository.GetAsync(id); + } + + public async Task CreateAsync(IdentityClaimType claimType) + { + if (await _identityClaimTypeRepository.DoesNameExist(claimType.Name)) + { + throw new AbpException($"Name Exist: {claimType.Name}"); + } + + return await _identityClaimTypeRepository.InsertAsync(claimType); + } + + public async Task UpdateAsync(IdentityClaimType claimType) + { + if (await _identityClaimTypeRepository.DoesNameExist(claimType.Name, claimType.Id)) + { + throw new AbpException($"Name Exist: {claimType.Name}"); + } + + return await _identityClaimTypeRepository.UpdateAsync(claimType); + } + + public async Task DeleteAsync(Guid id) + { + await _identityClaimTypeRepository.DeleteAsync(id); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaimType.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaimType.cs new file mode 100644 index 0000000000..5755279585 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaimType.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Identity +{ + public class IdentityClaimType : Entity + { + public virtual string Name { get; protected set; } + + public virtual bool Required { get; protected set; } + + public virtual bool IsStatic { get; protected set; } + + public virtual string Regex { get; protected set; } + + public virtual string RegexDescription { get; protected set; } + + public virtual string Description { get; protected set; } + + public virtual IdentityClaimValueType ValueType { get; protected set; } + + protected IdentityClaimType() + { + } + + public IdentityClaimType(Guid id, [NotNull] string name, bool required, bool isStatic, [CanBeNull]string regex, [CanBeNull]string regexDescription, [CanBeNull] string description, IdentityClaimValueType valueType = IdentityClaimValueType.String) + { + Check.NotNull(name, nameof(name)); + + Name = name; + Required = required; + IsStatic = isStatic; + Regex = regex; + RegexDescription = regexDescription; + Description = description; + ValueType = valueType; + } + + + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs new file mode 100644 index 0000000000..81cab3ee8c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityClaimTypeRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class EfCoreIdentityClaimTypeRepository : EfCoreRepository, IIdentityClaimTypeRepository + { + public EfCoreIdentityClaimTypeRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task DoesNameExist(string name, Guid? claimTypeId = null) + { + return await DbSet.WhereIf(claimTypeId != null, ct => ct.Id == claimTypeId).CountAsync(ct => ct.Name == name) > 0; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs index 5b8503f163..474885e44b 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs @@ -10,5 +10,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore DbSet Users { get; set; } DbSet Roles { get; set; } + + DbSet ClaimTypes { get; set; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs index 15eab5cc08..a9eb5c7648 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs @@ -18,6 +18,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore public DbSet Roles { get; set; } + public DbSet ClaimTypes { get; set; } + public IdentityDbContext(DbContextOptions options) : base(options) { diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index d306b16714..04a0e11047 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -112,6 +112,17 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.HasIndex(uc => uc.RoleId); }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "ClaimTypes", options.Schema); + + b.Property(uc => uc.Name).HasMaxLength(IdentityClaimTypeConsts.MaxNameLength).IsRequired(); // make unique + b.Property(uc => uc.Regex).HasMaxLength(IdentityClaimTypeConsts.MaxRegexLength); + b.Property(uc => uc.RegexDescription).HasMaxLength(IdentityClaimTypeConsts.MaxRegexDescriptionLength); + b.Property(uc => uc.Description).HasMaxLength(IdentityClaimTypeConsts.MaxDescriptionLength); + + }); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs index 8036db7fc8..5d380afde9 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs @@ -22,6 +22,11 @@ namespace Volo.Abp.Identity.MongoDB { map.AutoMap(); }); + + BsonClassMap.RegisterClassMap(map => + { + map.AutoMap(); + }); }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs index 14e81278bd..f02be5eb9f 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs @@ -13,6 +13,8 @@ namespace Volo.Abp.Identity.MongoDB public IMongoCollection Roles => Collection(); + public IMongoCollection ClaimTypes => Collection(); + protected override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs index 767ba55fc7..266c100ff6 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs @@ -24,6 +24,11 @@ namespace Volo.Abp.Identity.MongoDB { b.CollectionName = options.CollectionPrefix + "Roles"; }); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "ClaimTypes"; + }); } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs index d7743a748b..e88fab8a2d 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.Identity.MongoDB { options.AddRepository(); options.AddRepository(); + options.AddRepository(); }); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs index cb344e0fc8..ac6420b449 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs @@ -10,5 +10,7 @@ namespace Volo.Abp.Identity.MongoDB IMongoCollection Users { get; } IMongoCollection Roles { get; } + + IMongoCollection ClaimTypes { get; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs new file mode 100644 index 0000000000..3d80cc6520 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public class MongoIdentityClaimTypeRepository : MongoDbRepository, IIdentityClaimTypeRepository + { + public MongoIdentityClaimTypeRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + public async Task DoesNameExist(string name, Guid? claimTypeId = null) + { + return GetMongoQueryable().WhereIf(claimTypeId != null, ct => ct.Id == claimTypeId).Count(ct => ct.Name == name) > 0; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index e86149c47f..65cdf27b4b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -20,8 +20,7 @@ - - + diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs index 310f6e7107..95dddc2393 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs @@ -4,4 +4,4 @@ { } -} \ No newline at end of file +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityClaimTypeRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityClaimTypeRepository_Tests.cs new file mode 100644 index 0000000000..07f830ba86 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityClaimTypeRepository_Tests.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Identity.EntityFrameworkCore; + +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityClaimTypeRepository_Tests : IdentityClaimTypeRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs new file mode 100644 index 0000000000..b7926aae00 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityClaimTypeRepository_Tests : IdentityClaimTypeRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs index 448dfe1c61..ee5fc56fd9 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs @@ -9,6 +9,7 @@ namespace Volo.Abp.Identity { private readonly IGuidGenerator _guidGenerator; private readonly IIdentityUserRepository _userRepository; + private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository; private readonly IIdentityRoleRepository _roleRepository; private readonly ILookupNormalizer _lookupNormalizer; private readonly IdentityTestData _testData; @@ -20,12 +21,14 @@ namespace Volo.Abp.Identity public AbpIdentityTestDataBuilder( IGuidGenerator guidGenerator, IIdentityUserRepository userRepository, + IIdentityClaimTypeRepository identityClaimTypeRepository, IIdentityRoleRepository roleRepository, ILookupNormalizer lookupNormalizer, IdentityTestData testData) { _guidGenerator = guidGenerator; _userRepository = userRepository; + _identityClaimTypeRepository = identityClaimTypeRepository; _roleRepository = roleRepository; _lookupNormalizer = lookupNormalizer; _testData = testData; @@ -35,6 +38,7 @@ namespace Volo.Abp.Identity { AddRoles(); AddUsers(); + AddClaimTypes(); } private void AddRoles() @@ -73,5 +77,13 @@ namespace Volo.Abp.Identity neo.AddClaim(_guidGenerator, new Claim("TestClaimType", "43")); _userRepository.Insert(neo); } + + private void AddClaimTypes() + { + var ageClaim = new IdentityClaimType(_testData.AgeClaimId, "Age", false, false, null, null, null,IdentityClaimValueType.Int); + _identityClaimTypeRepository.Insert(ageClaim); + var educationClaim = new IdentityClaimType(_testData.EducationClaimId, "Education", true, false, null, null, null); + _identityClaimTypeRepository.Insert(educationClaim); + } } } \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs new file mode 100644 index 0000000000..56de1d5d5f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityClaimTypeRepository_Tests.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Guids; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityClaimTypeRepository_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected IIdentityClaimTypeRepository ClaimTypeRepository { get; } + protected IGuidGenerator GuidGenerator { get; } + + public IdentityClaimTypeRepository_Tests() + { + ClaimTypeRepository = ServiceProvider.GetRequiredService(); + GuidGenerator = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task Should_Check_Name_If_It_Is_Uniquee() + { + var claim = (await ClaimTypeRepository.GetListAsync()).FirstOrDefault(); + + var result1 = await ClaimTypeRepository.DoesNameExist(claim.Name); + + result1.ShouldBe(true); + + var result2 = await ClaimTypeRepository.DoesNameExist(Guid.NewGuid().ToString()); + + result2.ShouldBe(false); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs index b8b99a2453..10fcce89af 100644 --- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs @@ -10,5 +10,7 @@ namespace Volo.Abp.Identity public Guid UserJohnId { get; } = Guid.NewGuid(); public Guid UserDavidId { get; } = Guid.NewGuid(); public Guid UserNeoId { get; } = Guid.NewGuid(); + public Guid AgeClaimId { get; } = Guid.NewGuid(); + public Guid EducationClaimId { get; } = Guid.NewGuid(); } } diff --git a/modules/identityserver/Volo.Abp.IdentityServer.sln b/modules/identityserver/Volo.Abp.IdentityServer.sln new file mode 100644 index 0000000000..fcdc057cf3 --- /dev/null +++ b/modules/identityserver/Volo.Abp.IdentityServer.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2047 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{59A0FC0F-EA6D-477B-84A7-3B1E41B4C858}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityServer.Domain", "src\Volo.Abp.IdentityServer.Domain\Volo.Abp.IdentityServer.Domain.csproj", "{A3B81AEE-EE96-4F75-856B-55B25D8822E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityServer.Domain.Shared", "src\Volo.Abp.IdentityServer.Domain.Shared\Volo.Abp.IdentityServer.Domain.Shared.csproj", "{FC035412-78AD-424C-BECE-B19D04C7B5A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityServer.EntityFrameworkCore", "src\Volo.Abp.IdentityServer.EntityFrameworkCore\Volo.Abp.IdentityServer.EntityFrameworkCore.csproj", "{F352D620-1CBF-4658-953F-70BA73B458F1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2C792EC1-BA27-44ED-B7CC-D0939553F1B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityServer.EntityFrameworkCore.Tests", "test\Volo.Abp.IdentityServer.EntityFrameworkCore.Tests\Volo.Abp.IdentityServer.EntityFrameworkCore.Tests.csproj", "{8B8FBA95-4FA2-4438-A387-7C5EC7A89E82}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A3B81AEE-EE96-4F75-856B-55B25D8822E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3B81AEE-EE96-4F75-856B-55B25D8822E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3B81AEE-EE96-4F75-856B-55B25D8822E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3B81AEE-EE96-4F75-856B-55B25D8822E2}.Release|Any CPU.Build.0 = Release|Any CPU + {FC035412-78AD-424C-BECE-B19D04C7B5A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC035412-78AD-424C-BECE-B19D04C7B5A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC035412-78AD-424C-BECE-B19D04C7B5A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC035412-78AD-424C-BECE-B19D04C7B5A6}.Release|Any CPU.Build.0 = Release|Any CPU + {F352D620-1CBF-4658-953F-70BA73B458F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F352D620-1CBF-4658-953F-70BA73B458F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F352D620-1CBF-4658-953F-70BA73B458F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F352D620-1CBF-4658-953F-70BA73B458F1}.Release|Any CPU.Build.0 = Release|Any CPU + {8B8FBA95-4FA2-4438-A387-7C5EC7A89E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B8FBA95-4FA2-4438-A387-7C5EC7A89E82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B8FBA95-4FA2-4438-A387-7C5EC7A89E82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B8FBA95-4FA2-4438-A387-7C5EC7A89E82}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A3B81AEE-EE96-4F75-856B-55B25D8822E2} = {59A0FC0F-EA6D-477B-84A7-3B1E41B4C858} + {FC035412-78AD-424C-BECE-B19D04C7B5A6} = {59A0FC0F-EA6D-477B-84A7-3B1E41B4C858} + {F352D620-1CBF-4658-953F-70BA73B458F1} = {59A0FC0F-EA6D-477B-84A7-3B1E41B4C858} + {8B8FBA95-4FA2-4438-A387-7C5EC7A89E82} = {2C792EC1-BA27-44ED-B7CC-D0939553F1B2} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {45562023-C330-4060-A583-2BA10F472D3D} + EndGlobalSection +EndGlobal diff --git a/modules/identityserver/common.props b/modules/identityserver/common.props new file mode 100644 index 0000000000..f00a3fc2cb --- /dev/null +++ b/modules/identityserver/common.props @@ -0,0 +1,16 @@ + + + latest + 0.3.0 + $(NoWarn);CS1591 + http://www.aspnetboilerplate.com/images/abp_nupkg.png + http://abp.io + git + https://github.com/volosoft/abp/ + + + + + + + \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj new file mode 100644 index 0000000000..02f6f60ff4 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj @@ -0,0 +1,20 @@ + + + + + + netstandard2.0 + Volo.Abp.IdentityServer.Domain.Shared + Volo.Abp.IdentityServer.Domain.Shared + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs new file mode 100644 index 0000000000..27da5c162b --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.IdentityServer +{ + public class AbpIdentityServerDomainSharedModule : AbpModule + { + + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiResourceConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiResourceConsts.cs new file mode 100644 index 0000000000..1c3aecc531 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiResourceConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiResourceConsts + { + public const int NameMaxLength = 200; + public const int DisplayNameMaxLength = 200; + public const int DescriptionMaxLength = 1000; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiScopeConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiScopeConsts.cs new file mode 100644 index 0000000000..79629fee83 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/ApiResources/ApiScopeConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiScopeConsts + { + public const int NameMaxLength = 196; + public const int DisplayNameMaxLength = 128; + public const int DescriptionMaxLength = 256; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientClaimConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientClaimConsts.cs new file mode 100644 index 0000000000..3a5d1fe907 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientClaimConsts.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientClaimConsts + { + public const int TypeMaxLength = 250; + public const int ValueMaxLength = 250; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientConsts.cs new file mode 100644 index 0000000000..d83138fd2f --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientConsts.cs @@ -0,0 +1,25 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientConsts + { + public const int ClientIdMaxLength = 200; + + public const int ProtocolTypeMaxLength = 200; + + public const int ClientNameMaxLength = 200; + + public const int ClientUriMaxLength = 2000; + + public const int LogoUriMaxLength = 2000; + + public const int DescriptionMaxLength = 1000; + + public const int FrontChannelLogoutUriMaxLength = 2000; + + public const int BackChannelLogoutUriMaxLength = 2000; + + public const int ClientClaimsPrefixMaxLength = 200; + + public const int PairWiseSubjectSaltMaxLength = 200; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientCorsOriginConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientCorsOriginConsts.cs new file mode 100644 index 0000000000..07ba598d6c --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientCorsOriginConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientCorsOriginConsts + { + public const int OriginMaxLength = 150; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientGrantTypeConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientGrantTypeConsts.cs new file mode 100644 index 0000000000..f13f6cda1e --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientGrantTypeConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientGrantTypeConsts + { + public const int GrantTypeMaxLength = 196; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs new file mode 100644 index 0000000000..d89d76bdc5 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientIdPRestrictionConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientIdPRestrictionConsts + { + public const int ProviderMaxLength = 64; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs new file mode 100644 index 0000000000..8fe55ba018 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUriConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientPostLogoutRedirectUriConsts + { + public const int PostLogoutRedirectUriMaxLength = 2000; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs new file mode 100644 index 0000000000..6535304596 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientPropertyConsts.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientPropertyConsts + { + public const int KeyMaxLength = 250; + public const int ValueMaxLength = 2000; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs new file mode 100644 index 0000000000..d67bbea509 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientRedirectUriConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientRedirectUriConsts + { + public const int RedirectUriMaxLength = 2000; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs new file mode 100644 index 0000000000..9b9d4f8077 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Clients/ClientScopeConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientScopeConsts + { + public const int ScopeMaxLength = 196; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs new file mode 100644 index 0000000000..75f67dcc3a --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/Grants/PersistedGrantConsts.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.IdentityServer.Grants +{ + public class PersistedGrantConsts + { + public const int KeyMaxLength = 200; + public const int TypeMaxLength = 50; + public const int SubjectIdMaxLength = 200; + public const int ClientIdMaxLength = 200; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs new file mode 100644 index 0000000000..c60cc34ad8 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer.IdentityResources +{ + public class IdentityResourceConsts + { + public const int NameMaxLength = 200; + public const int DisplayNameMaxLength = 200; + public const int DescriptionMaxLength = 1000; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs new file mode 100644 index 0000000000..1194a7c82d --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/SecretConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer +{ + public class SecretConsts + { + public const int TypeMaxLength = 32; + public const int ValueMaxLength = 196; + public const int DescriptionMaxLength = 256; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs new file mode 100644 index 0000000000..bb10344a63 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/UserClaimConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.IdentityServer +{ + public class UserClaimConsts + { + public const int TypeMaxLength = 196; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj new file mode 100644 index 0000000000..e438297e05 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj @@ -0,0 +1,28 @@ + + + + + + netstandard2.0 + Volo.Abp.IdentityServer.Domain + Volo.Abp.IdentityServer.Domain + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + + + diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs new file mode 100644 index 0000000000..c60c241209 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using IdentityServer4.Services; +using Microsoft.Extensions.Logging; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.IdentityServer +{ + public class AbpClaimsService : DefaultClaimsService + { + public AbpClaimsService(IProfileService profile, ILogger logger) + : base(profile, logger) + { + } + + protected override IEnumerable GetOptionalClaims(ClaimsPrincipal subject) + { + var tenantClaim = subject.FindFirst(AbpClaimTypes.TenantId); + if (tenantClaim == null) + { + return base.GetOptionalClaims(subject); + } + + return base.GetOptionalClaims(subject).Union(new[] { tenantClaim }); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs new file mode 100644 index 0000000000..585b022b34 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using IdentityModel; +using IdentityServer4.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Identity; +using Volo.Abp.IdentityServer.AspNetIdentity; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.IdentityServer +{ + public static class AbpIdentityServerBuilderExtensions + { + public static IIdentityServerBuilder AddAbpIdentityServer( + this IIdentityServerBuilder builder, + Action optionsAction = null) + { + var options = new AbpIdentityServerOptions(); + optionsAction?.Invoke(options); + + //TODO: AspNet Identity integration lines. Can be extracted to a extension method + builder.AddAspNetIdentity(); + builder.AddProfileService(); + builder.AddResourceOwnerValidator(); + + builder.Services.Replace(ServiceDescriptor.Transient()); + + if (options.UpdateAbpClaimTypes) + { + AbpClaimTypes.UserId = JwtClaimTypes.Subject; + AbpClaimTypes.UserName = JwtClaimTypes.Name; + AbpClaimTypes.Role = JwtClaimTypes.Role; + AbpClaimTypes.Email = JwtClaimTypes.Email; + } + + if (options.UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserId] = AbpClaimTypes.UserId; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserName] = AbpClaimTypes.UserName; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.Role] = AbpClaimTypes.Role; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.Email] = AbpClaimTypes.Email; + } + + return builder; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerConsts.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerConsts.cs new file mode 100644 index 0000000000..c0593a245e --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer +{ + public static class AbpIdentityServerConsts + { + public const string DefaultDbTablePrefix = "IdentityServer"; + + public const string DefaultDbSchema = null; + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs new file mode 100644 index 0000000000..fccf60ddf0 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AutoMapper; +using Volo.Abp.Domain; +using Volo.Abp.Identity; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.Modularity; +using Volo.Abp.Security; + +namespace Volo.Abp.IdentityServer +{ + [DependsOn(typeof(AbpIdentityServerDomainSharedModule))] + [DependsOn(typeof(AbpDddDomainModule))] + [DependsOn(typeof(AbpAutoMapperModule))] + [DependsOn(typeof(AbpIdentityDomainModule))] + [DependsOn(typeof(AbpSecurityModule))] + public class AbpIdentityServerDomainModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.Configure(options => + { + options.AddProfile(validate: true); + }); + + AddIdentityServer(context.Services); + } + + private static void AddIdentityServer(IServiceCollection services) + { + var identityServerBuilder = services.AddIdentityServer(options => + { + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + }); + + identityServerBuilder + .AddDeveloperSigningCredential() //TODO: Should be able to change this! + .AddClientStore() + .AddResourceStore() + .AddAbpIdentityServer(); + + services.ExecutePreConfiguredActions(identityServerBuilder); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerOptions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerOptions.cs new file mode 100644 index 0000000000..2b66f7a0fb --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerOptions.cs @@ -0,0 +1,17 @@ +namespace Volo.Abp.IdentityServer +{ + public class AbpIdentityServerOptions + { + /// + /// Updates to be compatible with identity server claims. + /// Default: true. + /// + public bool UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap { get; set; } = true; + + /// + /// Updates to be compatible with identity server claims. + /// Default: true. + /// + public bool UpdateAbpClaimTypes { get; set; } = true; + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResource.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResource.cs new file mode 100644 index 0000000000..fa0614a70c --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResource.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using IdentityServer4; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiResource : AggregateRoot + { + [NotNull] + public virtual string Name { get; protected set; } + + public virtual string DisplayName { get; set; } + + public virtual string Description { get; set; } + + public virtual bool Enabled { get; set; } + + public virtual List Secrets { get; protected set; } + + public virtual List Scopes { get; protected set; } + + public virtual List UserClaims { get; protected set; } + + protected ApiResource() + { + + } + + public ApiResource(Guid id, [NotNull] string name, string displayName = null, string description = null) + { + Check.NotNull(name, nameof(name)); + + Id = id; + + Name = name; + + DisplayName = displayName; + Description = description; + + Enabled = true; + + Secrets = new List(); + Scopes = new List(); + UserClaims = new List(); + + Scopes.Add(new ApiScope(id, name, displayName, description)); + } + + public virtual void AddSecret( + [NotNull] string value, + DateTime? expiration = null, + string type = IdentityServerConstants.SecretTypes.SharedSecret, + string description = null) + { + Secrets.Add(new ApiSecret(Id, value, expiration, type, description)); + } + + public virtual void AddScope( + [NotNull] string name, + string displayName = null, + string description = null, + bool required = false, + bool emphasize = false, + bool showInDiscoveryDocument = true) + { + Scopes.Add(new ApiScope(Id, name, displayName, description, required, emphasize, showInDiscoveryDocument)); + } + + public virtual void AddUserClaim([NotNull] string type) + { + UserClaims.Add(new ApiResourceClaim(Id, type)); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResourceClaim.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResourceClaim.cs new file mode 100644 index 0000000000..89fec1c017 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiResourceClaim.cs @@ -0,0 +1,26 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiResourceClaim : UserClaim + { + public virtual Guid ApiResourceId { get; set; } + + protected ApiResourceClaim() + { + + } + + protected internal ApiResourceClaim(Guid apiResourceId, [NotNull] string type) + : base(type) + { + ApiResourceId = apiResourceId; + } + + public override object[] GetKeys() + { + return new object[] {ApiResourceId, Type}; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScope.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScope.cs new file mode 100644 index 0000000000..67fe8c6ad8 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScope.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiScope : Entity + { + public virtual Guid ApiResourceId { get; protected set; } + + [NotNull] + public virtual string Name { get; protected set; } + + public virtual string DisplayName { get; set; } + + public virtual string Description { get; set; } + + public virtual bool Required { get; set; } + + public virtual bool Emphasize { get; set; } + + public virtual bool ShowInDiscoveryDocument { get; set; } + + public virtual List UserClaims { get; protected set; } + + protected ApiScope() + { + + } + + protected internal ApiScope( + Guid apiResourceId, + [NotNull] string name, + string displayName = null, + string description = null, + bool required = false, + bool emphasize = false, + bool showInDiscoveryDocument = true) + { + Check.NotNull(name, nameof(name)); + + ApiResourceId = apiResourceId; + Name = name; + DisplayName = displayName ?? name; + Description = description; + Required = required; + Emphasize = emphasize; + ShowInDiscoveryDocument = showInDiscoveryDocument; + + UserClaims = new List(); + } + + public virtual void AddUserClaim([NotNull] string type) + { + UserClaims.Add(new ApiScopeClaim(ApiResourceId, Name, type)); + } + + public override object[] GetKeys() + { + return new object[] { ApiResourceId, Name }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScopeClaim.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScopeClaim.cs new file mode 100644 index 0000000000..5c7dbdbe23 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiScopeClaim.cs @@ -0,0 +1,32 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiScopeClaim : UserClaim + { + public Guid ApiResourceId { get; protected set; } + + [NotNull] + public string Name { get; protected set; } + + protected ApiScopeClaim() + { + + } + + protected internal ApiScopeClaim(Guid apiResourceId, [NotNull] string name, [NotNull] string type) + : base(type) + { + Check.NotNull(name, nameof(name)); + + ApiResourceId = apiResourceId; + Name = name; + } + + public override object[] GetKeys() + { + return new object[] { ApiResourceId, Name, Type }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiSecret.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiSecret.cs new file mode 100644 index 0000000000..41c4bc53c2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/ApiSecret.cs @@ -0,0 +1,36 @@ +using System; +using IdentityServer4; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiSecret : Secret + { + public virtual Guid ApiResourceId { get; protected set; } + + protected ApiSecret() + { + + } + + protected internal ApiSecret( + Guid apiResourceId, + [NotNull] string value, + DateTime? expiration = null, + string type = IdentityServerConstants.SecretTypes.SharedSecret, + string description = null + ) : base( + value, + expiration, + type, + description) + { + ApiResourceId = apiResourceId; + } + + public override object[] GetKeys() + { + return new object[] { ApiResourceId, Type, Value }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs new file mode 100644 index 0000000000..10cc7f7228 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public interface IApiResourceRepository : IBasicRepository + { + Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + Task> GetListByScopesAsync( + string[] scopeNames, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs new file mode 100644 index 0000000000..7b411221df --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using System.Security.Principal; +using IdentityServer4.AspNetIdentity; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Volo.Abp.IdentityServer.AspNetIdentity +{ + public class AbpProfileService : ProfileService + { + private readonly ICurrentTenant _currentTenant; + public AbpProfileService( + IdentityUserManager userManager, + IUserClaimsPrincipalFactory claimsFactory, + ICurrentTenant currentTenant) + : base(userManager, claimsFactory) + { + _currentTenant = currentTenant; + } + + [UnitOfWork] + public override async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + using (_currentTenant.Change(context.Subject.FindTenantId())) + { + await base.GetProfileDataAsync(context); + } + } + + [UnitOfWork] + public override async Task IsActiveAsync(IsActiveContext context) + { + using (_currentTenant.Change(context.Subject.FindTenantId())) + { + await base.IsActiveAsync(context); + } + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs new file mode 100644 index 0000000000..778b3445bf --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using IdentityServer4.AspNetIdentity; +using IdentityServer4.Services; +using IdentityServer4.Validation; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Volo.Abp.Identity; +using Volo.Abp.Uow; + +namespace Volo.Abp.IdentityServer.AspNetIdentity +{ + public class AbpResourceOwnerPasswordValidator : ResourceOwnerPasswordValidator + { + public AbpResourceOwnerPasswordValidator( + IdentityUserManager userManager, + SignInManager signInManager, + IEventService events, + ILogger> logger + ) : base( + userManager, + signInManager, + events, + logger) + { + } + + [UnitOfWork] + public override async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) + { + await base.ValidateAsync(context); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/Client.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/Client.cs new file mode 100644 index 0000000000..13ae8d7f6c --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/Client.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using IdentityServer4; +using IdentityServer4.Models; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Guids; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class Client : AggregateRoot + { + public virtual string ClientId { get; set; } + + public virtual string ClientName { get; set; } + + public virtual string Description { get; set; } + + public virtual string ClientUri { get; set; } + + public virtual string LogoUri { get; set; } + + public virtual bool Enabled { get; set; } = true; + + public virtual string ProtocolType { get; set; } + + public virtual bool RequireClientSecret { get; set; } + + public virtual bool RequireConsent { get; set; } + + public virtual bool AllowRememberConsent { get; set; } + + public virtual bool AlwaysIncludeUserClaimsInIdToken { get; set; } + + public virtual bool RequirePkce { get; set; } + + public virtual bool AllowPlainTextPkce { get; set; } + + public virtual bool AllowAccessTokensViaBrowser { get; set; } + + public virtual string FrontChannelLogoutUri { get; set; } + + public virtual bool FrontChannelLogoutSessionRequired { get; set; } + + public virtual string BackChannelLogoutUri { get; set; } + + public virtual bool BackChannelLogoutSessionRequired { get; set; } + + public virtual bool AllowOfflineAccess { get; set; } + + public virtual int IdentityTokenLifetime { get; set; } + + public virtual int AccessTokenLifetime { get; set; } + + public virtual int AuthorizationCodeLifetime { get; set; } + + public virtual int? ConsentLifetime { get; set; } + + public virtual int AbsoluteRefreshTokenLifetime { get; set; } + + public virtual int SlidingRefreshTokenLifetime { get; set; } + + public virtual int RefreshTokenUsage { get; set; } + + public virtual bool UpdateAccessTokenClaimsOnRefresh { get; set; } + + public virtual int RefreshTokenExpiration { get; set; } + + public virtual int AccessTokenType { get; set; } + + public virtual bool EnableLocalLogin { get; set; } + + public virtual bool IncludeJwtId { get; set; } + + public virtual bool AlwaysSendClientClaims { get; set; } + + public virtual string ClientClaimsPrefix { get; set; } + + public virtual string PairWiseSubjectSalt { get; set; } + + public virtual List AllowedScopes { get; set; } + + public virtual List ClientSecrets { get; set; } + + public virtual List AllowedGrantTypes { get; set; } + + public virtual List AllowedCorsOrigins { get; set; } + + public virtual List RedirectUris { get; set; } + + public virtual List PostLogoutRedirectUris { get; set; } + + public virtual List IdentityProviderRestrictions { get; set; } + + public virtual List Claims { get; set; } + + public virtual List Properties { get; set; } + + protected Client() + { + + } + + public Client(Guid id, [NotNull] string clientId) + { + Check.NotNull(clientId, nameof(clientId)); + + Id = id; + ClientId = clientId; + + //TODO: Replace magics with constants? + + ProtocolType = IdentityServerConstants.ProtocolTypes.OpenIdConnect; + RequireClientSecret = true; + RequireConsent = true; + AllowRememberConsent = true; + FrontChannelLogoutSessionRequired = true; + BackChannelLogoutSessionRequired = true; + IdentityTokenLifetime = 300; + AccessTokenLifetime = 3600; + AuthorizationCodeLifetime = 300; + AbsoluteRefreshTokenLifetime = 2592000; + SlidingRefreshTokenLifetime = 1296000; + RefreshTokenUsage = (int)TokenUsage.OneTimeOnly; + RefreshTokenExpiration = (int)TokenExpiration.Absolute; + AccessTokenType = (int)IdentityServer4.Models.AccessTokenType.Jwt; + EnableLocalLogin = true; + ClientClaimsPrefix = "client_"; + + AllowedScopes = new List(); + ClientSecrets = new List(); + AllowedGrantTypes = new List(); + AllowedCorsOrigins = new List(); + RedirectUris = new List(); + PostLogoutRedirectUris = new List(); + IdentityProviderRestrictions = new List(); + Claims = new List(); + Properties = new List(); + } + + public virtual void AddGrantType([NotNull] string grantType) + { + AllowedGrantTypes.Add(new ClientGrantType(Id, grantType)); + } + + public virtual void AddGrantTypes(IEnumerable grantTypes) + { + AllowedGrantTypes.AddRange( + grantTypes.Select( + grantType => new ClientGrantType(Id, grantType) + ) + ); + } + + public virtual void AddSecret([NotNull] string value, DateTime? expiration = null, string type = IdentityServerConstants.SecretTypes.SharedSecret, string description = null) + { + ClientSecrets.Add(new ClientSecret(Id, value, expiration, type, description)); + } + + public virtual void AddScope([NotNull] string scope) + { + AllowedScopes.Add(new ClientScope(Id, scope)); + } + + public virtual void AddCorsOrigin([NotNull] string origin) + { + AllowedCorsOrigins.Add(new ClientCorsOrigin(Id, origin)); + } + + public virtual void AddRedirectUri([NotNull] string redirectUri) + { + RedirectUris.Add(new ClientRedirectUri(Id, redirectUri)); + } + + public virtual void AddPostLogoutRedirectUri([NotNull] string postLogoutRedirectUri) + { + PostLogoutRedirectUris.Add(new ClientPostLogoutRedirectUri(Id, postLogoutRedirectUri)); + } + + public virtual void AddIdentityProviderRestriction([NotNull] string provider) + { + IdentityProviderRestrictions.Add(new ClientIdPRestriction(Id, provider)); + } + + public virtual void AddProperty([NotNull] string key) + { + Properties.Add(new ClientProperty(Id, key)); + } + + public virtual void AddClaim(IGuidGenerator guidGenerator, [NotNull] string type, string value) + { + Claims.Add(new ClientClaim(guidGenerator.Create(), Id, type, value)); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs new file mode 100644 index 0000000000..81e5841d6b --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Security.Claims; +using AutoMapper; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientAutoMapperProfile : Profile + { + public ClientAutoMapperProfile() + { + //TODO: Reverse maps will not used probably. Remove those will not used + + CreateMap(); + + CreateMap() + .ConstructUsing(src => src.Origin) + .ReverseMap() + .ForMember(dest => dest.Origin, opt => opt.MapFrom(src => src)); + + CreateMap() + .ForMember(dest => dest.ApiSecrets, opt => opt.MapFrom(src => src.Secrets)); + + //TODO: Why PersistedGrant mapping is in this profile? + CreateMap().ReverseMap(); + + CreateMap(); + + CreateMap() + .ConstructUsing(src => src.Type) + .ReverseMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src)); + + CreateMap(); + + CreateMap(); + + CreateMap>() + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.ProtocolType, opt => opt.Condition(srs => srs != null)) + .ReverseMap(); + + CreateMap() + .ConstructUsing(src => src.Origin) + .ReverseMap() + .ForMember(dest => dest.Origin, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.Provider) + .ReverseMap() + .ForMember(dest => dest.Provider, opt => opt.MapFrom(src => src)); + + CreateMap(MemberList.None) + .ConstructUsing(src => new Claim(src.Type, src.Value)) + .ReverseMap(); + + CreateMap() + .ConstructUsing(src => src.Scope) + .ReverseMap() + .ForMember(dest => dest.Scope, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.PostLogoutRedirectUri) + .ReverseMap() + .ForMember(dest => dest.PostLogoutRedirectUri, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.RedirectUri) + .ReverseMap() + .ForMember(dest => dest.RedirectUri, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.GrantType) + .ReverseMap() + .ForMember(dest => dest.GrantType, opt => opt.MapFrom(src => src)); + + CreateMap(MemberList.Destination) + .ForMember(dest => dest.Type, opt => opt.Condition(srs => srs != null)) + .ReverseMap(); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientClaim.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientClaim.cs new file mode 100644 index 0000000000..29b44a40f7 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientClaim.cs @@ -0,0 +1,30 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientClaim : Entity + { + public virtual Guid ClientId { get; set; } + + public virtual string Type { get; set; } + + public virtual string Value { get; set; } + + protected ClientClaim() + { + + } + + protected internal ClientClaim(Guid id, Guid clientId, [NotNull] string type, string value) + { + Check.NotNull(type, nameof(type)); + + Id = id; + ClientId = clientId; + Type = type; + Value = value; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientCorsOrigin.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientCorsOrigin.cs new file mode 100644 index 0000000000..c8177baee8 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientCorsOrigin.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientCorsOrigin : Entity + { + public virtual Guid ClientId { get; protected set; } + + public virtual string Origin { get; protected set; } + + protected ClientCorsOrigin() + { + + } + + protected internal ClientCorsOrigin(Guid clientId, [NotNull] string origin) + { + Check.NotNull(origin, nameof(origin)); + + ClientId = clientId; + Origin = origin; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, Origin }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientGrantType.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientGrantType.cs new file mode 100644 index 0000000000..ccbf0b666e --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientGrantType.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientGrantType : Entity + { + public virtual Guid ClientId { get; protected set; } + + public virtual string GrantType { get; protected set; } + + protected ClientGrantType() + { + + } + + protected internal ClientGrantType(Guid clientId, [NotNull] string grantType) + { + Check.NotNull(grantType, nameof(grantType)); + + ClientId = clientId; + GrantType = grantType; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, GrantType }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientIdPRestriction.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientIdPRestriction.cs new file mode 100644 index 0000000000..00f1aa1020 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientIdPRestriction.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientIdPRestriction : Entity + { + public virtual Guid ClientId { get; set; } + + public virtual string Provider { get; set; } + + protected ClientIdPRestriction() + { + + } + + protected internal ClientIdPRestriction(Guid clientId, [NotNull] string provider) + { + Check.NotNull(provider, nameof(provider)); + + ClientId = clientId; + Provider = provider; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, Provider }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUri.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUri.cs new file mode 100644 index 0000000000..9042d54522 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientPostLogoutRedirectUri.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientPostLogoutRedirectUri : Entity + { + public virtual Guid ClientId { get; protected set; } + + public virtual string PostLogoutRedirectUri { get; protected set; } + + protected ClientPostLogoutRedirectUri() + { + + } + + protected internal ClientPostLogoutRedirectUri(Guid clientId, [NotNull] string postLogoutRedirectUri) + { + Check.NotNull(postLogoutRedirectUri, nameof(postLogoutRedirectUri)); + + ClientId = clientId; + PostLogoutRedirectUri = postLogoutRedirectUri; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, PostLogoutRedirectUri }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientProperty.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientProperty.cs new file mode 100644 index 0000000000..a7a9da6e4f --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientProperty.cs @@ -0,0 +1,33 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientProperty : Entity + { + public virtual Guid ClientId { get; set; } + + public virtual string Key { get; set; } + + public virtual string Value { get; set; } + + protected ClientProperty() + { + + } + + protected internal ClientProperty(Guid clientId, [NotNull] string key) + { + Check.NotNull(key, nameof(key)); + + ClientId = clientId; + Key = key; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, Key }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientRedirectUri.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientRedirectUri.cs new file mode 100644 index 0000000000..ff8a3edc05 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientRedirectUri.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientRedirectUri : Entity + { + public virtual Guid ClientId { get; protected set; } + + public virtual string RedirectUri { get; protected set; } + + protected ClientRedirectUri() + { + + } + + protected internal ClientRedirectUri(Guid clientId, [NotNull] string redirectUri) + { + Check.NotNull(redirectUri, nameof(redirectUri)); + + ClientId = clientId; + RedirectUri = redirectUri; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, RedirectUri }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientScope.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientScope.cs new file mode 100644 index 0000000000..eb72ddf7cb --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientScope.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientScope : Entity + { + public virtual Guid ClientId { get; protected set; } + + public virtual string Scope { get; protected set; } + + protected ClientScope() + { + + } + + protected internal ClientScope(Guid clientId, string scope) + { + ClientId = clientId; + Scope = scope; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, Scope }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientSecret.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientSecret.cs new file mode 100644 index 0000000000..c46f4a2d34 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientSecret.cs @@ -0,0 +1,36 @@ +using System; +using IdentityServer4; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientSecret : Secret + { + public virtual Guid ClientId { get; protected set; } + + protected ClientSecret() + { + + } + + protected internal ClientSecret( + Guid clientId, + [NotNull] string value, + DateTime? expiration = null, + string type = IdentityServerConstants.SecretTypes.SharedSecret, + string description = null + ) : base( + value, + expiration, + type, + description) + { + ClientId = clientId; + } + + public override object[] GetKeys() + { + return new object[] { ClientId, Type, Value }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientStore.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientStore.cs new file mode 100644 index 0000000000..c99686000f --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientStore.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using IdentityServer4.Stores; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientStore : IClientStore + { + private readonly IClientRepository _clientRepository; + private readonly IObjectMapper _objectMapper; + + public ClientStore(IClientRepository clientRepository, IObjectMapper objectMapper) + { + _clientRepository = clientRepository; + _objectMapper = objectMapper; + } + + public virtual async Task FindClientByIdAsync(string clientId) + { + var client = await _clientRepository.FindByCliendIdAsync(clientId); + return _objectMapper.Map(client); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/IClientRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/IClientRepository.cs new file mode 100644 index 0000000000..ed45d8a90c --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/IClientRepository.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.Clients +{ + public interface IClientRepository : IBasicRepository + { + Task FindByCliendIdAsync( + [NotNull] string clientId, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs new file mode 100644 index 0000000000..6e316806b6 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.Grants +{ + public interface IPersistentGrantRepository : IBasicRepository + { + Task FindByKeyAsync( + string key, + CancellationToken cancellationToken = default + ); + + Task> GetListBySubjectIdAsync( + string key, + CancellationToken cancellationToken = default + ); + + Task DeleteAsync( + string subjectId, + string clientId, + CancellationToken cancellationToken = default + ); + + Task DeleteAsync( + string subjectId, + string clientId, + string type, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrant.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrant.cs new file mode 100644 index 0000000000..4a2bccd456 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrant.cs @@ -0,0 +1,32 @@ +using System; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.Grants +{ + public class PersistedGrant : AggregateRoot + { + public virtual string Key { get; set; } + + public virtual string Type { get; set; } + + public virtual string SubjectId { get; set; } + + public virtual string ClientId { get; set; } + + public virtual DateTime CreationTime { get; set; } + + public virtual DateTime? Expiration { get; set; } + + public virtual string Data { get; set; } + + protected PersistedGrant() + { + + } + + public PersistedGrant(Guid id) + { + Id = id; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs new file mode 100644 index 0000000000..0d40241ee5 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Stores; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.IdentityServer.Grants +{ + public class PersistedGrantStore : IPersistedGrantStore, ITransientDependency + { + private readonly IPersistentGrantRepository _persistentGrantRepository; + private readonly IObjectMapper _objectMapper; + private readonly IGuidGenerator _guidGenerator; + + public PersistedGrantStore(IPersistentGrantRepository persistentGrantRepository, IObjectMapper objectMapper, IGuidGenerator guidGenerator) + { + _persistentGrantRepository = persistentGrantRepository; + _objectMapper = objectMapper; + _guidGenerator = guidGenerator; + } + + public virtual async Task StoreAsync(IdentityServer4.Models.PersistedGrant grant) + { + var entity = _objectMapper.Map(grant); + var existing = await _persistentGrantRepository.FindByKeyAsync(grant.Key); + if (existing == null) + { + entity.Id = _guidGenerator.Create(); + await _persistentGrantRepository.InsertAsync(entity); + } + else + { + await _persistentGrantRepository.UpdateAsync(entity); + } + } + + public virtual async Task GetAsync(string key) + { + var persistedGrant = await _persistentGrantRepository.FindByKeyAsync(key); + return _objectMapper.Map(persistedGrant); + } + + public virtual async Task> GetAllAsync(string subjectId) + { + var persistedGrants = await _persistentGrantRepository.GetListBySubjectIdAsync(subjectId); + return persistedGrants.Select(x => _objectMapper.Map(x)); + } + + public virtual async Task RemoveAsync(string key) + { + var persistedGrant = await _persistentGrantRepository.FindByKeyAsync(key); + if (persistedGrant == null) + { + return; + } + + await _persistentGrantRepository.DeleteAsync(persistedGrant); + } + + public virtual async Task RemoveAllAsync(string subjectId, string clientId) + { + await _persistentGrantRepository.DeleteAsync(subjectId, clientId); + } + + public virtual async Task RemoveAllAsync(string subjectId, string clientId, string type) + { + await _persistentGrantRepository.DeleteAsync(subjectId, clientId, type); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IIdentityResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IIdentityResourceRepository.cs new file mode 100644 index 0000000000..d5bcf07071 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IIdentityResourceRepository.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.IdentityResources +{ + public interface IIdentityResourceRepository : IBasicRepository + { + Task> GetListByScopesAsync( + string[] scopeNames, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityClaim.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityClaim.cs new file mode 100644 index 0000000000..39453421ef --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityClaim.cs @@ -0,0 +1,26 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityServer.IdentityResources +{ + public class IdentityClaim : UserClaim + { + public virtual Guid IdentityResourceId { get; set; } + + protected IdentityClaim() + { + + } + + protected internal IdentityClaim(Guid identityResourceId, [NotNull] string type) + : base(type) + { + IdentityResourceId = identityResourceId; + } + + public override object[] GetKeys() + { + return new object[] { IdentityResourceId, Type }; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityResource.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityResource.cs new file mode 100644 index 0000000000..e3628ffa14 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityResources/IdentityResource.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer.IdentityResources +{ + public class IdentityResource : AggregateRoot + { + public virtual string Name { get; set; } + + public virtual string DisplayName { get; set; } + + public virtual string Description { get; set; } + + public virtual bool Enabled { get; set; } + + public virtual bool Required { get; set; } + + public virtual bool Emphasize { get; set; } + + public virtual bool ShowInDiscoveryDocument { get; set; } + + public virtual List UserClaims { get; set; } + + protected IdentityResource() + { + + } + + public IdentityResource( + Guid id, + [NotNull] string name, + string displayName = null, + string description = null, + bool enabled = true, + bool required = false, + bool emphasize = false, + bool showInDiscoveryDocument = true) + { + Check.NotNull(name, nameof(name)); + + Id = id; + Name = name; + DisplayName = displayName; + Description = description; + Enabled = enabled; + Required = required; + Emphasize = emphasize; + ShowInDiscoveryDocument = showInDiscoveryDocument; + + UserClaims = new List(); + } + + public virtual void AddUserClaim([NotNull] string type) + { + UserClaims.Add(new IdentityClaim(Id, type)); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Jwt/JwtTokenMiddleware.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Jwt/JwtTokenMiddleware.cs new file mode 100644 index 0000000000..57ca9cc4c2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Jwt/JwtTokenMiddleware.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; + +namespace Volo.Abp.IdentityServer.Jwt +{ + //TODO: Should we move this to another package..? + + public static class JwtTokenMiddleware + { + public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string schema) + { + return app.Use(async (ctx, next) => + { + if (ctx.User.Identity?.IsAuthenticated != true) + { + var result = await ctx.AuthenticateAsync(schema); + if (result.Succeeded && result.Principal != null) + { + ctx.User = result.Principal; + } + } + + await next(); + }); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ResourceStore.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ResourceStore.cs new file mode 100644 index 0000000000..8b2e79271a --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ResourceStore.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.IdentityResources; +using Volo.Abp.ObjectMapping; +using ApiResource = IdentityServer4.Models.ApiResource; +using IdentityResource = Volo.Abp.IdentityServer.IdentityResources.IdentityResource; + +namespace Volo.Abp.IdentityServer +{ + public class ResourceStore : IResourceStore + { + private readonly IIdentityResourceRepository _identityResourceRepository; + private readonly IApiResourceRepository _apiResourceRepository; + private readonly IObjectMapper _objectMapper; + + public ResourceStore( + IIdentityResourceRepository identityResourceRepository, + IObjectMapper objectMapper, + IApiResourceRepository apiResourceRepository) + { + _identityResourceRepository = identityResourceRepository; + _objectMapper = objectMapper; + _apiResourceRepository = apiResourceRepository; + } + + public virtual async Task> FindIdentityResourcesByScopeAsync(IEnumerable scopeNames) + { + var resource = await _identityResourceRepository.GetListByScopesAsync(scopeNames.ToArray(), includeDetails: true); + return _objectMapper.Map, List>(resource); + } + + public virtual async Task> FindApiResourcesByScopeAsync(IEnumerable scopeNames) + { + var resources = await _apiResourceRepository.GetListByScopesAsync(scopeNames.ToArray(), includeDetails: true); + return resources.Select(x => _objectMapper.Map(x)); + } + + public virtual async Task FindApiResourceAsync(string name) + { + var resource = await _apiResourceRepository.FindByNameAsync(name); + return _objectMapper.Map(resource); + } + + public virtual async Task GetAllResourcesAsync() + { + var identityResources = await _identityResourceRepository.GetListAsync(includeDetails: true); + var apiResources = await _apiResourceRepository.GetListAsync(includeDetails: true); + + return new Resources( + _objectMapper.Map, IdentityServer4.Models.IdentityResource[]>(identityResources), + _objectMapper.Map, ApiResource[]>(apiResources) + ); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Secret.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Secret.cs new file mode 100644 index 0000000000..faef3a12ed --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Secret.cs @@ -0,0 +1,37 @@ +using System; +using IdentityServer4; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer +{ + public abstract class Secret : Entity + { + public virtual string Type { get; protected set; } + + public virtual string Value { get; set; } + + public virtual string Description { get; set; } + + public virtual DateTime? Expiration { get; set; } + + protected Secret() + { + + } + + protected Secret( + [NotNull] string value, + DateTime? expiration = null, + string type = IdentityServerConstants.SecretTypes.SharedSecret, + string description = null) + { + Check.NotNull(value, nameof(value)); + + Value = value; + Expiration = expiration; + Type = type; + Description = description; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Temp/IdentityServerConfig.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Temp/IdentityServerConfig.cs new file mode 100644 index 0000000000..183a9b61ac --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Temp/IdentityServerConfig.cs @@ -0,0 +1,37 @@ +namespace Volo.Abp.IdentityServer.Temp +{ + //TODO: Remove! + //internal static class IdentityServerConfig + //{ + // public static IEnumerable GetApiResources() + // { + // return new List + // { + // new ApiResource("api1", "My API") + // }; + // } + + // public static IEnumerable GetClients() + // { + // return new List + // { + // new Client + // { + // ClientId = "client", + + // // no interactive user, use the clientid/secret for authentication + // AllowedGrantTypes = GrantTypes.ClientCredentials, + + // // secret for authentication + // ClientSecrets = + // { + // new IdentityServer4.Models.Secret("secret".Sha256()) + // }, + + // // scopes that client has access to + // AllowedScopes = { "api1" } + // } + // }; + // } + //} +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/UserClaim.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/UserClaim.cs new file mode 100644 index 0000000000..7acaa695db --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/UserClaim.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.IdentityServer +{ + public abstract class UserClaim : Entity + { + public virtual string Type { get; protected set; } + + protected UserClaim() + { + + } + + protected UserClaim([NotNull] string type) + { + Check.NotNull(type, nameof(type)); + + Type = type; + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj new file mode 100644 index 0000000000..df8b285c1b --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.IdentityServer.EntityFrameworkCore + Volo.Abp.IdentityServer.EntityFrameworkCore + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj.DotSettings b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo.Abp.IdentityServer.EntityFrameworkCore.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/AbpIdentityServerEfCoreQueryableExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/AbpIdentityServerEfCoreQueryableExtensions.cs new file mode 100644 index 0000000000..51b490fc47 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/AbpIdentityServerEfCoreQueryableExtensions.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace Volo.Abp.IdentityServer +{ + public static class AbpIdentityServerEfCoreQueryableExtensions + { + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.Secrets) + .Include(x => x.UserClaims) + .Include(x => x.Scopes) + .ThenInclude(s => s.UserClaims); + } + + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.UserClaims); + } + + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.AllowedGrantTypes) + .Include(x => x.RedirectUris) + .Include(x => x.PostLogoutRedirectUris) + .Include(x => x.AllowedScopes) + .Include(x => x.ClientSecrets) + .Include(x => x.Claims) + .Include(x => x.IdentityProviderRestrictions) + .Include(x => x.AllowedCorsOrigins) + .Include(x => x.Properties); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResources/ApiResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResources/ApiResourceRepository.cs new file mode 100644 index 0000000000..d5e9f841a7 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResources/ApiResourceRepository.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public class ApiResourceRepository : EfCoreRepository, IApiResourceRepository + { + public ApiResourceRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + + } + + public virtual async Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var query = from apiResource in DbSet.IncludeDetails(includeDetails) + where apiResource.Name == name + select apiResource; + + return await query + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListByScopesAsync( + string[] scopeNames, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = from api in DbSet.IncludeDetails(includeDetails) + where api.Scopes.Any(x => scopeNames.Contains(x.Name)) + select api; + + return await query.ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListAsync( + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public override IQueryable WithDetails() + { + return GetQueryable().IncludeDetails(); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs new file mode 100644 index 0000000000..3006678edf --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Clients/ClientRepository.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientRepository : EfCoreRepository, IClientRepository + { + public ClientRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + + } + + public virtual async Task FindByCliendIdAsync( + string clientId, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync(x => x.ClientId == clientId, GetCancellationToken(cancellationToken)); + } + + public override IQueryable WithDetails() + { + return GetQueryable().IncludeDetails(); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/AbpIdentityServerEntityFrameworkCoreModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/AbpIdentityServerEntityFrameworkCoreModule.cs new file mode 100644 index 0000000000..3ef521b474 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/AbpIdentityServerEntityFrameworkCoreModule.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; +using Volo.Abp.Modularity; + +namespace Volo.Abp.IdentityServer.EntityFrameworkCore +{ + [DependsOn(typeof(AbpIdentityServerDomainModule))] + [DependsOn(typeof(AbpEntityFrameworkCoreModule))] + public class AbpIdentityServerEntityFrameworkCoreModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(); + + options.AddRepository(); + options.AddRepository(); + options.AddRepository(); + options.AddRepository(); + }); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IIdentityServerDbContext.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IIdentityServerDbContext.cs new file mode 100644 index 0000000000..ded2ca3031 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IIdentityServerDbContext.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace Volo.Abp.IdentityServer.EntityFrameworkCore +{ + [ConnectionStringName("AbpIdentityServer")] + public interface IIdentityServerDbContext : IEfCoreDbContext + { + DbSet ApiResources { get; set; } + + DbSet ApiSecrets { get; set; } + + DbSet ApiResourceClaims { get; set; } + + DbSet ApiScopes { get; set; } + + DbSet ApiScopeClaims { get; set; } + + DbSet IdentityResources { get; set; } + + DbSet IdentityClaims { get; set; } + + DbSet Clients { get; set; } + + DbSet ClientGrantTypes { get; set; } + + DbSet ClientRedirectUris { get; set; } + + DbSet ClientPostLogoutRedirectUris { get; set; } + + DbSet ClientScopes { get; set; } + + DbSet ClientSecrets { get; set; } + + DbSet ClientClaims { get; set; } + + DbSet ClientIdPRestrictions { get; set; } + + DbSet ClientCorsOrigins { get; set; } + + DbSet ClientProperties { get; set; } + + DbSet PersistedGrants { get; set; } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContext.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContext.cs new file mode 100644 index 0000000000..bda3957e37 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContext.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace Volo.Abp.IdentityServer.EntityFrameworkCore +{ + [ConnectionStringName("AbpIdentityServer")] + public class IdentityServerDbContext : AbpDbContext, IIdentityServerDbContext + { + public static string TablePrefix { get; set; } = AbpIdentityServerConsts.DefaultDbTablePrefix; + + public static string Schema { get; set; } = AbpIdentityServerConsts.DefaultDbSchema; + + public DbSet ApiResources { get; set; } + + public DbSet ApiSecrets { get; set; } + + public DbSet ApiResourceClaims { get; set; } + + public DbSet ApiScopes { get; set; } + + public DbSet ApiScopeClaims { get; set; } + + public DbSet IdentityResources { get; set; } + + public DbSet IdentityClaims { get; set; } + + public DbSet Clients { get; set; } + + public DbSet ClientGrantTypes { get; set; } + + public DbSet ClientRedirectUris { get; set; } + + public DbSet ClientPostLogoutRedirectUris { get; set; } + + public DbSet ClientScopes { get; set; } + + public DbSet ClientSecrets { get; set; } + + public DbSet ClientClaims { get; set; } + + public DbSet ClientIdPRestrictions { get; set; } + + public DbSet ClientCorsOrigins { get; set; } + + public DbSet ClientProperties { get; set; } + + public DbSet PersistedGrants { get; set; } + + public IdentityServerDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ConfigureIdentityServer(TablePrefix, Schema); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs new file mode 100644 index 0000000000..a6e9522919 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs @@ -0,0 +1,228 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; + +namespace Volo.Abp.IdentityServer.EntityFrameworkCore +{ + public static class IdentityServerDbContextModelCreatingExtensions + { + public static void ConfigureIdentityServer( + this ModelBuilder builder, + [CanBeNull] string tablePrefix = AbpIdentityServerConsts.DefaultDbTablePrefix, + [CanBeNull] string schema = AbpIdentityServerConsts.DefaultDbSchema) + { + Check.NotNull(builder, nameof(builder)); + + if (tablePrefix == null) + { + tablePrefix = ""; + } + + builder.Entity(client => + { + client.ToTable(tablePrefix + "Clients", schema); + + client.Property(x => x.ClientId).HasMaxLength(ClientConsts.ClientIdMaxLength).IsRequired(); + client.Property(x => x.ProtocolType).HasMaxLength(ClientConsts.ProtocolTypeMaxLength).IsRequired(); + client.Property(x => x.ClientName).HasMaxLength(ClientConsts.ClientNameMaxLength); + client.Property(x => x.ClientUri).HasMaxLength(ClientConsts.ClientUriMaxLength); + client.Property(x => x.LogoUri).HasMaxLength(ClientConsts.LogoUriMaxLength); + client.Property(x => x.Description).HasMaxLength(ClientConsts.DescriptionMaxLength); + client.Property(x => x.FrontChannelLogoutUri).HasMaxLength(ClientConsts.FrontChannelLogoutUriMaxLength); + client.Property(x => x.BackChannelLogoutUri).HasMaxLength(ClientConsts.BackChannelLogoutUriMaxLength); + client.Property(x => x.ClientClaimsPrefix).HasMaxLength(ClientConsts.ClientClaimsPrefixMaxLength); + client.Property(x => x.PairWiseSubjectSalt).HasMaxLength(ClientConsts.PairWiseSubjectSaltMaxLength); + + client.HasMany(x => x.AllowedScopes).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.ClientSecrets).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.AllowedGrantTypes).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.AllowedCorsOrigins).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.RedirectUris).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.PostLogoutRedirectUris).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.IdentityProviderRestrictions).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.Claims).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + client.HasMany(x => x.Properties).WithOne().HasForeignKey(x => x.ClientId).IsRequired(); + + client.HasIndex(x => x.ClientId).IsUnique(); + }); + + builder.Entity(grantType => + { + grantType.ToTable(tablePrefix + "ClientGrantTypes", schema); + + grantType.HasKey(x => new { x.ClientId, x.GrantType }); + + grantType.Property(x => x.GrantType).HasMaxLength(ClientGrantTypeConsts.GrantTypeMaxLength).IsRequired(); + }); + + builder.Entity(redirectUri => + { + redirectUri.ToTable(tablePrefix + "ClientRedirectUris", schema); + + redirectUri.HasKey(x => new { x.ClientId, x.RedirectUri }); + + redirectUri.Property(x => x.RedirectUri).HasMaxLength(ClientRedirectUriConsts.RedirectUriMaxLength).IsRequired(); + }); + + builder.Entity(postLogoutRedirectUri => + { + postLogoutRedirectUri.ToTable(tablePrefix + "ClientPostLogoutRedirectUris", schema); + + postLogoutRedirectUri.HasKey(x => new { x.ClientId, x.PostLogoutRedirectUri }); + + postLogoutRedirectUri.Property(x => x.PostLogoutRedirectUri).HasMaxLength(ClientPostLogoutRedirectUriConsts.PostLogoutRedirectUriMaxLength).IsRequired(); + }); + + builder.Entity(scope => + { + scope.ToTable(tablePrefix + "ClientScopes", schema); + + scope.HasKey(x => new { x.ClientId, x.Scope }); + + scope.Property(x => x.Scope).HasMaxLength(ClientScopeConsts.ScopeMaxLength).IsRequired(); + }); + + builder.Entity(secret => + { + secret.ToTable(tablePrefix + "ClientSecrets", schema); + + secret.HasKey(x => new { x.ClientId, x.Type, x.Value }); + + secret.Property(x => x.Type).HasMaxLength(SecretConsts.TypeMaxLength).IsRequired(); + secret.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLength).IsRequired(); + secret.Property(x => x.Description).HasMaxLength(SecretConsts.DescriptionMaxLength); + }); + + builder.Entity(claim => + { + claim.ToTable(tablePrefix + "ClientClaims", schema); + + claim.Property(x => x.Type).HasMaxLength(ClientClaimConsts.TypeMaxLength).IsRequired(); + claim.Property(x => x.Value).HasMaxLength(ClientClaimConsts.ValueMaxLength).IsRequired(); + }); + + builder.Entity(idPRestriction => + { + idPRestriction.ToTable(tablePrefix + "ClientIdPRestrictions", schema); + + idPRestriction.HasKey(x => new { x.ClientId, x.Provider }); + + idPRestriction.Property(x => x.Provider).HasMaxLength(ClientIdPRestrictionConsts.ProviderMaxLength).IsRequired(); + }); + + builder.Entity(corsOrigin => + { + corsOrigin.ToTable(tablePrefix + "ClientCorsOrigins", schema); + + corsOrigin.HasKey(x => new { x.ClientId, x.Origin }); + + corsOrigin.Property(x => x.Origin).HasMaxLength(ClientCorsOriginConsts.OriginMaxLength).IsRequired(); + }); + + builder.Entity(property => + { + property.ToTable(tablePrefix + "ClientProperties", schema); + + property.HasKey(x => new { x.ClientId, x.Key }); + + property.Property(x => x.Key).HasMaxLength(ClientPropertyConsts.KeyMaxLength).IsRequired(); + property.Property(x => x.Value).HasMaxLength(ClientPropertyConsts.ValueMaxLength).IsRequired(); + }); + + builder.Entity(grant => + { + grant.ToTable(tablePrefix + "PersistedGrants", schema); + + grant.Property(x => x.Key).HasMaxLength(PersistedGrantConsts.KeyMaxLength).ValueGeneratedNever(); + grant.Property(x => x.Type).HasMaxLength(PersistedGrantConsts.TypeMaxLength).IsRequired(); + grant.Property(x => x.SubjectId).HasMaxLength(PersistedGrantConsts.SubjectIdMaxLength); + grant.Property(x => x.ClientId).HasMaxLength(PersistedGrantConsts.ClientIdMaxLength).IsRequired(); + grant.Property(x => x.CreationTime).IsRequired(); + grant.Property(x => x.Data).IsRequired(); + + grant.HasKey(x => x.Key); //TODO: What about Id!!! + + grant.HasIndex(x => new { x.SubjectId, x.ClientId, x.Type }); + }); + + builder.Entity(identityResource => + { + identityResource.ToTable(tablePrefix + "IdentityResources", schema); + + identityResource.Property(x => x.Name).HasMaxLength(IdentityResourceConsts.NameMaxLength).IsRequired(); + identityResource.Property(x => x.DisplayName).HasMaxLength(IdentityResourceConsts.DisplayNameMaxLength); + identityResource.Property(x => x.Description).HasMaxLength(IdentityResourceConsts.DescriptionMaxLength); + + identityResource.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => x.IdentityResourceId).IsRequired(); + }); + + builder.Entity(claim => + { + claim.ToTable(tablePrefix + "IdentityClaims", schema); + + claim.HasKey(x => new { x.IdentityResourceId, x.Type }); + + claim.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); + }); + + builder.Entity(apiResource => + { + apiResource.ToTable(tablePrefix + "ApiResources", schema); + + apiResource.Property(x => x.Name).HasMaxLength(ApiResourceConsts.NameMaxLength).IsRequired(); + apiResource.Property(x => x.DisplayName).HasMaxLength(ApiResourceConsts.DisplayNameMaxLength); + apiResource.Property(x => x.Description).HasMaxLength(ApiResourceConsts.DescriptionMaxLength); + + apiResource.HasMany(x => x.Secrets).WithOne().HasForeignKey(x => x.ApiResourceId).IsRequired(); + apiResource.HasMany(x => x.Scopes).WithOne().HasForeignKey(x => x.ApiResourceId).IsRequired(); + apiResource.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => x.ApiResourceId).IsRequired(); + }); + + builder.Entity(apiSecret => + { + apiSecret.ToTable(tablePrefix + "ApiSecrets", schema); + + apiSecret.HasKey(x => new { x.ApiResourceId, x.Type, x.Value }); + + apiSecret.Property(x => x.Type).HasMaxLength(SecretConsts.TypeMaxLength).IsRequired(); + apiSecret.Property(x => x.Value).HasMaxLength(SecretConsts.ValueMaxLength).IsRequired(); + apiSecret.Property(x => x.Description).HasMaxLength(SecretConsts.DescriptionMaxLength); + }); + + builder.Entity(apiClaim => + { + apiClaim.ToTable(tablePrefix + "ApiClaims", schema); + + apiClaim.HasKey(x => new { x.ApiResourceId, x.Type }); + + apiClaim.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); + }); + + builder.Entity(apiScope => + { + apiScope.ToTable(tablePrefix + "ApiScopes", schema); + + apiScope.HasKey(x => new { x.ApiResourceId, x.Name }); + + apiScope.Property(x => x.Name).HasMaxLength(ApiScopeConsts.NameMaxLength).IsRequired(); + apiScope.Property(x => x.DisplayName).HasMaxLength(ApiScopeConsts.DisplayNameMaxLength); + apiScope.Property(x => x.Description).HasMaxLength(ApiScopeConsts.DescriptionMaxLength); + + apiScope.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => new { x.ApiResourceId, x.Name }).IsRequired(); + }); + + builder.Entity(apiScopeClaim => + { + apiScopeClaim.ToTable(tablePrefix + "ApiScopeClaims", schema); + + apiScopeClaim.HasKey(x => new { x.ApiResourceId, x.Name, x.Type }); + + apiScopeClaim.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired(); + apiScopeClaim.Property(x => x.Name).HasMaxLength(ApiScopeConsts.NameMaxLength).IsRequired(); + }); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs new file mode 100644 index 0000000000..6f8b8fd4aa --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; + +namespace Volo.Abp.IdentityServer.Grants +{ + public class PersistentGrantRepository : EfCoreRepository, IPersistentGrantRepository + { + public PersistentGrantRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + + } + + public Task FindByKeyAsync( + string key, + CancellationToken cancellationToken = default) + { + return DbSet + .FirstOrDefaultAsync(x => x.Key == key, GetCancellationToken(cancellationToken)); + } + + public Task> GetListBySubjectIdAsync( + string subjectId, + CancellationToken cancellationToken = default) + { + return DbSet + .Where(x => x.SubjectId == subjectId) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task DeleteAsync( + string subjectId, + string clientId, + CancellationToken cancellationToken = default) + { + await DeleteAsync( + x => x.SubjectId == subjectId && x.ClientId == clientId, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } + + public async Task DeleteAsync( + string subjectId, + string clientId, + string type, + CancellationToken cancellationToken = default) + { + await DeleteAsync( + x => x.SubjectId == subjectId && x.ClientId == clientId && x.Type == type, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceRepository.cs new file mode 100644 index 0000000000..58fde21ea7 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/IdentityResources/IdentityResourceRepository.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; + +namespace Volo.Abp.IdentityServer.IdentityResources +{ + public class IdentityResourceRepository : EfCoreRepository, IIdentityResourceRepository + { + public IdentityResourceRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public virtual async Task> GetListByScopesAsync( + string[] scopeNames, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = from identityResource in DbSet.IncludeDetails(includeDetails) + where scopeNames.Contains(identityResource.Name) + select identityResource; + + return await query.ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListAsync( + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public override IQueryable WithDetails() + { + return GetQueryable().IncludeDetails(); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests.csproj b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000000..3c177cda81 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,33 @@ + + + + netcoreapp2.0 + Volo.Abp.IdentityServer.EntityFrameworkCore.Tests + Volo.Abp.IdentityServer.EntityFrameworkCore.Tests + true + false + false + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityApplicationTestBase.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityApplicationTestBase.cs new file mode 100644 index 0000000000..b102a72329 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityApplicationTestBase.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.IdentityServer +{ + public class AbpIdentityServerTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs new file mode 100644 index 0000000000..1bbeb65e07 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs @@ -0,0 +1,129 @@ +using IdentityServer4.Models; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; +using ApiResource = Volo.Abp.IdentityServer.ApiResources.ApiResource; +using Client = Volo.Abp.IdentityServer.Clients.Client; +using IdentityResource = Volo.Abp.IdentityServer.IdentityResources.IdentityResource; +using PersistedGrant = Volo.Abp.IdentityServer.Grants.PersistedGrant; + +namespace Volo.Abp.IdentityServer +{ + public class AbpIdentityServerTestDataBuilder : ITransientDependency + { + private readonly IGuidGenerator _guidGenerator; + private readonly IClientRepository _clientRepository; + private readonly IPersistentGrantRepository _persistentGrantRepository; + private readonly IApiResourceRepository _apiResourceRepository; + private readonly IIdentityResourceRepository _identityResourceRepository; + + public AbpIdentityServerTestDataBuilder( + IClientRepository clientRepository, + IGuidGenerator guidGenerator, + IPersistentGrantRepository persistentGrantRepository, + IApiResourceRepository apiResourceRepository, + IIdentityResourceRepository identityResourceRepository) + { + _clientRepository = clientRepository; + _guidGenerator = guidGenerator; + _persistentGrantRepository = persistentGrantRepository; + _apiResourceRepository = apiResourceRepository; + _identityResourceRepository = identityResourceRepository; + } + + public void Build() + { + AddClients(); + AddPersistentGrants(); + AddApiResources(); + AddIdentityResources(); + } + + private void AddClients() + { + var client42 = new Client(_guidGenerator.Create(), "42") + { + ProtocolType = "TestProtocol-42" + }; + + client42.AddCorsOrigin("Origin1"); + + client42.AddScope("api1"); + + _clientRepository.Insert(client42); + } + + private void AddPersistentGrants() + { + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "38", + ClientId = "TestClientId-38", + Type = "TestType-38", + SubjectId = "TestSubject", + Data = "TestData-38" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "37", + ClientId = "TestClientId-37", + Type = "TestType-37", + SubjectId = "TestSubject", + Data = "TestData-37" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "36", + ClientId = "TestClientId-X", + Type = "TestType-36", + SubjectId = "TestSubject-X", + Data = "TestData-36" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "35", + ClientId = "TestClientId-X", + Type = "TestType-35", + SubjectId = "TestSubject-X", + Data = "TestData-35" + }); + } + + private void AddApiResources() + { + var apiResource = new ApiResource(_guidGenerator.Create(), "Test-ApiResource-Name-1") + { + Enabled = true, + Description = "Test-ApiResource-Description-1", + DisplayName = "Test-ApiResource-DisplayName-1" + }; + + apiResource.AddSecret("secret".Sha256()); + apiResource.AddScope("Test-ApiResource-ApiScope-Name-1", "Test-ApiResource-ApiScope-DisplayName-1"); + apiResource.AddUserClaim("Test-ApiResource-Claim-Type-1"); + + _apiResourceRepository.Insert(apiResource); + } + + private void AddIdentityResources() + { + var identityResource = new IdentityResource(_guidGenerator.Create(), "Test-Identity-Resource-Name-1") + { + Description = "Test-Identity-Resource-Description-1", + DisplayName = "Test-Identity-Resource-DisplayName-1", + Required = true, + Emphasize = true + }; + + identityResource.AddUserClaim("Test-Identity-Resource-1-IdentityClaim-Type-1"); + + _identityResourceRepository.Insert(identityResource); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestEntityFrameworkCoreModule.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestEntityFrameworkCoreModule.cs new file mode 100644 index 0000000000..8f0122673d --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestEntityFrameworkCoreModule.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; + +namespace Volo.Abp.IdentityServer +{ + [DependsOn(typeof(AbpAutofacModule))] + [DependsOn(typeof(AbpIdentityServerEntityFrameworkCoreModule))] + [DependsOn(typeof(AbpIdentityEntityFrameworkCoreModule))] + public class AbpIdentityServerTestEntityFrameworkCoreModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddEntityFrameworkInMemoryDatabase(); + + var databaseName = Guid.NewGuid().ToString(); + + context.Services.Configure(options => + { + options.Configure(abpDbContextConfigurationContext => + { + abpDbContextConfigurationContext.DbContextOptions.UseInMemoryDatabase(databaseName); + }); + }); + + context.Services.Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + SeedTestData(context); + } + + private static void SeedTestData(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .Build(); + } + } + } +} \ No newline at end of file diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/ClientStore_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/ClientStore_Tests.cs new file mode 100644 index 0000000000..e89f385b57 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/ClientStore_Tests.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using IdentityServer4.Stores; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class ClientStore_Tests : AbpIdentityServerTestBase + { + private readonly IClientStore _clientStore; + + public ClientStore_Tests() + { + _clientStore = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindClientByIdAsync_Should_Return_Null_If_Not_Found() + { + var client = await _clientStore.FindClientByIdAsync("non-existing-id"); + client.ShouldBeNull(); + } + + [Fact] + public async Task FindClientByIdAsync_Should_Return_The_Client_If_Found() + { + //Act + var client = await _clientStore.FindClientByIdAsync("42"); + + //Assert + client.ShouldNotBeNull(); + client.ClientId.ShouldBe("42"); + client.ProtocolType.ShouldBe("TestProtocol-42"); + client.AllowedCorsOrigins.ShouldContain("Origin1"); + client.AllowedScopes.ShouldContain("api1"); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs new file mode 100644 index 0000000000..44c37b93b7 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class IdentityResourceStore_Tests : AbpIdentityServerTestBase + { + private readonly IResourceStore _resourceStore; + + public IdentityResourceStore_Tests() + { + _resourceStore = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindApiResourceAsync_Should_Return_Null_If_Not_Found() + { + //Act + var resource = await _resourceStore.FindApiResourceAsync("non-existing-name"); + + //Assert + resource.ShouldBeNull(); + } + + [Fact] + public async Task FindApiResourceAsync_Should_Return_If_Found() + { + //Act + var apiResource = await _resourceStore.FindApiResourceAsync("Test-ApiResource-Name-1"); + + //Assert + apiResource.ShouldNotBe(null); + apiResource.Name.ShouldBe("Test-ApiResource-Name-1"); + apiResource.Description.ShouldBe("Test-ApiResource-Description-1"); + apiResource.DisplayName.ShouldBe("Test-ApiResource-DisplayName-1"); + } + + [Fact] + public async Task FindApiResourcesByScopeAsync_Should_Return_If_Found() + { + //Act + var apiResources = (await _resourceStore.FindApiResourcesByScopeAsync(new List + { + "Test-ApiResource-ApiScope-Name-1" + })).ToList(); + + //Assert + apiResources.ShouldNotBe(null); + + apiResources[0].Scopes.Count.ShouldBe(2); + } + + [Fact] + public async Task FindIdentityResourcesByScopeAsync_Should_Return_For_Given_Scopes() + { + //Act + var identityResourcesByScope = await _resourceStore.FindIdentityResourcesByScopeAsync(new List + { + "Test-Identity-Resource-Name-1" + }); + + //Assert + var resourcesByScope = identityResourcesByScope as IdentityResource[] ?? identityResourcesByScope.ToArray(); + resourcesByScope.Length.ShouldBe(1); + resourcesByScope.First().DisplayName.ShouldBe("Test-Identity-Resource-DisplayName-1"); + resourcesByScope.First().Description.ShouldBe("Test-Identity-Resource-Description-1"); + resourcesByScope.First().Required.ShouldBe(true); + } + + [Fact] + public async Task GetAllResourcesAsync_Should_Return() + { + //Act + var resources = await _resourceStore.GetAllResourcesAsync(); + + //Assert + resources.ShouldNotBe(null); + resources.ApiResources.Count.ShouldBe(1); + resources.ApiResources.First().Name.ShouldBe("Test-ApiResource-Name-1"); + resources.IdentityResources.First().Name.ShouldBe("Test-Identity-Resource-Name-1"); + resources.IdentityResources.First().Required.ShouldBe(true); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs new file mode 100644 index 0000000000..9a87e30d64 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs @@ -0,0 +1,130 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class PersistentGrantStore_Tests : AbpIdentityServerTestBase + { + private readonly IPersistedGrantStore _persistedGrantStore; + + public PersistentGrantStore_Tests() + { + _persistedGrantStore = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindClientByIdAsync_Should_Return_Null_If_Not_Found() + { + var persistentGrant = await _persistedGrantStore.GetAsync("not-existing-id"); + persistentGrant.ShouldBeNull(); + } + + [Fact] + public async Task FindPersistentGrantByIdAsync_Should_Return_The_PersistentGrant_If_Found() + { + //Act + var client = await _persistedGrantStore.GetAsync("38"); + + //Assert + client.ShouldNotBeNull(); + client.ClientId.ShouldBe("TestClientId-38"); + client.SubjectId.ShouldBe("TestSubject"); + client.Data.ShouldContain("TestData-38"); + client.Type.ShouldContain("TestType-38"); + } + + [Fact] + public async Task StoreAsync_Should_Store_PersistedGrant() + { + //Act + await _persistedGrantStore.StoreAsync(new PersistedGrant + { + Key = "39", + ClientId = "TestClientId-39", + Type = "TestType-39", + SubjectId = "TestSubject", + Data = "TestData-39", + Expiration = new DateTime(2018, 1, 6, 21, 22, 23), + CreationTime = new DateTime(2018, 1, 5, 19, 20, 21) + }); + + //Assert + var persistedGrant = await _persistedGrantStore.GetAsync("39"); + persistedGrant.Key.ShouldBe("39"); + persistedGrant.ClientId.ShouldBe("TestClientId-39"); + persistedGrant.Type.ShouldBe("TestType-39"); + persistedGrant.SubjectId.ShouldBe("TestSubject"); + persistedGrant.Data.ShouldBe("TestData-39"); + + persistedGrant.Expiration.HasValue.ShouldBe(true); + persistedGrant.Expiration.Value.Year.ShouldBe(2018); + persistedGrant.Expiration.Value.Month.ShouldBe(1); + persistedGrant.Expiration.Value.Day.ShouldBe(6); + persistedGrant.Expiration.Value.Hour.ShouldBe(21); + persistedGrant.Expiration.Value.Minute.ShouldBe(22); + persistedGrant.Expiration.Value.Second.ShouldBe(23); + + persistedGrant.CreationTime.Year.ShouldBe(2018); + persistedGrant.CreationTime.Month.ShouldBe(1); + persistedGrant.CreationTime.Day.ShouldBe(5); + persistedGrant.CreationTime.Hour.ShouldBe(19); + persistedGrant.CreationTime.Minute.ShouldBe(20); + persistedGrant.CreationTime.Second.ShouldBe(21); + } + + [Fact] + public async Task GetAllAsync_Should_Get_All_PersistedGrants_For_A_Given_SubjectId() + { + //Act + var persistentGrants = await _persistedGrantStore.GetAllAsync("TestSubject"); + + //Assert + var persistedGrants = persistentGrants as PersistedGrant[] ?? persistentGrants.ToArray(); + persistedGrants.ShouldNotBe(null); + persistedGrants.Length.ShouldBe(2); + persistedGrants[0].SubjectId.ShouldBe("TestSubject"); + persistedGrants[1].SubjectId.ShouldBe("TestSubject"); + } + + [Fact] + public async Task RemoveAsync_Should_Remove_PeristedGrant() + { + //Arrange + await _persistedGrantStore.StoreAsync(new PersistedGrant + { + Key = "#1P3R" + }); + + //Act + await _persistedGrantStore.RemoveAsync("#1P3R"); + + //Assert + var persistedGrant = await _persistedGrantStore.GetAsync("#1P3R"); + persistedGrant.ShouldBe(null); + } + + [Fact] + public async Task RemoveAllAsync_Should_RemoveAll_PeristedGrants_For_A_Given_Subject_And_ClientId() + { + //Arrange + var persistedGrantsWithTestSubjectX = await _persistedGrantStore.GetAllAsync("TestSubject-X"); + var persistedGrantsWithTestSubjectXBeforeLength = persistedGrantsWithTestSubjectX.ToArray().Length; + + //Act + await _persistedGrantStore.RemoveAllAsync("TestSubject-X", "TestClientId-X"); + + //Assert + persistedGrantsWithTestSubjectXBeforeLength.ShouldBe(2); + + var persistedGrants = (await _persistedGrantStore.GetAllAsync("TestClientId-37")).ToArray(); + persistedGrants.ShouldNotBe(null); + persistedGrants.Length.ShouldBe(0); + } + } +}