mirror of https://github.com/abpframework/abp.git
476 changed files with 5104 additions and 2895 deletions
|
After Width: | Height: | Size: 676 KiB |
@ -0,0 +1,95 @@ |
|||
# Upgrade Your Existing Projects to .NET 8 & ABP 8.0 |
|||
|
|||
A new .NET version was released on November 14, 2023 and ABP 8.0 RC.1 shipped based on .NET 8.0 just after Microsoft's .NET 8.0 release. Therefore, it's a good time to see what we need to do to upgrade our existing projects to .NET 8.0. |
|||
|
|||
Despite all the related dependency upgrades and changes made on ABP Framework and ABP Commercial sides, we still need to make some changes. Let's see the required actions that need to be taken in the following sections. |
|||
|
|||
## Installing the .NET 8.0 SDK |
|||
|
|||
To get started with ASP.NET Core in .NET 8.0, you need to install the .NET 8 SDK. You can install it at [https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). |
|||
|
|||
After installing the SDK & Runtime, you can upgrade your existing ASP.NET Core application to .NET 8.0. |
|||
|
|||
## Updating the Target Framework |
|||
|
|||
First, you need to update all your `*.csproj` files to support .NET 8. Find and replace all your `TargetFramework` definitions in the `*.csproj` files to support .NET 8.0: |
|||
|
|||
```xml |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
``` |
|||
|
|||
> This and all other changes mentioned in this article have already been done in the ABP Framework and ABP Commercial side, so you would not get any problems related to that. |
|||
|
|||
## Updating Microsoft Package Versions |
|||
|
|||
You are probably using some Microsoft packages in your solution, so you need to update them to the latest .NET 8.0 version. Therefore, update all `Microsoft.AspNetCore.*` and `Microsoft.Extensions.*` packages' references to `8.0.0`. |
|||
|
|||
## Checking the Breaking Changes in .NET 8.0 |
|||
|
|||
As I have mentioned earlier in this article, on the ABP Framework & ABP Commercial sides all the related code changes have been made, so you would not get any error related to breaking changes introduced with .NET 8.0. However, you still need to check the [Breaking Changes in .NET 8.0 documentation](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0), because the breaking changes listed in this documentation still might affect you. Therefore, read them accordingly and make the related changes in your application, if needed. |
|||
|
|||
## Update Your Global Dotnet CLI Tools (optional) |
|||
|
|||
You can update the global dotnet tools to the latest version by running the `dotnet tool update` command. For example, if you are using EF Core, you can update your `dotnet-ef` CLI tool with the following command: |
|||
|
|||
```bash |
|||
dotnet tool update dotnet-ef --global |
|||
``` |
|||
|
|||
## Installing/Restoring the Workloads (required for Blazor WASM & MAUI apps) |
|||
|
|||
The `dotnet workload restore` command installs the workloads needed for a project or a solution. This command analyzes a project or solution to determine which workloads are needed and if you have a .NET MAUI or Blazor-WASM project, you can update your workloads by running the following command in a terminal: |
|||
|
|||
```bash |
|||
dotnet workload restore |
|||
``` |
|||
|
|||
## Docker Image Updates |
|||
|
|||
If you are using Docker to automate the deployment of applications, you also need to update your images. |
|||
|
|||
For example, you can update the ASP.NET Core image as follows: |
|||
|
|||
```diff |
|||
- FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim AS base |
|||
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0-bullseye-slim AS base |
|||
``` |
|||
|
|||
You can check the related images from Docker Hub and update them accordingly: |
|||
|
|||
* [https://hub.docker.com/_/microsoft-dotnet-aspnet/](https://hub.docker.com/_/microsoft-dotnet-aspnet/) |
|||
* [https://hub.docker.com/_/microsoft-dotnet-sdk/](https://hub.docker.com/_/microsoft-dotnet-sdk/) |
|||
* [https://hub.docker.com/_/microsoft-dotnet-runtime/](https://hub.docker.com/_/microsoft-dotnet-runtime/) |
|||
|
|||
## Upgrading Your Existing Projects to ABP 8.0 |
|||
|
|||
Updating your application to ABP 8.0 is pretty straight-forward. You first need to upgrade the ABP CLI to version `8.0.0-rc.1` using a command line terminal: |
|||
|
|||
````bash |
|||
dotnet tool update Volo.Abp.Cli -g --version 8.0.0-rc.1 |
|||
```` |
|||
|
|||
**or install** it if you haven't before: |
|||
|
|||
````bash |
|||
dotnet tool install Volo.Abp.Cli -g --version 8.0.0-rc.1 |
|||
```` |
|||
|
|||
Then, you can use the `abp update` command to update all the ABP related NuGet and NPM packages in your solution: |
|||
|
|||
```bash |
|||
abp update --version 8.0.0-rc.1 |
|||
``` |
|||
|
|||
After that, you need to check the migration guide documents, listed below: |
|||
|
|||
* [ABP Framework 7.x to 8.0 Migration Guide](https://docs.abp.io/en/abp/8.0/Migration-Guides/Abp-8_0) |
|||
* [ABP Commercial 7.x to 8.0 Migration Guide](https://docs.abp.io/en/commercial/8.0/migration-guides/v8_0) |
|||
|
|||
> Check these documents carefully and make the related changes in your solution to prevent errors. |
|||
|
|||
## Final Words |
|||
|
|||
That's it! These were all the related steps that need to be taken to upgrade your application to .NET 8 and ABP 8.0. Now, you can enjoy the .NET 8 & ABP 8.0 and benefit from the performance improvements and new features. |
|||
|
|||
Happy Coding 🤗 |
|||
@ -0,0 +1,81 @@ |
|||
# Dynamic Claims |
|||
|
|||
When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. For most of the claims, that may not be a problem since claims are not frequently changing values. However, some claims may be changed and these changes should be reflected to the current session immediately. For example, we can revoke a role from a user and that should be immediately effective, otherwise user will continue to use that role's permissions until re-login to the application. |
|||
|
|||
ABP's dynamic claims feature is used to automatically and dynamically override the configured claim values in the client's authentication token/cookie by the latest values of these claims. |
|||
|
|||
## How to Use |
|||
|
|||
This feature is disabled by default. You should enable it for your application and use the Dynamic Claims middleware. |
|||
|
|||
> **Beginning from the v8.0, all the [startup templates](Startup-Templates/Index.md) are pre-configured and the dynamic claims feature is enabled by default. So, if you have created a solution with v8.0 and above, you don't need to make any configuration. Follow the instructions only if you've upgraded from a version lower than 8.0.** |
|||
|
|||
### Enabling the Dynamic Claims |
|||
|
|||
You can enable it by the following code: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options => |
|||
{ |
|||
options.IsDynamicClaimsEnabled = true; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
This is typically done on the authentication server. In a monolith application, you will typically have a single application, so you can configure it. If you are using the tiered solution structure (where the UI part is hosted in a separate application) you will need to also set the `RemoteRefreshUrl` to the Authentication Server's URL in the UI application. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options => |
|||
{ |
|||
options.IsDynamicClaimsEnabled = true; |
|||
options.RemoteRefreshUrl = configuration["AuthServerUrl"] + options.RemoteRefreshUrl; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
### The Dynamic Claims Middleware |
|||
|
|||
Add the `DynamicClaims` middleware to all the applications that performs authentication (including the authentication server): |
|||
|
|||
````csharp |
|||
public override void OnApplicationInitialization( |
|||
ApplicationInitializationContext context) |
|||
{ |
|||
//... |
|||
app.UseDynamicClaims(); // Add this line before UseAuthorization. |
|||
app.UseAuthorization(); |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
## How It Works |
|||
|
|||
The `DynamicClaims` middleware will use `IAbpClaimsPrincipalFactory` to dynamically generate claims for the current user(`HttpContext.User`) in each request. |
|||
|
|||
There are two pre-built implementations of `IAbpDynamicClaimsPrincipalContributor` for different scenarios: |
|||
|
|||
* `IdentityDynamicClaimsPrincipalContributor`: Provided by the [Identity module](Modules/Identity.md) and generates and overrides the actual dynamic claims, and writes to the distributed cache. Typically works in the authentication server in a distributed system. |
|||
* `RemoteDynamicClaimsPrincipalContributor`: For distributed scenarios, this implementation works in the UI application. It tries to get dynamic claim values in the distributed cache. If not found in the distributed cache, it makes an HTTP call to the authentication server and requests filling it by the authentication server. `AbpClaimsPrincipalFactoryOptions.RemoteRefreshUrl` should be properly configure to make it running. |
|||
|
|||
### IAbpDynamicClaimsPrincipalContributor |
|||
|
|||
If you want to add your own dynamic claims contributor, you can create a class that implement the `IAbpDynamicClaimsPrincipalContributor` interface (and register it to the [dependency injection](Dependency-Injection.md) system. ABP Framework will call the `ContributeAsync` method to get the claims. It better to use a kind of cache to improve the performance since that is a frequently executed method (in every HTTP request). |
|||
|
|||
## AbpClaimsPrincipalFactoryOptions |
|||
|
|||
`AbpClaimsPrincipalFactoryOptions` is the main options class to configure the behavior of the dynamic claims system. It has the following properties: |
|||
|
|||
* `IsDynamicClaimsEnabled`: Enable or disable the dynamic claims feature. |
|||
* `RemoteRefreshUrl`: The `url ` of the Auth Server to refresh the cache. It will be used by the `RemoteDynamicClaimsPrincipalContributor`. The default value is `/api/account/dynamic-claims/refresh ` and you should provide the full URL in the authentication server, like `http://my-account-server/api/account/dynamic-claims/refresh `. |
|||
* `DynamicClaims`: A list of dynamic claim types. Only the claims in that list will be overridden by the dynamic claims system. |
|||
* `ClaimsMap`: A dictionary to map the claim types. This is used when the claim types are different between the Auth Server and the client. Already set up for common claim types by default. |
|||
|
|||
## See Also |
|||
|
|||
* [Authorization](Authorization.md) |
|||
* [Claims-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims) |
|||
* [Mapping, customizing, and transforming claims in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims) |
|||
@ -1,77 +1,288 @@ |
|||
# ABP Version 8.0 Migration Guide |
|||
|
|||
This document is a guide for upgrading ABP v7.4.x solutions to ABP v8.0.x. |
|||
This document is a guide for upgrading ABP v7.x solutions to ABP v8.0. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. |
|||
|
|||
> ABP Framework upgraded to .NET 8.0, so you need to move your solutions to .NET 8.0 if you want to use the ABP 8.0. You can check the [Migrate from ASP.NET Core 7.0 to 8.0](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80) documentation. |
|||
|
|||
## Injected the `IDistributedEventBus` Dependency into the `IdentityUserManager` |
|||
|
|||
In this version, `IDistributedEventBus` service has been injected to the `IdentityUserManager` service, to publish a distributed event when the email or username is changed for a user, this was needed because sometimes there may be scenarios where the old email/username is needed for the synchronization purposes. |
|||
|
|||
Therefore, you might need to update the `IdentityUserManager`'s constructor if you have overridden the class and are using it. |
|||
|
|||
> See the issue for more information: https://github.com/abpframework/abp/pull/17990 |
|||
|
|||
## Updated Method Signatures in the Bundling System |
|||
|
|||
In this version, ABP Framework introduced the CDN support for bundling. During the development, we have made some improvements on the bundling system and changed some method signatures. |
|||
|
|||
See https://github.com/abpframework/abp/issues/17864 for more information. |
|||
|
|||
## Replaced `IdentityUserLookupAppService` with the `IIdentityUserIntegrationService` |
|||
|
|||
[Integration Services](../Integration-Services.md) are built for module-to-module (or microservice-to-microservice) communication rather than consumed from a UI or a client application as [Application Services](../Application-Services.md) are intended to do. |
|||
|
|||
In that regard, we are discarding the `IIdentityUserLookupAppService` in the Identity Module and moving its functionality to the `IIdentityUserIntegrationService`. Therefore, if you have used that application service directly, use the integration service (`IIdentityUserIntegrationService`) instead. `IIdentityUserLookupAppService` will be removed in thes next versions, so you may need to create a similar service in your application. |
|||
|
|||
> Notice that integration services have no authorization and are not exposed as HTTP API by default. |
|||
Also, if you have overridden the `IdentityUserLookupAppService` and `IdentityUserIntegrationService` classes in your application, you should update these classes' constructors as follows: |
|||
|
|||
*IdentityUserLookupAppService.cs* |
|||
```csharp |
|||
public IdentityUserLookupAppService(IIdentityUserIntegrationService identityUserIntegrationService) |
|||
{ |
|||
IdentityUserIntegrationService = identityUserIntegrationService; |
|||
} |
|||
``` |
|||
|
|||
*IdentityUserIntegrationService.cs* |
|||
|
|||
```diff |
|||
public IdentityUserIntegrationService( |
|||
IUserRoleFinder userRoleFinder, |
|||
+ IdentityUserRepositoryExternalUserLookupServiceProvider userLookupServiceProvider) |
|||
{ |
|||
UserRoleFinder = userRoleFinder; |
|||
+ UserLookupServiceProvider = userLookupServiceProvider; |
|||
} |
|||
``` |
|||
|
|||
## MongoDB Event Bus Enhancements |
|||
|
|||
In this version, we have made some enhancements in the transactional inbox/outbox pattern implementation and defined two new methods: `ConfigureEventInbox` and `ConfigureEventOutbox` for MongoDB Event Box collections. |
|||
|
|||
If you call one of these methods in your DbContext class, then this introduces a breaking-change because if you do it, MongoDB collection names will be changed. Therefore, it should be carefully done since existing (non-processed) event records are not automatically moved to new collection and they will be lost. Existing applications with event records should rename the collection manually while deploying their solutions. |
|||
|
|||
See https://github.com/abpframework/abp/pull/17723 for more information. Also, check the documentation for the related configurations: [Distributed Event Bus](../Distributed-Event-Bus.md) |
|||
|
|||
## Moved the CMS Kit Pages Feature's Routing to a `DynamicRouteValueTransformer` |
|||
|
|||
In this version, we have made some improvements in the [CMS Kit's Pages Feature](../Modules/Cms-Kit/Pages.md), such as moving the routing logic to a `DynamicRouteValueTransformer` and etc... |
|||
|
|||
These enhancements led to some breaking changes as listed below that should be taken care of: |
|||
|
|||
* Page routing has been moved to **DynamicRouteValueTransformer**. If you use `{**slug}` pattern in your routing, it might conflict with new CMS Kit routing. |
|||
* `PageConsts.UrlPrefix` has been removed, instead, the default prefix is *pages* for now. Still `/pages/{slug}` route works for backward compatibility alongside with `/{slug}` route. |
|||
|
|||
* **Endpoints changed:** |
|||
* `api/cms-kit-public/pages/{slug}` endpoint is changed to `api/cms-kit-public/pages/by-slug?slug={slug}`. Now multiple level of page URLs can be used and `/` characters will be transferred as URL Encoded in querysting to the HTTP API. |
|||
* `api/cms-kit-public/pages` changed to `api/cms-kit-public/pages/home` |
|||
|
|||
>_CmsKit Client Proxies are updated. If you don't send a **custom request** to this endpoint, **you don't need to take an action**_ |
|||
|
|||
## Added Integration Postfix for Auto Controllers |
|||
|
|||
With this version on, the `Integration` suffix from controller names while generating [auto controllers](../API/Auto-API-Controllers.md) are not going to be removed, to differ the integration services from application services in the OpenAPI specification: |
|||
|
|||
 |
|||
|
|||
> This should not affect most of the applications since you normally do not depend on the controller names in the client side. |
|||
|
|||
See https://github.com/abpframework/abp/issues/17625 for more information (how to preserve the existing behaviour, etc...). |
|||
|
|||
## Revised the reCaptcha Generator for CMS Kit's Comment Feature |
|||
|
|||
In this version, we have made improvements on the [CMS Kit's Comment Feature](../Modules/Cms-Kit/Comments.md) and revised the reCaptcha generation process, and made a performance improvement. |
|||
|
|||
This introduced some breaking changes that you should aware of: |
|||
|
|||
* Lifetime of the `SimpleMathsCaptchaGenerator` changed from singleton to transient, |
|||
* Changed method signatures for `SimpleMathsCaptchaGenerator` class. (all of its methods are now async) |
|||
|
|||
If you haven't override the comment view component, then you don't need to make any changes, however if you have overriden the component and used the `SimpleMathsCaptchaGenerator` class, then you should make the required changes as described. |
|||
|
|||
## Disabled Logging for `HEAD` HTTP Methods |
|||
|
|||
HTTP GET requests should not make any change in the database normally and audit log system of ABP Framework doesn't save audit log objects for GET requests by default. You can configure the `AbpAuditingOptions` and set the `IsEnabledForGetRequests` to **true** if you want to record _GET_ requests as described in [the documentation](../Audit-Logging.md). |
|||
|
|||
Prior to this version, only the _GET_ requests were not saved as audit logs. From this version on, also the _HEAD_ requests will not be saved as audit logs, if the `IsEnabledForGetRequests` explicitly set as **true**. |
|||
|
|||
You don't need to make any changes related to that, however it's important to know this change. |
|||
|
|||
## Obsolete the `AbpAspNetCoreIntegratedTestBase` Class |
|||
|
|||
In this version, `AbpAspNetCoreAsyncIntegratedTestBase` class has been set as `Obsolete` and it's recommended to use `AbpWebApplicationFactoryIntegratedTest` instead. |
|||
|
|||
## Use NoTracking for Readonly Repositories for EF Core |
|||
|
|||
In this version, ABP Framework provides read-only [repository](Repositories.md) interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services. |
|||
|
|||
Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. |
|||
|
|||
> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface. |
|||
|
|||
> See the issue for more information: https://github.com/abpframework/abp/pull/17421 |
|||
|
|||
## Use `IAbpDaprClientFactory` to Obtain `DaprClient` |
|||
|
|||
From this version on, instead of injecting the `DaprClient` directly, using the `IAbpDaprClientFactory.CreateAsync` method to create `DaprClient` or `HttpClient` objects to perform operations on Dapr is recommended. |
|||
|
|||
The documentation is already updated according to this suggestion and can be found at https://docs.abp.io/en/abp/8.0/Dapr/Index. So, if you want to learn more you can check the documentation or see the PR: https://github.com/abpframework/abp/pull/18117. |
|||
|
|||
## Angular UI |
|||
|
|||
# Guards |
|||
|
|||
From Angular Documentation; |
|||
|
|||
> Class-based **`Route`** guards are deprecated in favor of functional guards. |
|||
|
|||
- Angular has been using functional guards since version 14. According to this situation we have moved our guards to functional guards. |
|||
|
|||
We have modified our modules to adaptate functional guards. |
|||
|
|||
```diff |
|||
- import {AuthGuard, PermissionGuard} from '@abp/ng.core'; |
|||
+ import {authGuard, permissionGuard} from '@abp/ng.core'; |
|||
|
|||
- canActivate: mapToCanActivate([AuthGuard, PermissionGuard]) |
|||
+ canActivate: [authGuard, permissionGuard] |
|||
``` |
|||
|
|||
You can still use class based guards but we recommend it to use functional guards like us :) |
|||
|
|||
## Upgraded NuGet Dependencies |
|||
|
|||
The following NuGet libraries have been upgraded: |
|||
You can see the following list of NuGet libraries that have been upgraded with .NET 8.0 upgrade, if you are using one of these packages explicitly, you may consider upgrading them in your solution: |
|||
|
|||
| Package | Old Version | New Version | |
|||
| ------------------- | ----------- | ----------- | |
|||
| All Microsoft packages | 7.x | 8.x | |
|||
| Microsoft.CodeAnalysis | 4.2.0 | 4.5.0 | |
|||
| NUglify | 1.20.0 | 1.21.0 | |
|||
| Polly | 7.2.3 | 8.0.0 | |
|||
| aliyun-net-sdk-sts | 3.1.0 | 3.1.1 | |
|||
| Autofac | 7.0.0 | 7.1.0 | |
|||
| Autofac.Extras.DynamicProxy | 6.0.1 | 7.1.0 | |
|||
| AutoMapper | 12.0.0 | 12.0.1 | |
|||
| AsyncKeyedLock | 6.2.1 | 6.2.2 | |
|||
| AWSSDK.S3 | 3.7.9.2 | 3.7.205.9 | |
|||
| AWSSDK.SecurityToken | 3.7.1.151 | 3.7.202.4 | |
|||
| Azure.Storage.Blobs | 12.15.0 | 12.18.0 | |
|||
| ConfigureAwait.Fody | 3.3.1 | 3.3.2 | |
|||
| Confluent.Kafka | 1.8.2 | 2.2.0 | |
|||
| Dapper | 2.0.123 | 2.1.4 | |
|||
| Dapr.Client | 1.11.0 | 1.9.0 | |
|||
| DistributedLock.Core | 1.0.4 | 1.0.5 | |
|||
| DistributedLock.Redis | 1.0.1 | 1.0.2 | |
|||
| EphemeralMongo.Core | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.linux-x64 | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.osx-x64 | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.win-x64 | 1.1.0 | 1.1.3 | |
|||
| FluentValidation | 11.0.1 | 11.7.1 | |
|||
| Fody | 6.6.1 | 6.8.0 | |
|||
| Hangfire.AspNetCore | 1.8.2 | 1.8.5 | |
|||
| Hangfire.SqlServer | 1.8.2 | 1.8.5 | |
|||
| HtmlSanitizer | 5.0.331 | 8.0.723 | |
|||
| IdentityModel | 6.0.0 | 6.2.0 | |
|||
| IdentityServer4.AspNetIdentity | 4.1.1 | 4.1.2 | |
|||
| JetBrains.Annotations | 2022.1.0 | 2023.2.0 | |
|||
| LibGit2Sharp | 0.26.2 | 0.27.2 | |
|||
| Magick.NET-Q16-AnyCPU | 13.2.0 | 13.3.0 | |
|||
| MailKit | 3.2.0 | 4.2.0 | |
|||
| Markdig.Signed | 0.26.0 | 0.33.0 | |
|||
| Microsoft.AspNetCore.Mvc.Versioning | 5.0.0 | 5.1.0 | |
|||
| Microsoft.AspNetCore.Razor.Language | 6.0.8 | 6.0.22 | |
|||
| Microsoft.NET.Test.Sdk | 17.2.0 | 17.7.2 | |
|||
| Minio | 4.0.6 | 6.0.0 | |
|||
| MongoDB.Driver | 2.19.1 | 2.21.0 | |
|||
| NEST | 7.14.1 | 7.17.5 | |
|||
| Newtonsoft.Json | 13.0.1 | 13.0.3 | |
|||
| NSubstitute | 4.3.0 | 5.1.0 | |
|||
| NSubstitute.Analyzers.CSharp | 1.0.15 | 1.0.16 | |
|||
| NuGet.Versioning | 5.11.0 | 6.7.0 | |
|||
| Octokit | 0.50.0 | 8.0.1 | |
|||
| Quartz | 3.4.0 | 3.7.0 | |
|||
| Quartz.Extensions.DependencyInjection | 3.4.0 | 3.7.0 | |
|||
| Quartz.Plugins.TimeZoneConverter | 3.4.0 | 3.7.0 | |
|||
| RabbitMQ.Client | 6.3.0 | 6.5.0 | |
|||
| Rebus | 6.6.5 | 7.2.1 | |
|||
| Rebus.ServiceProvider | 7.0.0 | 9.1.0 | |
|||
| Scriban | 5.4.4 | 5.9.0 | |
|||
| Serilog.AspNetCore | 5.0.0 | 7.0.0 | |
|||
| Serilog.Extensions.Hosting | 3.1.0 | 7.0.0 | |
|||
| Serilog.Extensions.Logging | 3.1.0 | 7.0.0 | |
|||
| Serilog.Sinks.Async | 1.4.0 | 1.5.0 | |
|||
| SharpZipLib | 1.3.3 | 1.4.2 | |
|||
| Shouldly | 4.1.0 | 4.2.1 | |
|||
| SixLabors.ImageSharp | 1.0.4 | 3.0.2 | |
|||
| Slugify.Core | 3.0.0 | 4.0.1 | |
|||
| Spectre.Console | 0.46.1-preview.0.7 | 0.47.0 | |
|||
| Swashbuckle.AspNetCore | 6.2.1 | 6.5.0 | |
|||
| System.Linq.Dynamic.Core | 1.3.3 | 1.3.5 | |
|||
| TimeZoneConverter | 5.0.0 | 6.1.0 | |
|||
| xunit | 2.4.1 | 2.5.1 | |
|||
| xunit.extensibility.execution | 2.4.1 | 2.5.1 | |
|||
| xunit.runner.visualstudio | 2.4.5 | 2.5.1 | |
|||
| aliyun-net-sdk-sts | 3.1.0 | 3.1.2 | |
|||
| AsyncKeyedLock | 6.2.1 | 6.2.2 | |
|||
| Autofac | 7.0.0 | 7.1.0 | |
|||
| Autofac.Extras.DynamicProxy | 6.0.1 | 7.1.0 | |
|||
| AutoMapper | 12.0.0 | 12.0.1 | |
|||
| AWSSDK.S3 | 3.7.9.2 | 3.7.300.2 | |
|||
| AWSSDK.SecurityToken | 3.7.1.151 | 3.7.300.2 | |
|||
| Azure.Messaging.ServiceBus | 7.8.1 | 7.17.0 | |
|||
| Azure.Storage.Blobs | 12.15.0 | 12.19.1 | |
|||
| Blazorise | 1.3.1 | 1.3.2 | |
|||
| Blazorise.Bootstrap5 | 1.3.1 | 1.3.2 | |
|||
| Blazorise.Icons.FontAwesome | 1.3.1 | 1.3.2 | |
|||
| Blazorise.Components | 1.3.1 | 1.3.2 | |
|||
| Blazorise.DataGrid | 1.3.1 | 1.3.2 | |
|||
| Blazorise.Snackbar | 1.3.1 | 1.3.2 | |
|||
| Confluent.Kafka | 1.8.2 | 2.3.0 | |
|||
| Dapper | 2.0.123 | 2.1.21 | |
|||
| Dapr.AspNetCore | 1.9.0 | 1.12.0 | |
|||
| Dapr.Client | 1.9.0 | 1.12.0 | |
|||
| Devart.Data.Oracle.EFCore | 10.1.134.7 | 10.1.151.7 | |
|||
| DistributedLock.Core | 1.0.4 | 1.0.5 | |
|||
| DistributedLock.Redis | 1.0.1 | 1.0.2 | |
|||
| EphemeralMongo.Core | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.linux-x64 | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.osx-x64 | 1.1.0 | 1.1.3 | |
|||
| EphemeralMongo6.runtime.win-x64 | 1.1.0 | 1.1.3 | |
|||
| FluentValidation | 11.0.1 | 11.8.0 | |
|||
| Hangfire.AspNetCore | 1.8.2 | 1.8.6 | |
|||
| Hangfire.SqlServer | 1.8.2 | 1.8.6 | |
|||
| HtmlSanitizer | 5.0.331 | 8.0.746 | |
|||
| HtmlAgilityPack | 1.11.42 | 1.11.54 | |
|||
| IdentityModel | 6.0.0 | 6.2.0 | |
|||
| IdentityServer4.AspNetIdentity | 4.1.1 | 4.1.2 | |
|||
| JetBrains.Annotations | 2022.1.0 | 2023.3.0 | |
|||
| LibGit2Sharp | 0.26.2 | 0.28.0 | |
|||
| Magick.NET-Q16-AnyCPU | 13.2.0 | 13.4.0 | |
|||
| MailKit | 3.2.0 | 4.3.0 | |
|||
| Markdig.Signed | 0.26.0 | 0.33.0 | |
|||
| Microsoft.AspNetCore.Authentication.JwtBearer | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Authentication.OpenIdConnect | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Authorization | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.Authorization | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.Web | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.WebAssembly | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.WebAssembly.Authentication | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.WebAssembly.DevServer | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Components.WebAssembly.Server | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.DataProtection.StackExchangeRedis | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Mvc.NewtonsoftJson | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.Mvc.Versioning | 5.0.0 | 5.1.0 | |
|||
| Microsoft.AspNetCore.Razor.Language | 6.0.8 | 6.0.25 | |
|||
| Microsoft.AspNetCore.TestHost | 7.0.10 | 8.0.0 | |
|||
| Microsoft.AspNetCore.WebUtilities | 2.2.0 | 8.0.0 | |
|||
| Microsoft.Bcl.AsyncInterfaces | 7.0.0 | 8.0.0 | |
|||
| Microsoft.CodeAnalysis.CSharp | 4.2.0 | 4.5.0 | |
|||
| Microsoft.Data.Sqlite | 7.0.0 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore | 7.0.10 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.Design | 7.0.0 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.InMemory | 7.0.10 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.Proxies | 7.0.10 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.Relational | 7.0.10 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.Sqlite | 7.0.10 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.SqlServer | 7.0.0 | 8.0.0 | |
|||
| Microsoft.EntityFrameworkCore.Tools | 7.0.1 | 8.0.0 | |
|||
| Microsoft.Extensions.Caching.Memory | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Caching.StackExchangeRedis | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Configuration.Binder | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Configuration.CommandLine | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Configuration.EnvironmentVariables | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Configuration.UserSecrets | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.DependencyInjection | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.DependencyInjection.Abstractions | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.FileProviders.Composite | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.FileProviders.Embedded | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.FileProviders.Physical | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.FileSystemGlobbing | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Hosting | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Hosting.Abstractions | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Http | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Http.Polly| 7.0.10 | 8.0.0 | |
|||
| Microsoft.Extensions.Identity.Core | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Localization | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Logging | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Logging.Console | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Options | 7.0.0 | 8.0.0 | |
|||
| Microsoft.Extensions.Options.ConfigurationExtensions | 7.0.0 | 8.0.0 | |
|||
| Microsoft.NET.Test.Sdk | 17.2.0 | 17.8.0 | |
|||
| Microsoft.VisualStudio.Web.CodeGeneration.Design | 7.0.0 | 8.0.0 | |
|||
| Minio | 4.0.6 | 6.0.1 | |
|||
| MongoDB.Driver | 2.19.1 | 2.22.0 | |
|||
| NEST | 7.14.1 | 7.17.5 | |
|||
| Newtonsoft.Json | 13.0.1 | 13.0.3 | |
|||
| NSubstitute | 4.3.0 | 5.1.0 | |
|||
| NuGet.Versioning | 5.11.0 | 6.7.0 | |
|||
| NUglify | 1.20.0 | 1.21.0 | |
|||
| Npgsql.EntityFrameworkCore.PostgreSQL | 7.0.0 | 8.0.0-rc.2 | |
|||
| NSubstitute.Analyzers.CSharp | 1.0.15 | 1.0.16 | |
|||
| Octokit | 0.50.0 | 9.0.0 | |
|||
| OpenIddict.Abstractions | 4.8.0 | 4.10.0 | |
|||
| OpenIddict.Core | 4.8.0 | 4.10.0 | |
|||
| OpenIddict.Server.AspNetCore | 4.8.0 | 4.10.0 | |
|||
| OpenIddict.Validation.AspNetCore | 4.8.0 | 4.10.0 | |
|||
| OpenIddict.Validation.ServerIntegration | 4.8.0 | 4.10.0 | |
|||
| Oracle.EntityFrameworkCore | 7.21.8 | 7.21.12 | |
|||
| Polly | 7.2.3 | 8.2.0 | |
|||
| Pomelo.EntityFrameworkCore.MySql | 7.0.0 | 8.0.0-beta.1 | |
|||
| Quartz | 3.4.0 | 3.7.0 | |
|||
| Quartz.Extensions.DependencyInjection | 3.4.0 | 3.7.0 | |
|||
| Quartz.Plugins.TimeZoneConverter | 3.4.0 | 3.7.0 | |
|||
| Quartz.Serialization.Json | 3.3.3 | 3.7.0 | |
|||
| RabbitMQ.Client | 6.3.0 | 6.6.0 | |
|||
| Rebus | 6.6.5 | 7.2.1 | |
|||
| Rebus.ServiceProvider | 7.0.0 | 9.1.0 | |
|||
| Scriban | 5.4.4 | 5.9.0 | |
|||
| Serilog | 2.11.0 | 3.1.1 | |
|||
| Serilog.AspNetCore | 5.0.0 | 8.0.0 | |
|||
| Serilog.Extensions.Hosting | 3.1.0 | 8.0.0 | |
|||
| Serilog.Extensions.Logging | 3.1.0 | 8.0.0 | |
|||
| Serilog.Sinks.Async | 1.4.0 | 1.5.0 | |
|||
| Serilog.Sinks.Console | 3.1.1 | 5.0.0 | |
|||
| Serilog.Sinks.File | 4.1.0 | 5.0.0 | |
|||
| SharpZipLib | 1.3.3 | 1.4.2 | |
|||
| Shouldly | 4.0.3 | 4.2.1 | |
|||
| SixLabors.ImageSharp.Drawing | 2.0.0 | 2.0.1 | |
|||
| Slugify.Core | 3.0.0 | 4.0.1 | |
|||
| StackExchange.Redis | 2.6.122 | 2.7.4 | |
|||
| Swashbuckle.AspNetCore | 6.2.1 | 6.5.0 | |
|||
| System.Collections.Immutable | 7.0.0 | 8.0.0 | |
|||
| System.Linq.Dynamic.Core | 1.3.3 | 1.3.5 | |
|||
| System.Security.Permissions | 7.0.0 | 8.0.0 | |
|||
| System.Text.Encoding.CodePages | 7.0.0 | 8.0.0 | |
|||
| System.Text.Encodings.Web | 7.0.0 | 8.0.0 | |
|||
| System.Text.Json | 7.0.0 | 8.0.0 | |
|||
| TimeZoneConverter | 5.0.0 | 6.1.0 | |
|||
| xunit | 2.4.1 | 2.6.1 | |
|||
| xunit.extensibility.execution | 2.4.1 | 2.6.1 | |
|||
| xunit.runner.visualstudio | 2.4.5 | 2.5.3 | |
|||
|
|||
|
After Width: | Height: | Size: 130 KiB |
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
|||
|
|||
public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
if (identity == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var userId = identity.FindUserId(); |
|||
if (userId == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var dynamicClaimsCache = context.GetRequiredService<RemoteDynamicClaimsPrincipalContributorCache>(); |
|||
AbpDynamicClaimCacheItem dynamicClaims; |
|||
try |
|||
{ |
|||
dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// In case if failed refresh remote dynamic cache, We force to clear the claims principal.
|
|||
context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
var logger = context.GetRequiredService<ILogger<RemoteDynamicClaimsPrincipalContributor>>(); |
|||
logger.LogWarning(e, $"Failed to refresh remote dynamic claims cache for user: {userId.Value}"); |
|||
return; |
|||
} |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.Authentication; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
|||
|
|||
public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency |
|||
{ |
|||
public const string HttpClientName = nameof(RemoteDynamicClaimsPrincipalContributorCache); |
|||
|
|||
public ILogger<RemoteDynamicClaimsPrincipalContributorCache> Logger { get; set; } |
|||
protected IDistributedCache<AbpDynamicClaimCacheItem> Cache { get; } |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } |
|||
|
|||
public RemoteDynamicClaimsPrincipalContributorCache( |
|||
IDistributedCache<AbpDynamicClaimCacheItem> cache, |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
|||
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator) |
|||
{ |
|||
Cache = cache; |
|||
HttpClientFactory = httpClientFactory; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
HttpClientAuthenticator = httpClientAuthenticator; |
|||
|
|||
Logger = NullLogger<RemoteDynamicClaimsPrincipalContributorCache>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<AbpDynamicClaimCacheItem> GetAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); |
|||
var dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
if (dynamicClaims != null && !dynamicClaims.Claims.IsNullOrEmpty()) |
|||
{ |
|||
return dynamicClaims; |
|||
} |
|||
|
|||
Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); |
|||
try |
|||
{ |
|||
var client = HttpClientFactory.CreateClient(HttpClientName); |
|||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, AbpClaimsPrincipalFactoryOptions.Value.RemoteRefreshUrl); |
|||
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration("/"), string.Empty)); |
|||
var response = await client.SendAsync(requestMessage); |
|||
response.EnsureSuccessStatusCode(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); |
|||
throw; |
|||
} |
|||
|
|||
dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
if (dynamicClaims == null || dynamicClaims.Claims.IsNullOrEmpty()) |
|||
{ |
|||
throw new AbpException($"Failed to refresh remote claims for user: {userId}"); |
|||
} |
|||
|
|||
return dynamicClaims!; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Security.Claims; |
|||
|
|||
public class AbpDynamicClaimsMiddleware : IMiddleware, ITransientDependency |
|||
{ |
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
if (context.User.Identity?.IsAuthenticated == true) |
|||
{ |
|||
if (context.RequestServices.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value.IsDynamicClaimsEnabled) |
|||
{ |
|||
var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
context.User = await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); |
|||
} |
|||
} |
|||
|
|||
await next(context); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,28 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
|||
<Nullable>enable</Nullable> |
|||
<WarningsAsErrors>Nullable</WarningsAsErrors> |
|||
<PackageId>Volo.Abp.Imaging.SkiaSharp</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="SkiaSharp" /> |
|||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> |
|||
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn(typeof(AbpImagingAbstractionsModule))] |
|||
public class AbpImagingSkiaSharpModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using SkiaSharp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITransientDependency |
|||
{ |
|||
protected SkiaSharpResizerOptions Options { get; } |
|||
|
|||
public SkiaSharpImageResizerContributor(IOptions<SkiaSharpResizerOptions> options) |
|||
{ |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public virtual async Task<ImageResizeResult<byte[]>> TryResizeAsync(byte[] bytes, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) |
|||
{ |
|||
return new ImageResizeResult<byte[]>(bytes, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
using (var memoryStream = new MemoryStream(bytes)) |
|||
{ |
|||
var result = await TryResizeAsync(memoryStream, resizeArgs, mimeType, cancellationToken); |
|||
|
|||
if (result.State != ImageProcessState.Done) |
|||
{ |
|||
return new ImageResizeResult<byte[]>(bytes, result.State); |
|||
} |
|||
|
|||
var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); |
|||
|
|||
result.Result.Dispose(); |
|||
|
|||
return new ImageResizeResult<byte[]>(newBytes, result.State); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task<ImageResizeResult<Stream>> TryResizeAsync(Stream stream, ImageResizeArgs resizeArgs, string? mimeType = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
if (!mimeType.IsNullOrWhiteSpace() && !CanResize(mimeType)) |
|||
{ |
|||
return new ImageResizeResult<Stream>(stream, ImageProcessState.Unsupported); |
|||
} |
|||
|
|||
var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream); |
|||
|
|||
using (var original = SKBitmap.Decode(memoryBitmapStream)) |
|||
{ |
|||
using (var resized = original.Resize(new SKImageInfo(resizeArgs.Width, resizeArgs.Height), Options.SKFilterQuality)) |
|||
{ |
|||
using (var image = SKImage.FromBitmap(resized)) |
|||
{ |
|||
using (var codec = SKCodec.Create(memorySkCodecStream)) |
|||
{ |
|||
var memoryStream = new MemoryStream(); |
|||
image.Encode(codec.EncodedFormat, Options.Quality).SaveTo(memoryStream); |
|||
return new ImageResizeResult<Stream>(memoryStream, ImageProcessState.Done); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream) |
|||
{ |
|||
var streamPosition = stream.Position; |
|||
|
|||
var memoryBitmapStream = new MemoryStream(); |
|||
var memorySkCodecStream = new MemoryStream(); |
|||
|
|||
await stream.CopyToAsync(memoryBitmapStream); |
|||
stream.Position = streamPosition; |
|||
await stream.CopyToAsync(memorySkCodecStream); |
|||
stream.Position = streamPosition; |
|||
|
|||
memoryBitmapStream.Position = 0; |
|||
memorySkCodecStream.Position = 0; |
|||
|
|||
return (memoryBitmapStream, memorySkCodecStream); |
|||
} |
|||
|
|||
protected virtual bool CanResize(string? mimeType) |
|||
{ |
|||
return mimeType switch { |
|||
MimeTypes.Image.Jpeg => true, |
|||
MimeTypes.Image.Png => true, |
|||
MimeTypes.Image.Webp => true, |
|||
_ => false |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using SkiaSharp; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class SkiaSharpResizerOptions |
|||
{ |
|||
public SKFilterQuality SKFilterQuality { get; set; } |
|||
|
|||
public int Quality { get; set; } |
|||
|
|||
public SkiaSharpResizerOptions() |
|||
{ |
|||
SKFilterQuality = SKFilterQuality.None; |
|||
Quality = 75; |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
[Serializable] |
|||
public class AbpDynamicClaim |
|||
{ |
|||
public string Type { get; set; } |
|||
|
|||
public string? Value { get; set; } |
|||
|
|||
public AbpDynamicClaim(string type, string? value) |
|||
{ |
|||
Type = type; |
|||
Value = value; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
[Serializable] |
|||
public class AbpDynamicClaimCacheItem |
|||
{ |
|||
public List<AbpDynamicClaim> Claims { get; set; } |
|||
|
|||
public AbpDynamicClaimCacheItem() |
|||
{ |
|||
Claims = new List<AbpDynamicClaim>(); |
|||
} |
|||
|
|||
public AbpDynamicClaimCacheItem(List<AbpDynamicClaim> claims) |
|||
{ |
|||
Claims = claims; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(Guid userId, Guid? tenantId) |
|||
{ |
|||
return $"{tenantId}-{userId}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public abstract Task ContributeAsync(AbpClaimsPrincipalContributorContext context); |
|||
|
|||
protected virtual async Task AddDynamicClaimsAsync(AbpClaimsPrincipalContributorContext context, ClaimsIdentity identity, List<AbpDynamicClaim> dynamicClaims) |
|||
{ |
|||
var options = context.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value; |
|||
foreach (var map in options.ClaimsMap) |
|||
{ |
|||
await MapClaimAsync(identity, dynamicClaims, map.Key, map.Value.ToArray()); |
|||
} |
|||
|
|||
foreach (var claimGroup in dynamicClaims.GroupBy(x => x.Type)) |
|||
{ |
|||
identity.RemoveAll(claimGroup.First().Type); |
|||
identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(claimGroup.First().Type, c.Value!))); |
|||
} |
|||
} |
|||
|
|||
protected virtual Task MapClaimAsync(ClaimsIdentity identity, List<AbpDynamicClaim> dynamicClaims, string targetClaimType, params string[] sourceClaimTypes) |
|||
{ |
|||
var claims = dynamicClaims.Where(c => sourceClaimTypes.Contains(c.Type)).ToList(); |
|||
if (claims.IsNullOrEmpty()) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
dynamicClaims.RemoveAll(claims); |
|||
identity.RemoveAll(targetClaimType); |
|||
identity.AddClaims(claims.Where(c => c.Value != null).Select(c => new Claim(targetClaimType, c.Value!))); |
|||
|
|||
return Task.CompletedTask;; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public interface IAbpDynamicClaimsPrincipalContributor |
|||
{ |
|||
Task ContributeAsync(AbpClaimsPrincipalContributorContext context); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
|||
<ProjectReference Include="..\Volo.Abp.Imaging.Abstractions.Tests\Volo.Abp.Imaging.Abstractions.Tests.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Imaging.SkiaSharp\Volo.Abp.Imaging.SkiaSharp.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\Imaging\Files\**" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpImagingSkiaSharpModule), |
|||
typeof(AbpTestBaseModule) |
|||
)] |
|||
public class AbpImagingSkiaSharpTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public abstract class AbpImagingSkiaSharpTestBase : AbpIntegratedTest<AbpImagingSkiaSharpTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Imaging; |
|||
|
|||
public class SkiaSharpImageResizerTests : AbpImagingSkiaSharpTestBase |
|||
{ |
|||
public IImageResizer ImageResizer { get; } |
|||
|
|||
public SkiaSharpImageResizerTests() |
|||
{ |
|||
ImageResizer = GetRequiredService<IImageResizer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Jpg() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Png() |
|||
{ |
|||
await using var pngImage = ImageFileHelper.GetPngTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(pngImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(pngImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Webp() |
|||
{ |
|||
await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); |
|||
var resizedImage = await ImageResizer.ResizeAsync(webpImage, new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage.ShouldNotBeNull(); |
|||
resizedImage.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage.Result.Length.ShouldBeLessThan(webpImage.Length); |
|||
|
|||
resizedImage.Result.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Resize_Stream_And_Byte_Array_The_Same() |
|||
{ |
|||
await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); |
|||
var resizedImage1 = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); |
|||
var resizedImage2 = await ImageResizer.ResizeAsync(await jpegImage.GetAllBytesAsync(), new ImageResizeArgs(100, 100)); |
|||
|
|||
resizedImage1.ShouldNotBeNull(); |
|||
resizedImage1.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage2.ShouldNotBeNull(); |
|||
resizedImage2.State.ShouldBe(ImageProcessState.Done); |
|||
resizedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); |
|||
|
|||
resizedImage1.Result.Length.ShouldBe(resizedImage2.Result.LongLength); |
|||
|
|||
resizedImage1.Result.Dispose(); |
|||
} |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; |
|||
private static string TestAuthenticationType => "Identity.Application"; |
|||
|
|||
public AbpClaimsPrincipalFactory_Test() |
|||
{ |
|||
_abpClaimsPrincipalFactory = GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
|
|||
} |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
services.AddTransient<TestAbpClaimsPrincipalContributor>(); |
|||
services.AddTransient<Test2AbpClaimsPrincipalContributor>(); |
|||
services.AddTransient<Test3AbpClaimsPrincipalContributor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateAsync(); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Create_With_Exists_ClaimsPrincipal() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(TestAuthenticationType, ClaimTypes.Name, ClaimTypes.Role)); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Name, "123")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Role, "admin")); |
|||
|
|||
await _abpClaimsPrincipalFactory.CreateAsync(claimsPrincipal); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public class AbpClaimsPrincipalFactory_Tests : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; |
|||
private static string TestAuthenticationType => "Identity.Application"; |
|||
|
|||
public AbpClaimsPrincipalFactory_Tests() |
|||
{ |
|||
_abpClaimsPrincipalFactory = GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
|
|||
} |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateAsync(); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Create_With_Exists_ClaimsPrincipal() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(TestAuthenticationType, ClaimTypes.Name, ClaimTypes.Role)); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Name, "123")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Role, "admin")); |
|||
|
|||
await _abpClaimsPrincipalFactory.CreateAsync(claimsPrincipal); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task DynamicCreateAsync() |
|||
{ |
|||
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task DynamicCreate_With_Exists_ClaimsPrincipal() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(TestAuthenticationType, ClaimTypes.Name, ClaimTypes.Role)); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Name, "123")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Role, "123")); |
|||
|
|||
await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Name && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io"); |
|||
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0"); |
|||
} |
|||
|
|||
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class TestAbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test4AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Name, "admin")); |
|||
claimsIdentity.RemoveAll(ClaimTypes.Role); |
|||
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin")); |
|||
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "manager")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
class TestAbpDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
Check.NotNull(identity, nameof(identity)); |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims.Claims); |
|||
} |
|||
} |
|||
|
|||
public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly TestAbpDynamicClaimsPrincipalContributor _dynamicClaimsPrincipalContributorBase = new TestAbpDynamicClaimsPrincipalContributor(); |
|||
|
|||
public readonly static AbpDynamicClaimCacheItem DynamicClaims = new AbpDynamicClaimCacheItem(); |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task AddDynamicClaimsAsync() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.UserName, "test-source-userName")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Name, "test-source-name")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.SurName, "test-source-surname")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Role, "test-source-role1")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Role, "test-source-role2")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Email, "test-source-email")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.EmailVerified, "test-source-emailVerified")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.PhoneNumber, "test-source-phoneNumber")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.PhoneNumberVerified, "test-source-phoneNumberVerified")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim("my-claim", "test-source-my-claim")); |
|||
|
|||
DynamicClaims.Claims.AddRange(new [] |
|||
{ |
|||
new AbpDynamicClaim("preferred_username", "test-preferred_username"), |
|||
new AbpDynamicClaim(ClaimTypes.GivenName, "test-given_name"), |
|||
new AbpDynamicClaim("family_name", "test-family_name"), |
|||
new AbpDynamicClaim("role", "test-role1"), |
|||
new AbpDynamicClaim("roles", "test-role2"), |
|||
new AbpDynamicClaim(ClaimTypes.Role, "test-role3"), |
|||
new AbpDynamicClaim("email", "test-email"), |
|||
new AbpDynamicClaim(AbpClaimTypes.EmailVerified, "test-email-verified"), |
|||
new AbpDynamicClaim(AbpClaimTypes.PhoneNumberVerified, null), |
|||
}); |
|||
|
|||
await _dynamicClaimsPrincipalContributorBase.ContributeAsync(new AbpClaimsPrincipalContributorContext(claimsPrincipal, GetRequiredService<IServiceProvider>())); |
|||
|
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.UserName && c.Value == "test-preferred_username"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.SurName && c.Value == "test-family_name"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Name && c.Value == "test-given_name"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role1"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role2"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role3"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Email && c.Value == "test-email"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.EmailVerified && c.Value == "test-email-verified"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.PhoneNumber && c.Value == "test-source-phoneNumber"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldNotContain(c => c.Type == AbpClaimTypes.PhoneNumberVerified); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == "my-claim" && c.Value == "test-source-my-claim"); |
|||
} |
|||
} |
|||
@ -1,6 +1,6 @@ |
|||
{ |
|||
"sdk": { |
|||
"version": "8.0.100-rc.2.23502.2", |
|||
"version": "8.0.100", |
|||
"rollForward": "latestFeature" |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
public interface IDynamicClaimsAppService : IApplicationService |
|||
{ |
|||
Task RefreshAsync(); |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Security.Claims; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[Authorize] |
|||
public class DynamicClaimsAppService : IdentityAppServiceBase, IDynamicClaimsAppService |
|||
{ |
|||
protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } |
|||
protected IAbpClaimsPrincipalFactory AbpClaimsPrincipalFactory { get; } |
|||
protected ICurrentPrincipalAccessor PrincipalAccessor { get; } |
|||
|
|||
public DynamicClaimsAppService( |
|||
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, |
|||
IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, |
|||
ICurrentPrincipalAccessor principalAccessor) |
|||
{ |
|||
IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; |
|||
AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory; |
|||
PrincipalAccessor = principalAccessor; |
|||
} |
|||
|
|||
public virtual async Task RefreshAsync() |
|||
{ |
|||
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(CurrentUser.GetId(), CurrentUser.TenantId); |
|||
await AbpClaimsPrincipalFactory.CreateDynamicAsync(PrincipalAccessor.Principal); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Account; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IDynamicClaimsAppService), typeof(DynamicClaimsClientProxy))] |
|||
public partial class DynamicClaimsClientProxy : ClientProxyBase<IDynamicClaimsAppService>, IDynamicClaimsAppService |
|||
{ |
|||
public virtual async Task RefreshAsync() |
|||
{ |
|||
await RequestAsync(nameof(RefreshAsync)); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
// This file is part of DynamicClaimsClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
public partial class DynamicClaimsClientProxy |
|||
{ |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(AccountRemoteServiceConsts.ModuleName)] |
|||
[ControllerName("DynamicClaims")] |
|||
[Route("/api/account/dynamic-claims")] |
|||
public class DynamicClaimsController : AbpControllerBase, IDynamicClaimsAppService |
|||
{ |
|||
protected IDynamicClaimsAppService DynamicClaimsAppService { get; } |
|||
|
|||
public DynamicClaimsController(IDynamicClaimsAppService dynamicClaimsAppService) |
|||
{ |
|||
DynamicClaimsAppService = dynamicClaimsAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("refresh")] |
|||
public virtual Task RefreshAsync() |
|||
{ |
|||
return DynamicClaimsAppService.RefreshAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
var userId = identity?.FindUserId(); |
|||
if (userId == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var dynamicClaimsCache = context.GetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>(); |
|||
AbpDynamicClaimCacheItem dynamicClaims; |
|||
try |
|||
{ |
|||
dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); |
|||
} |
|||
catch (EntityNotFoundException e) |
|||
{ |
|||
// In case if user not found, We force to clear the claims principal.
|
|||
context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
var logger = context.GetRequiredService<ILogger<IdentityDynamicClaimsPrincipalContributor>>(); |
|||
logger.LogWarning(e, $"User not found: {userId.Value}"); |
|||
return; |
|||
} |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependency |
|||
{ |
|||
public ILogger<IdentityDynamicClaimsPrincipalContributorCache> Logger { get; set; } |
|||
|
|||
protected IDistributedCache<AbpDynamicClaimCacheItem> Cache { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IdentityUserManager UserManager { get; } |
|||
protected IUserClaimsPrincipalFactory<IdentityUser> UserClaimsPrincipalFactory { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
protected IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> CacheOptions { get; } |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributorCache( |
|||
IDistributedCache<AbpDynamicClaimCacheItem> cache, |
|||
ICurrentTenant currentTenant, |
|||
IdentityUserManager userManager, |
|||
IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
|||
IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> cacheOptions) |
|||
{ |
|||
Cache = cache; |
|||
CurrentTenant = currentTenant; |
|||
UserManager = userManager; |
|||
UserClaimsPrincipalFactory = userClaimsPrincipalFactory; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
CacheOptions = cacheOptions; |
|||
|
|||
Logger = NullLogger<IdentityDynamicClaimsPrincipalContributorCache>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<AbpDynamicClaimCacheItem> GetAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); |
|||
|
|||
return await Cache.GetOrAddAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => |
|||
{ |
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
Logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); |
|||
|
|||
var user = await UserManager.GetByIdAsync(userId); |
|||
var principal = await UserClaimsPrincipalFactory.CreateAsync(user); |
|||
|
|||
var dynamicClaims = new AbpDynamicClaimCacheItem(); |
|||
foreach (var claimType in AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims) |
|||
{ |
|||
var claims = principal.Claims.Where(x => x.Type == claimType).ToList(); |
|||
if (claims.Any()) |
|||
{ |
|||
dynamicClaims.Claims.AddRange(claims.Select(claim => new AbpDynamicClaim(claimType, claim.Value))); |
|||
} |
|||
else |
|||
{ |
|||
dynamicClaims.Claims.Add(new AbpDynamicClaim(claimType, null)); |
|||
} |
|||
} |
|||
|
|||
return dynamicClaims; |
|||
} |
|||
}, () => new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration |
|||
}); |
|||
} |
|||
|
|||
public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); |
|||
await Cache.RemoveAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributorCacheOptions |
|||
{ |
|||
public TimeSpan CacheAbsoluteExpiration { get; set; } |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributorCacheOptions() |
|||
{ |
|||
CacheAbsoluteExpiration = TimeSpan.FromHours(1); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class UserEntityUpdatedEventHandler : |
|||
ILocalEventHandler<EntityUpdatedEventData<IdentityUser>>, |
|||
ILocalEventHandler<EntityDeletedEventData<IdentityUser>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IdentityDynamicClaimsPrincipalContributorCache _cache; |
|||
|
|||
public UserEntityUpdatedEventHandler(IdentityDynamicClaimsPrincipalContributorCache cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(EntityUpdatedEventData<IdentityUser> eventData) |
|||
{ |
|||
await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(EntityDeletedEventData<IdentityUser> eventData) |
|||
{ |
|||
await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); |
|||
} |
|||
|
|||
protected virtual async Task ClearAsync(Guid userId, Guid? tenantId) |
|||
{ |
|||
await _cache.ClearAsync(userId, tenantId); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue