Browse Source

Merge branch 'rel-9.2' into IgnoreEventSelectors

pull/22471/head
maliming 1 year ago
parent
commit
86c4185e81
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      Directory.Packages.props
  2. 4
      docs/en/docs-nav.json
  3. 6
      docs/en/framework/architecture/microservices/index.md
  4. 2
      docs/en/framework/infrastructure/settings.md
  5. 29
      docs/en/framework/infrastructure/timing.md
  6. BIN
      docs/en/images/account-pro-external-login-settings.png
  7. BIN
      docs/en/images/pen-test-alert-list-9.1.png
  8. 67
      docs/en/others/penetration-test-report.md
  9. 64
      docs/en/release-info/migration-guides/abp-9-2.md
  10. BIN
      docs/en/release-info/migration-guides/images/require-email-verification-for-register.png
  11. 1
      docs/en/release-info/migration-guides/index.md
  12. 13
      docs/en/release-info/release-notes.md
  13. 20
      docs/en/release-info/road-map.md
  14. 15
      docs/en/tutorials/microservice/part-06.md
  15. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor
  16. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor
  17. 15
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/UserExceptionInformer.cs
  18. 3
      framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpComponentBase.cs
  19. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  20. 20
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs
  21. 27
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  22. 4
      framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css
  23. 13
      framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs
  24. 44
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs
  25. 2
      framework/src/Volo.Abp.Swashbuckle/Volo.Abp.Swashbuckle.csproj
  26. 39
      framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/oauth2-redirect.html
  27. 58
      framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js
  28. 6
      modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.abppkg.analyze.json
  29. 50
      modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo.CmsKit.Admin.Application.abppkg.analyze.json
  30. 6
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json
  31. 194
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.abppkg.analyze.json
  32. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitHomePageRouteValueTransformer.cs
  33. 16
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs
  34. 41
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKitDynamicRouteValueTransformerBase.cs
  35. 11
      modules/docs/src/Volo.Docs.Admin.Application/Volo.Docs.Admin.Application.abppkg.analyze.json
  36. 4
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs
  37. 49
      modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json
  38. 3
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs
  39. 4
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  40. 16
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  41. 46
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs
  42. 24
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  43. 9
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs
  44. 6
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs
  45. 7
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs
  46. 101
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs
  47. 266
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.abppkg.analyze.json
  48. 16
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs

4
Directory.Packages.props

@ -44,8 +44,8 @@
<PackageVersion Include="MongoSandbox6.runtime.win-x64" Version="1.0.1" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.17" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.18" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.18" />
<PackageVersion Include="HtmlSanitizer" Version="8.1.870" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="IdentityServer4" Version="4.1.2" />

4
docs/en/docs-nav.json

@ -613,6 +613,10 @@
"text": "Google Cloud Storage Provider",
"path": "framework/infrastructure/blob-storing/google.md"
},
{
"text": "Bunny Provider",
"path": "framework/infrastructure/blob-storing/bunny.md"
},
{
"text": "Create a Custom Provider",
"path": "framework/infrastructure/blob-storing/custom-provider.md"

6
docs/en/framework/architecture/microservices/index.md

@ -25,6 +25,8 @@ However, developing such a well-modular application can be a problem since it is
ABP can help you in that point by offering a **microservice-compatible, strict module architecture** where your module is split into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](../best-practices) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration.
## Microservice Demo Solution: eShopOnAbp
## Microservice Solution Template
The [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) demonstrates a complete microservice solution based on the ABP.
ABP provides a pre-architected and production-ready microservice solution template that includes multiple services, API gateways and applications well integrated with each other. This template helps you quickly start building distributed systems with common microservice patterns.
See the [Microservice Solution Template](../../../solution-templates/microservice/index.md) documentation for details.

2
docs/en/framework/infrastructure/settings.md

@ -24,6 +24,8 @@ public class EmailSettingProvider : SettingDefinitionProvider
}
````
> If you're developing a [DDD module](../architecture/domain-driven-design), you usually create this class in the `Domain` layer, of course, that's not mandatory.
ABP automatically discovers this class and registers the setting definitions.
### SettingDefinition

29
docs/en/framework/infrastructure/timing.md

@ -8,11 +8,11 @@ ABP provides a basic infrastructure to make it easy and handle automatically whe
## IClock
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**.
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. Things especially get complicated when you save this value to a database and read it later, or send it to a client in a **different time zone**.
One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed.
One solution to this problem is always using `DateTime.UtcNow` and assuming all `DateTime` objects as UTC time. In this way, you can convert it to the time zone of the target client when needed.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the datetime (UTC or local) in a single point in your application.
**Example: Getting the current time**
@ -40,14 +40,14 @@ namespace AbpDemo
}
````
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already injects it and provides as a base property - so, you can directly use as `Clock`.
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already inject it and provide it as a base property - so, you can directly use it as `Clock`.
* Use the `Now` property to get the current time.
> Most of the times, `IClock` is the only service you need to know and use in your application.
> Most of the time, `IClock` is the only service you need to know and use in your application.
### Clock Options
`AbpClockOptions` is the [options](../fundamentals/options.md) class that used to set the clock kind.
`AbpClockOptions` is the [options](../fundamentals/options.md) class that is used to set the clock kind.
**Example: Use UTC Clock**
@ -58,13 +58,12 @@ Configure<AbpClockOptions>(options =>
});
````
Write this inside the `ConfigureServices` method of your [module](../architecture/modularity/basics.md).
> Default `Kind` is `Unspecified`, that actually make the Clock as it doesn't exists at all. Either make it `Utc` or `Local` if you want to get benefit of the Clock system.
Write this inside of the `ConfigureServices` method of your [module](../architecture/modularity/basics.md).
> The default `Kind` is `Unspecified`, which effectively disables the **Clock** functionality. Either make it `Utc` or `Local` if you want to get the benefit of the Clock system.
### DateTime Normalization
Other important function of the `IClock` is to normalize `DateTime` objects.
Another important function of the `IClock` is to normalize `DateTime` objects.
**Example usage:**
@ -75,11 +74,11 @@ var normalizedDateTime = Clock.Normalize(dateTime)
`Normalize` method works as described below:
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if current Clock is UTC and given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if current Clock is local and given `DateTime` is UTC.
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if the current Clock is UTC and the given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if the current Clock is local and the given `DateTime` is UTC.
* Sets `Kind` of the given `DateTime` (using the `DateTime.SpecifyKind(...)` method) to the `Kind` of the current Clock if given `DateTime`'s `Kind` is `Unspecified`.
`Normalize` method is used by the ABP when the it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
`Normalize` method is used by the ABP when it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
* `DateTime` type binding in the ASP.NET Core MVC model binding.
* Saving data to and reading data from database via [Entity Framework Core](../data/entity-framework-core).
@ -94,7 +93,7 @@ var normalizedDateTime = Clock.Normalize(dateTime)
In addition to the `Now`, `IClock` service has the following properties:
* `Kind`: Returns a `DateTimeKind` for the currently used clock type (`DateTimeKind.Utc`, `DateTimeKind.Local` or `DateTimeKind.Unspecified`).
* `SupportsMultipleTimezone`: Returns `true` if currently used clock is UTC.
* `SupportsMultipleTimezone`: Returns `true` if the currently used clock is UTC.
## Time Zones
@ -108,6 +107,6 @@ See the [setting documentation](../infrastructure/settings.md) to learn more abo
### ITimezoneProvider
`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name.
`ITimezoneProvider` is a service to simply convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get the list of these time zones and get a `TimeZoneInfo` with a given name.
It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library.

BIN
docs/en/images/account-pro-external-login-settings.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 KiB

After

Width:  |  Height:  |  Size: 258 KiB

BIN
docs/en/images/pen-test-alert-list-9.1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

67
docs/en/others/penetration-test-report.md

@ -1,6 +1,6 @@
# ABP Penetration Test Report
The ABP Commercial MVC `v9.0.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
The ABP Commercial MVC `v9.1.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
Many of these alerts are **false-positive**, meaning the vulnerability scanner detected these issues, but they are not exploitable. It's clearly explained for each false-positive alert why this alert is a false-positive.
@ -10,53 +10,23 @@ In the next sections, you will find the affected URLs, attack parameters (reques
There are high _(red flag)_, medium _(orange flag)_, low _(yellow flag)_, and informational _(blue flag)_ alerts.
![penetration-test-9.0.0](../images/pen-test-alert-list-9.0.png)
![penetration-test-9.1.0](../images/pen-test-alert-list-9.1.png)
> The informational alerts are not mentioned in this document. These alerts are not raising any risks on your application and they are optional.
### Path Traversal [Risk: High] - False Positive
### Spring4Shell [Risk: High] - False Positive
- *[POST] - https://localhost:44349/Account/Login* (attack: **\Login**)
- *[POST] - https://localhost:44349/Account/LinkLogin* (attack: **\LinkLogin**)
- *[POST] - https://localhost:44349/Account/Register* (attack: **\Register**)
- *[POST] - https://localhost:44349/Account/SecurityLogs* (attack: **\SecurityLogs**)
- *[POST] - https://localhost:44349/Identity/SecurityLogs* (attack: **\SecurityLogs**)
- *[POST] - https://localhost:44349/Account/ForgotPassword* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
- *[POST] - https://localhost:44349/Account/Login* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
- *[POST] - https://localhost:44349/Account/Login?ReturnUrl=%2FSettingManagement* (attack: **class.module.classLoader.DefaultAssertionStatus=nonsense**)
**Description**:
The Path Traversal attack technique allows an attacker access to files, directories, and commands that potentially reside outside the web document root directory. An attacker may manipulate a URL in such a way that the website will execute or reveal the contents of arbitrary files anywhere on the web server. Any device that exposes an HTTP-based interface is potentially vulnerable to Path Traversal.
**Solution**:
This is a **false-positive** alert since ABP does all related checks for this kind of attacks on the backend side for these endpoints.
### SQL Injection [Risk: High] - False Positive
* *[POST] — https://localhost:44349/Account/Login* (attack: **1q2w3E* AND 1=1 --**)
* *[POST] — https://localhost:44349/Account/ImpersonateUser* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**)
* *[POST] — https://localhost:44349/Abp/MultiTenancy/TenantSwitchModal* (attack: **CfDJ8Pyqeg0vtHtJpnK-9eLaft7-JxLJfJ6WHKPOdBZVxz14BDo061qpJ2NLplgAn2Hw16ec0IR38_wWAUkJGxP8hL6PcLfH0bh-ATNTspWyWYTGGbiH-zeKWiS5vWX-br2BA1hE7Dc45eWGUZNcVc_vm2s AND 1=1 --**)
* *[POST] — https://localhost:44349/Identity/OrganizationUnits/\** (attack: **6f4cd0ab-f4eb-7ce0-8b26-3a138af1840d" AND '1'='1**) (also, several other URLs...)
* *[POST] — https://localhost:44349/Identity/ClaimTypes/CreateModal* (attack: **aaaad AND '1'='1**)
**Description**:
SQL injection may be possible. SQL injection is a web security vulnerability that allows an attacker to interfere with the queries that an application makes to its database. It allows an attacker to view data that they are not normally able to retrieve and perform unauthorized actions.
The application appears to be vulnerable to CVE-2022-22965 (otherwise known as Spring4Shell) - remote code execution (RCE) via data binding.
**Explanation**:
ABP uses Entity Framework Core and LINQ. **It's safe against SQL Injection because it passes all data to the database via SQL parameters.** LINQ queries are not composed by using string manipulation or concatenation, that's why they are not susceptible to traditional SQL injection attacks. Therefore, this is a **false-positive** alert.
### SQL Injection - Authentication Bypass [Risk: High] - False Positive
* *[POST] — https://localhost:44349/Account/Login* (attack: **false AND 1=1 --**)
**Description**:
SQL injection may be possible on a login page, potentially allowing the application's authentication mechanism to be bypassed.
**Solution**:
This alert indicates that we must not trust client side input (even if there is client side validation in place) and check all data on the server side. ABP Framework already does that and makes server-side validations while authenticating a user. Therefore this is a **false-positive** alert.
ABP Framework is built on top of ASP.NET Core and does not use the Spring Framework. This application does not rely on Java-based technologies, making it immune to vulnerabilities like Spring4Shell. The detection is a false positive as there are no Spring dependencies in the project.
### Absence of Anti-CSRF Tokens [Risk: Medium] — False Positive
@ -130,6 +100,10 @@ Configure<AbpSecurityHeadersOptions>(options =>
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A&returnUrl=%2F&uiCulture=ar* (with combination of different parameters)
- *[GET] — https://localhost:44349/Abp/ApplicationLocalizationScript?cultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A* (with combination of different parameters)
- *[GET] — https://localhost:44349/api/language-management/language-texts?filter=aa&resourceName=&baseCultureName=es&targetCultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A&getOnlyEmptyValues=false&sorting=name+asc&skipCount=0&maxResultCount=10* (with combination of different parameters)
- *[GET] — https://localhost:44349/LanguageManagement/Texts/Edit?name=IncorrectCaptchaAnswer&targetCultureName=sv&resourceName=AbpAccount&baseCultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A* (with combination of different parameters)
- *[POST] — https://localhost:44349/Account/Login?ReturnUrl=%2FSettingManagement*
- *[POST] — https://localhost:44349/Account/Manage* (with combination of different parameters)
**Description:**
@ -149,14 +123,9 @@ The second URL is also a **false-positive** alert because there is no bad charac
### XSLT Injection [Risk: Medium] - False Positive
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=%3Cxsl%3Avalue-of+select%3D%22system-property%28%27xsl%3Avendor%27%29%22%2F%3E&returnUrl=%2F&uiCulture=ar*
- *[POST] — https://localhost:44349/Account/Login _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/ImpersonateUser _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/Register _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/Manage _(same URL with different parameters...)_*
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=%3Cxsl%3Avalue-of+select%3D%22system-property%28%27xsl%3Avendor%27%29%22%2F%3E&returnUrl=%2F&uiCulture=tr _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/Account/ForgotPassword _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/SaasWidgets/LatestTenants _(same URL with different parameters...)_*
- *[POST] — https://localhost:44349/AuditLogs*
- *[GET] — https://localhost:44349/SaasWidgets/LatestTenants _(same URL with different parameters...)_*
**Description**:
@ -168,10 +137,12 @@ This is a **false-positive** alert. v9.0 uses .NET 9 and the XSLT transformation
### Application Error Disclosure [Risk: Low] — False Positive
- *[GET] — https://localhost:44349/Abp/Languages/Switch*
- *[POST] — https://localhost:44349/Account/ImpersonateUser*
- *[GET] — https://localhost:44349/Account/ExternalLogins*
- *[GET] — https://localhost:44349/Account/Logout*
- *[GET] — https://localhost:44349/OrganizationUnits*
- *[GET] — https://localhost:44349/HostDashboard*
- *[GET] — https://localhost:44349/Saas/Host/Editions*
- *[GET] — https://localhost:44349/Saas/Host/Tenants*
**Description:**
@ -326,7 +297,7 @@ This vulnerability was reported as a positive alert, because ABP uses the [zxcvb
- *[GET] — https://localhost:44349/client-proxies/account-proxy.js?_v=638550091940000000 (and other client-proxies related URLs...)*
- *[GET] — https://localhost:44349/favicon.svg*
- *[GET] — https://localhost:44349/images/getting-started/bg-01.png* (and other image URLs...)
- *[GET] — https://localhost:44349/images/getting-started/bg-01.png* (and other image URLs...)
- *[GET] — https://localhost:44349/global-styles.css?_v=638556076064360335*
- *[GET] — https://localhost:44349/libs/@fortawesome/fontawesome-free/css/all.css?_v=%5CWEB-INF%5Cweb.xml (other several URLs...)*
- other URLs...

64
docs/en/release-info/migration-guides/abp-9-2.md

@ -0,0 +1,64 @@
# ABP Version 9.2 Migration Guide
This document is a guide for upgrading ABP v9.x solutions to ABP v9.2. There are some changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## Open-Source (Framework)
### Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
When multiple applications share the same storage for [ABP's Background Jobs Module](../../modules/background-jobs.md), jobs from one application might be visible to another application. This can lead to the following issues:
1. Applications may attempt to process jobs that don't belong to them
2. These attempts fail with "Undefined background job for the job name" error
3. Failed jobs are marked as `IsAbandoned = true`
4. The original application can no longer process these abandoned jobs
To fix this, we added the `ApplicationName` property to the `AbpBackgroundJobWorkerOptions` class. This property allows you to specify the application name, which helps isolate jobs between different applications. (See the [PR](https://github.com/abpframework/abp/pull/22169) for more details.)
**By default there is no breaking change. However, you need to create a migration for the database to add the `ApplicationName` column to the relevant table and apply it to your database.**
### Upgraded `MongoDB.Driver` to `3.1.0`
In this version, we upgraded `MongoDB.Driver` to `3.1.0`. To migrate your application, please refer to our [MongoDB Driver 2 to 3 Migration Guide](./MongoDB-Driver-2-to-3.md) document.
### Replaced Toastr with Custom Implementation (without depending on any 3rd party library)
In this version, we replaced Toastr with a custom implementation that does not depend on any 3rd party library. This is a breaking change if you are using the `Toastr` library.
Here are the migration steps:
1. Remove any direct Toastr dependencies from your application
2. Update your notification calls to use the new API
3. Migrate any custom styles or configurations
For detailed implementation guidelines and API documentation, see:
- [Notify Documentation](../../framework/ui/mvc-razor-pages/javascript-api/notify.md)
- [PR #21940 for more information](https://github.com/abpframework/abp/pull/21940)
## PRO
> Please check the **Open-Source (Framework)** section before reading this section. The listed topics might affect your application and you might need to take care of them.
If you are a paid-license owner and using the ABP's paid version, then please follow the following sections to get informed about the breaking changes and apply the necessary ones:
### Identity Pro Module: Require Email Verification to Register
In this version, we added a new setting to the Identity Pro module to require email verification to register. This is a security measure to prevent spam registrations:
![](./images/require-email-verification-for-register.png)
Typically, no changes are required. However, if you have inherited from the `AccountAppService` class and implemented your own logic, you'll need to update your constructor to match the new signature since two new services are now injected:
```diff
//code omitted for brevity...
+ protected IDistributedCache<EmailConfirmationCodeCacheItem> EmailConfirmationCodeCache { get; }
+ protected IdentityErrorDescriber IdentityErrorDescriber { get; }
public AccountAppService(
- IdentityUserTwoFactorChecker identityUserTwoFactorChecker)
+ IdentityUserTwoFactorChecker identityUserTwoFactorChecker,
+ IdentityErrorDescriber identityErrorDescriber)
```

BIN
docs/en/release-info/migration-guides/images/require-email-verification-for-register.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

1
docs/en/release-info/migration-guides/index.md

@ -2,6 +2,7 @@
The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../upgrading.md).
- [9.x to 9.2](abp-9-2.md)
- [9.0 to 9.1](abp-9-1.md)
- [8.x to 9.0](abp-9-0.md)
- [8.x to 8.3](abp-8-3.md)

13
docs/en/release-info/release-notes.md

@ -4,9 +4,20 @@ This document contains **brief release notes** for each release. Release notes o
> If you want to read the release notes for each ABP Studio release, check it out from [here](../studio/release-notes.md).
## 9.2 (2025-03-25)
This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/articles/abp-platform-9.2-rc-has-been-released-jpq072nh)** for the v9.2 release.
* Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
* Docs Module: Added "Alternative Words" to Filter Items
* Introducing the [Bunny BLOB Storage Provider](../framework/infrastructure/blob-storing/bunny.md)
* Upgraded `MongoDB.Driver` to **v3.1.0**
* Identity Pro Module: Require Email Verification to Register
* Switching users during OAuth login
## 9.1 (2025-01-16)
This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/blog/abp-9-1-release-candidate)** for the v9.1 release.
See the detailed **[blog post / announcement](https://abp.io/community/articles/abp.io-platform-9.1-final-has-been-released-h96a56qa)** for the v9.1 release.
* Upgraded to Angular 19
* Upgraded to OpenIddict 6.0

20
docs/en/release-info/road-map.md

@ -4,9 +4,9 @@ This document provides a road map, release schedule, and planned features for th
## Next Versions
### v9.2
### v9.3
The next version will be 9.2 and planned to release the stable 9.2 version in June 2025. We will be mostly working on the following topics:
The next version will be 9.3 and planned to release the stable 9.3 version in July 2025. We will be mostly working on the following topics:
* Framework
* Upgrading 3rd-party dependencies
@ -15,22 +15,20 @@ The next version will be 9.2 and planned to release the stable 9.2 version in Ju
* ABP Suite
* Define navigation properties without target string property dependency
* Improvements one-to-many scenarios
* Access to default code generation templates for customized templates
* File Upload Modal enhancements
* Master/Detail DataGrid Toggle Detail Row Enhancements for Blazor UI
* ABP Studio
* Allow to directly create new solutions with ABP's RC (Release Candidate) versions
* Automate more details on new service creation for a microservice solution
* Support multiple concurrent Kubernetes deployment/integration scenarios
* Improve the module installation experience / installation guides
* Auto-install 3rd-party dependencies
* Better handle long log files
* Allow to directly create new solutions with ABP's RC (Release Candidate) versions
* Support Intel processors for Mac computers, support ARM chipset for Windows and support Linux OS
* Improve client proxy generation experience
* Modular Monolith Application Startup Template
* Application modules
* Account module: Support mixed social/local login scenarios & enforcing email verification in wide aspect
* Account module: Support mixed social/local login scenarios & adding security related features
* UI/UX improvements on existing application modules
* Updating existing tutorials & documents (with other UI & DB options)
@ -55,6 +53,8 @@ The ABP framework is [open source](https://github.com/abpframework/abp) and free
* [#15932](https://github.com/abpframework/abp/issues/15932) / Introduce ABP Diagnostics Module
* [#16744](https://github.com/abpframework/abp/issues/16744) / State Management API
* [#17815](https://github.com/abpframework/abp/issues/17815) / Operation Rate Limiting
* [#119](https://github.com/abpframework/abp/issues/119) / REST API Versioning Improvements
* [#2087](https://github.com/abpframework/abp/issues/2087) / RavenDB Database Support
### Application Modules / UI Themes
@ -100,7 +100,7 @@ Here, are some of the important planned features for next ABP Studio versions:
Here, are some of the important planned features for the next ABP Suite versions:
* Handle image properties for entities
* Handle image properties for entities (in addition to file properties, which is already supported)
* Allow to define extra properties for DTOs those are not a part of the entity
* Allow to create pages instead of modals for CRUD page generation
* View-only (detail view) modal/page for an entity
@ -111,7 +111,7 @@ Here, are some of the important planned features for the next ABP Suite versions
## Feature Requests
Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but first search in the existing issues please. You can also contact info@abp.io for your feature requests and other suggestions.
Vote for your favorite feature on the related GitHub issues (and write your thoughts). You can create an issue on [the GitHub repository](https://github.com/abpframework/abp) for your feature requests, but please first search the existing issues. You can also contact [info@abp.io](mailto:info@abp.io) for your feature requests and other suggestions.
## See Also

15
docs/en/tutorials/microservice/part-06.md

@ -304,3 +304,18 @@ Now, the Ordering service displays the product name instead of the product ID. W
> **Design Tip**
>
> It is suggested that you keep that type of communication to a minimum and not couple your services with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering service frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Catalog service.
### Updating the Kubernetes Configuration
ABP microservice startup template provides [pre-configured Helm charts](../../solution-templates/microservice/helm-charts-and-kubernetes.md) to deploy your solution to Kubernetes. When you develop your solution, you should also care about configurations of these charts if you want to keep them up to date and working.
In the section *Generating Proxy Classes for the Integration Service* above, we added a new configuration to the `appsettings.json` file of the Ordering microservice. We should configure the corresponding Helm chart configuration to keep it synchronized.
Open the `etc\helm\cloudcrm\charts\ordering\templates\ordering.yaml` file in a text editor, and add the following lines under the `env` section, just like the other values present (be careful on indents since it is critical in YAML files):
````yaml
- name: "RemoteServices__CatalogService__BaseUrl"
value: "http://{%{{{ .Release.Name }}}%}-catalog"
````
With this simple configuration, now the Ordering module can discover the catalog microservice's URL inside your Kubernetes cluster.

10
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor

@ -5,7 +5,12 @@
{
foreach (var file in ScriptFiles)
{
<script src="@file"></script>
var src = file;
if (!AppBasePath.IsNullOrWhiteSpace())
{
src = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/");
}
<script src="@src"></script>
}
}
@ -19,6 +24,9 @@
[Parameter]
public string? BundleName { get; set; }
[Parameter]
public string? AppBasePath { get; set; }
private List<string>? ScriptFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;

10
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor

@ -5,7 +5,12 @@
{
foreach (var file in StyleFiles)
{
<link rel="stylesheet" href="@file" />
var href = file;
if (!AppBasePath.IsNullOrWhiteSpace())
{
href = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/");
}
<link rel="stylesheet" href="@href" />
}
}
@ -19,6 +24,9 @@
[Parameter]
public string? BundleName { get; set; }
[Parameter]
public string? AppBasePath { get; set; }
private List<string>? StyleFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;

15
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/UserExceptionInformer.cs

@ -8,6 +8,7 @@ using Volo.Abp.AspNetCore.Components.Messages;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Client;
namespace Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
@ -35,6 +36,8 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
{
//TODO: Create sync versions of the MessageService APIs.
LogException(context);
var errorInfo = GetErrorInfo(context);
if (errorInfo.Details.IsNullOrEmpty())
@ -49,6 +52,8 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
public async Task InformAsync(UserExceptionInformerContext context)
{
LogException(context);
var errorInfo = GetErrorInfo(context);
if (errorInfo.Details.IsNullOrEmpty())
@ -70,4 +75,14 @@ public class UserExceptionInformer : IUserExceptionInformer, IScopedDependency
options.SendExceptionDataToClientTypes = Options.SendExceptionDataToClientTypes;
});
}
protected virtual void LogException(UserExceptionInformerContext context)
{
if (context.Exception is AbpRemoteCallException && OperatingSystem.IsBrowser())
{
return;
}
Logger.LogException(context.Exception);
}
}

3
framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpComponentBase.cs

@ -172,8 +172,7 @@ public abstract class AbpComponentBase : OwningComponentBase
{
return;
}
Logger.LogException(exception);
await InvokeAsync(async () =>
{
await UserExceptionInformer.InformAsync(new UserExceptionInformerContext(exception));

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs

@ -16,6 +16,8 @@ public class AbpAspNetCoreMvcOptions
public bool ExposeIntegrationServices { get; set; } = false;
public bool ExposeClientProxyServices { get; set; } = false;
public bool AutoModelValidation { get; set; }
public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; }

20
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -99,6 +100,7 @@ public class AbpRemoteStreamContentModelBinder<TRemoteStreamContent> : IModelBin
{
var form = await request.ReadFormAsync();
var useMemoryStream = form.Files.Count > 1;
foreach (var file in form.Files)
{
// If there is an <input type="file" ... /> in the form and is left blank.
@ -109,13 +111,27 @@ public class AbpRemoteStreamContentModelBinder<TRemoteStreamContent> : IModelBin
if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase))
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length).As<TRemoteStreamContent>());
if (useMemoryStream)
{
var memoryStream = new MemoryStream();
await file.OpenReadStream().CopyToAsync(memoryStream);
memoryStream.Position = 0;
postedFiles.Add(new RemoteStreamContent(memoryStream, file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
bindingContext.HttpContext.Response.OnCompleted(async () =>
{
await memoryStream.DisposeAsync();
});
}
else
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
}
}
}
}
else if (bindingContext.IsTopLevelObject)
{
postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength).As<TRemoteStreamContent>());
postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength, disposeStream: false).As<TRemoteStreamContent>());
}
}

27
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -74,16 +74,29 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
protected virtual void RemoveIntegrationControllersIfNotExposed(ApplicationModel application)
{
if (Options.ExposeIntegrationServices)
if (!Options.ExposeIntegrationServices)
{
return;
var integrationControllers = GetControllers(application)
.Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(c.ControllerType))
.ToArray();
application.Controllers.RemoveAll(integrationControllers);
}
if (!Options.ExposeClientProxyServices)
{
var clientProxyServiceControllers = GetControllers(application)
.Where(c => IsClientProxyService(c.ControllerType))
.ToArray();
application.Controllers.RemoveAll(clientProxyServiceControllers);
}
var integrationControllers = GetControllers(application)
.Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(c.ControllerType))
.ToArray();
}
application.Controllers.RemoveAll(integrationControllers);
protected virtual bool IsClientProxyService(Type controllerType)
{
return typeof(IApplicationService).IsAssignableFrom(controllerType) &&
controllerType.GetBaseClasses().Any(x => x.IsGenericType && x.GetGenericTypeDefinition().FullName!.StartsWith("Volo.Abp.Http.Client.ClientProxying.ClientProxyBase"));
}
protected virtual IList<ControllerModel> GetControllers(ApplicationModel application)

4
framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css

@ -57,4 +57,8 @@
100% {
transform: rotate(0deg);
}
}
.table-responsive .dropdown-menu-position-strategy-fixed {
position: fixed !important;
}

13
framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs

@ -17,7 +17,6 @@ public class RemoteStreamContent : IRemoteStreamContent
public RemoteStreamContent(Stream stream, string? fileName = null, string? contentType = null, long? readOnlyLength = null, bool disposeStream = true)
{
_stream = stream;
FileName = fileName;
if (contentType != null)
{
@ -34,10 +33,16 @@ public class RemoteStreamContent : IRemoteStreamContent
public virtual void Dispose()
{
if (!_disposed && _disposeStream)
if (_disposed)
{
_disposed = true;
_stream?.Dispose();
return;
}
if (_disposeStream)
{
_stream.Dispose();
}
_disposed = true;
}
}

44
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Json;
@ -29,17 +28,17 @@ public class ClientProxyApiDescriptionFinder : IClientProxyApiDescriptionFinder,
Initialize();
}
public ActionApiDescriptionModel? FindAction(string methodName)
public virtual ActionApiDescriptionModel? FindAction(string methodName)
{
return ActionApiDescriptionModels.ContainsKey(methodName) ? ActionApiDescriptionModels[methodName] : null;
return ActionApiDescriptionModels.TryGetValue(methodName, out var model) ? model : null;
}
public ApplicationApiDescriptionModel GetApiDescription()
public virtual ApplicationApiDescriptionModel GetApiDescription()
{
return ApplicationApiDescriptionModel;
}
private void Initialize()
protected virtual void Initialize()
{
ApplicationApiDescriptionModel = GetApplicationApiDescriptionModel();
var controllers = ApplicationApiDescriptionModel.Modules.Select(x => x.Value).SelectMany(x => x.Controllers.Values).ToList();
@ -51,7 +50,6 @@ public class ClientProxyApiDescriptionFinder : IClientProxyApiDescriptionFinder,
foreach (var actionItem in controller.Actions.Values)
{
var actionKey = $"{appServiceType}.{actionItem.Name}.{string.Join("-", actionItem.ParametersOnMethod.Select(x => x.Type))}";
if (!ActionApiDescriptionModels.ContainsKey(actionKey))
{
ActionApiDescriptionModels.Add(actionKey, actionItem);
@ -60,7 +58,7 @@ public class ClientProxyApiDescriptionFinder : IClientProxyApiDescriptionFinder,
}
}
private ApplicationApiDescriptionModel GetApplicationApiDescriptionModel()
protected virtual ApplicationApiDescriptionModel GetApplicationApiDescriptionModel()
{
var applicationApiDescription = ApplicationApiDescriptionModel.Create();
var fileInfoList = new List<IFileInfo>();
@ -87,41 +85,21 @@ public class ClientProxyApiDescriptionFinder : IClientProxyApiDescriptionFinder,
return applicationApiDescription;
}
private void GetGenerateProxyFileInfos(List<IFileInfo> fileInfoList, string path = "")
protected virtual void GetGenerateProxyFileInfos(List<IFileInfo> fileInfoList, string path = "")
{
foreach (var directoryContent in VirtualFileProvider.GetDirectoryContents(path))
foreach (var fileInfo in VirtualFileProvider.GetDirectoryContents(path))
{
if (directoryContent.IsDirectory)
if (fileInfo.IsDirectory)
{
GetGenerateProxyFileInfos(fileInfoList, GetDirectoryContentPath(path, directoryContent));
GetGenerateProxyFileInfos(fileInfoList, path + fileInfo.Name.EnsureStartsWith('/'));
}
else
{
if (directoryContent.Name.EndsWith("generate-proxy.json"))
if (fileInfo.Name.EndsWith("generate-proxy.json", StringComparison.OrdinalIgnoreCase))
{
fileInfoList.Add(VirtualFileProvider.GetFileInfo(GetProxyFileInfoPath(path, directoryContent)));
fileInfoList.Add(fileInfo);
}
}
}
}
private string GetDirectoryContentPath(string rootPath, IFileInfo fileInfo)
{
if (fileInfo is PhysicalDirectoryInfo physicalDirectoryInfo)
{
return rootPath + physicalDirectoryInfo.Name.EnsureStartsWith('/');
}
return fileInfo.PhysicalPath!;
}
private string GetProxyFileInfoPath(string rootPath, IFileInfo fileInfo)
{
if (fileInfo is PhysicalFileInfo physicalFileInfo)
{
return rootPath + physicalFileInfo.Name.EnsureStartsWith('/');
}
return fileInfo.GetVirtualOrPhysicalPathOrNull()!;
}
}

2
framework/src/Volo.Abp.Swashbuckle/Volo.Abp.Swashbuckle.csproj

@ -30,6 +30,8 @@
<EmbeddedResource Include="wwwroot\swagger\ui\abp.swagger.js" />
<None Remove="wwwroot\swagger\ui\abp.js" />
<EmbeddedResource Include="wwwroot\swagger\ui\abp.js" />
<None Remove="wwwroot\swagger\oauth2-redirect.html" />
<EmbeddedResource Include="wwwroot\swagger\oauth2-redirect.html" />
</ItemGroup>
</Project>

39
framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/oauth2-redirect.html

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
localStorage.setItem("abp_swagger_oauth2", JSON.stringify(qp));
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

58
framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js

@ -1,9 +1,9 @@
var abp = abp || {};
(function () {
var oldSwaggerUIBundle = SwaggerUIBundle;
SwaggerUIBundle = function (configObject) {
var excludeUrl = ["swagger.json", "connect/token"]
var firstRequest = true;
@ -112,7 +112,57 @@ var abp = abp || {};
return oldSwaggerUIBundle(configObject);
}
SwaggerUIBundle = Object.assign(SwaggerUIBundle, oldSwaggerUIBundle);
window.addEventListener("storage", function (event) {
if (event.key !== "abp_swagger_oauth2" || !event.newValue) {
return;
}
var qp = JSON.parse(event.newValue || "{}");
localStorage.removeItem("abp_swagger_oauth2");
var oauth2 = window.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
});
})();

6
modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.abppkg.analyze.json

@ -36,9 +36,9 @@
"name": "BloggingAdminApplicationModule"
},
{
"declaringAssemblyName": "Volo.BloggingTestApp.EntityFrameworkCore",
"namespace": "Volo.BloggingTestApp.EntityFrameworkCore",
"name": "BloggingTestAppEntityFrameworkCoreModule"
"declaringAssemblyName": "Volo.BloggingTestApp.MongoDB",
"namespace": "Volo.BloggingTestApp.MongoDB",
"name": "BloggingTestAppMongoDbModule"
},
{
"declaringAssemblyName": "Volo.Abp.Account.Web",

50
modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo.CmsKit.Admin.Application.abppkg.analyze.json

@ -803,6 +803,23 @@
"isOptional": false
}
]
},
{
"returnType": "ListResultDto<PermissionLookupDto>",
"namespace": "Volo.CmsKit.Admin.Menus",
"name": "GetPermissionLookupAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "PermissionLookupInputDto",
"name": "inputDto",
"isOptional": false
}
]
}
],
"contentType": "applicationService",
@ -1328,6 +1345,17 @@
}
]
},
{
"returnType": "ListResultDto<BlogDto>",
"namespace": "Volo.CmsKit.Admin.Blogs",
"name": "GetAllListAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": []
},
{
"returnType": "BlogDto",
"namespace": "Volo.CmsKit.Admin.Blogs",
@ -1367,6 +1395,28 @@
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Admin.Blogs",
"name": "MoveAllBlogPostsAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "blogId",
"isOptional": false
},
{
"type": "Nullable<Guid>",
"name": "assignToBlogId",
"isOptional": false
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Admin.Blogs",

6
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Volo.CmsKit.Admin.Web.abppkg.analyze.json

@ -159,6 +159,12 @@
"name": "CreateModalModel",
"summary": null
},
{
"namespace": "Volo.CmsKit.Admin.Web.Pages.CmsKit.Blogs",
"contentType": "webPage",
"name": "DeleteBlogModal",
"summary": null
},
{
"namespace": "Volo.CmsKit.Admin.Web.Pages.CmsKit.Blogs",
"contentType": "webPage",

194
modules/cms-kit/src/Volo.CmsKit.Domain/Volo.CmsKit.Domain.abppkg.analyze.json

@ -1085,6 +1085,11 @@
"type": "System.Nullable`1[System.Guid]",
"name": "TenantId",
"summary": null
},
{
"type": "System.String",
"name": "RequiredPermissionName",
"summary": null
}
],
"contentType": "aggregateRoot",
@ -2129,6 +2134,23 @@
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Blogs",
"name": "SetBlogId",
"summary": null,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "blogId",
"isOptional": false
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Blogs",
@ -3083,6 +3105,38 @@
"isOptional": false
}
]
},
{
"returnType": "List<String>",
"namespace": "Volo.CmsKit.MarkedItems",
"name": "GetEntityIdsFilteredByUserAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "userId",
"isOptional": false
},
{
"type": "String",
"name": "entityType",
"isOptional": false
},
{
"type": "Nullable<Guid>",
"name": "tenantId",
"isOptional": true
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
}
],
"implementingInterfaces": [
@ -3532,6 +3586,23 @@
"isOptional": false
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Blogs",
"name": "DeleteAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "blogId",
"isOptional": false
}
]
}
],
"implementingInterfaces": [
@ -5026,6 +5097,33 @@
"isOptional": true
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Comments",
"name": "DeleteByEntityTypeAndIdAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "entityType",
"isOptional": false
},
{
"type": "String",
"name": "entityId",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
}
],
"contentType": "repositoryInterface",
@ -5229,6 +5327,11 @@
"name": "tagId",
"isOptional": true
},
{
"type": "Nullable<Guid>",
"name": "favoriteUserId",
"isOptional": true
},
{
"type": "Nullable<BlogPostStatus>",
"name": "statusFilter",
@ -5271,6 +5374,11 @@
"name": "tagId",
"isOptional": true
},
{
"type": "Nullable<Guid>",
"name": "favoriteUserId",
"isOptional": true
},
{
"type": "Nullable<BlogPostStatus>",
"name": "statusFilter",
@ -5449,6 +5557,55 @@
"isOptional": true
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Blogs",
"name": "UpdateBlogAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "sourceBlogId",
"isOptional": false
},
{
"type": "Nullable<Guid>",
"name": "targetBlogId",
"isOptional": true
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "Void",
"namespace": "Volo.CmsKit.Blogs",
"name": "DeleteByBlogIdAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "blogId",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
}
],
"contentType": "repositoryInterface",
@ -5535,6 +5692,43 @@
}
]
},
{
"returnType": "List<BlogWithBlogPostCount>",
"namespace": "Volo.CmsKit.Blogs",
"name": "GetListWithBlogPostCountAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "filter",
"isOptional": true
},
{
"type": "String",
"name": "sorting",
"isOptional": true
},
{
"type": "Int32",
"name": "maxResultCount",
"isOptional": true
},
{
"type": "Int32",
"name": "skipCount",
"isOptional": true
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "Int64",
"namespace": "Volo.CmsKit.Blogs",

15
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitHomePageRouteValueTransformer.cs

@ -1,27 +1,32 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
using Volo.CmsKit.Features;
using Volo.CmsKit.Public.Pages;
using Volo.CmsKit.Public.Web.Pages.Public;
namespace Volo.CmsKit.Public.Web.Pages;
public class CmsKitHomePageRouteValueTransformer : DynamicRouteValueTransformer, ITransientDependency
public class CmsKitHomePageRouteValueTransformer : CmsKitDynamicRouteValueTransformerBase
{
protected IFeatureChecker FeatureChecker { get; }
protected IPagePublicAppService PagePublicAppService { get; }
public CmsKitHomePageRouteValueTransformer(IFeatureChecker featureChecker, IPagePublicAppService pagePublicAppService)
public CmsKitHomePageRouteValueTransformer(
ICurrentTenant currentTenant,
ITenantConfigurationProvider tenantConfigurationProvider,
IFeatureChecker featureChecker,
IPagePublicAppService pagePublicAppService)
: base(currentTenant, tenantConfigurationProvider)
{
FeatureChecker = featureChecker;
PagePublicAppService = pagePublicAppService;
}
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
protected async override ValueTask<RouteValueDictionary> DoTransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (await FeatureChecker.IsEnabledAsync(CmsKitFeatures.PageEnable))
{

16
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKitPageRouteValueTransformer.cs

@ -1,30 +1,36 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.MultiTenancy;
using Volo.CmsKit.Features;
using Volo.CmsKit.Pages;
using Volo.CmsKit.Public.Pages;
using Volo.CmsKit.Public.Web.Pages.Public;
namespace Volo.CmsKit.Public.Web.Pages;
public class CmsKitPageRouteValueTransformer : DynamicRouteValueTransformer, ITransientDependency
public class CmsKitPageRouteValueTransformer : CmsKitDynamicRouteValueTransformerBase
{
protected IFeatureChecker FeatureChecker { get; }
protected IPagePublicAppService PagePublicAppService { get; }
protected IDistributedCache<PageCacheItem> PageCache { get; }
public CmsKitPageRouteValueTransformer(IFeatureChecker featureChecker, IPagePublicAppService pagePublicAppService, IDistributedCache<PageCacheItem> pageCache)
public CmsKitPageRouteValueTransformer(
ICurrentTenant currentTenant,
ITenantConfigurationProvider tenantConfigurationProvider,
IFeatureChecker featureChecker,
IPagePublicAppService pagePublicAppService,
IDistributedCache<PageCacheItem> pageCache)
: base(currentTenant, tenantConfigurationProvider)
{
FeatureChecker = featureChecker;
PagePublicAppService = pagePublicAppService;
PageCache = pageCache;
}
public async override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
protected async override ValueTask<RouteValueDictionary> DoTransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values.TryGetValue("slug", out var slugParameter) && slugParameter is not null)
{

41
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKitDynamicRouteValueTransformerBase.cs

@ -0,0 +1,41 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.CmsKit.Public.Web.Pages.Public;
public abstract class CmsKitDynamicRouteValueTransformerBase : DynamicRouteValueTransformer, ITransientDependency
{
protected ICurrentTenant CurrentTenant { get; }
protected ITenantConfigurationProvider TenantConfigurationProvider { get; }
protected CmsKitDynamicRouteValueTransformerBase(ICurrentTenant currentTenant, ITenantConfigurationProvider tenantConfigurationProvider)
{
CurrentTenant = currentTenant;
TenantConfigurationProvider = tenantConfigurationProvider;
}
public async override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
TenantConfiguration tenant = null;
try
{
tenant = await TenantConfigurationProvider.GetAsync(saveResolveResult: false);
}
catch (Exception)
{
//Ignore
}
using (CurrentTenant.Change(tenant?.Id, tenant?.Name))
{
return await DoTransformAsync(httpContext, values);
}
}
protected abstract ValueTask<RouteValueDictionary> DoTransformAsync(HttpContext httpContext, RouteValueDictionary values);
}

11
modules/docs/src/Volo.Docs.Admin.Application/Volo.Docs.Admin.Application.abppkg.analyze.json

@ -455,6 +455,17 @@
"isPrivate": false,
"isStatic": false,
"parameters": []
},
{
"returnType": "List<ProjectWithoutDetailsDto>",
"namespace": "Volo.Docs.Admin.Documents",
"name": "GetProjectsAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": []
}
],
"contentType": "applicationService",

4
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs

@ -129,12 +129,12 @@ public partial class UserManagement
if (await PermissionChecker.IsGrantedAsync(IdentityPermissions.Users.ManageRoles))
{
var userRoleNames = (await AppService.GetRolesAsync(entity.Id)).Items.Select(r => r.Name).ToList();
var userRoleIds = (await AppService.GetRolesAsync(entity.Id)).Items.Select(r => r.Id).ToList();
EditUserRoles = Roles.Select(x => new AssignedRoleViewModel
{
Name = x.Name,
IsAssigned = userRoleNames.Contains(x.Name)
IsAssigned = userRoleIds.Contains(x.Id)
}).ToArray();
ChangePasswordTextRole(TextRole.Password);

49
modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json

@ -291,6 +291,12 @@
"namespace": "Volo.Abp.Domain.Entities",
"declaringAssemblyName": "Volo.Abp.Data",
"fullName": "Volo.Abp.Domain.Entities.IHasConcurrencyStamp"
},
{
"name": "IHasCreationTime",
"namespace": "Volo.Abp.Auditing",
"declaringAssemblyName": "Volo.Abp.Auditing.Contracts",
"fullName": "Volo.Abp.Auditing.IHasCreationTime"
}
],
"methods": [
@ -351,6 +357,11 @@
"type": "Volo.Abp.Identity.IdentityClaimValueType",
"name": "ValueType",
"summary": null
},
{
"type": "System.DateTime",
"name": "CreationTime",
"summary": null
}
],
"contentType": "aggregateRoot",
@ -488,6 +499,12 @@
"namespace": "Volo.Abp.Auditing",
"declaringAssemblyName": "Volo.Abp.Auditing.Contracts",
"fullName": "Volo.Abp.Auditing.IHasEntityVersion"
},
{
"name": "IHasCreationTime",
"namespace": "Volo.Abp.Auditing",
"declaringAssemblyName": "Volo.Abp.Auditing.Contracts",
"fullName": "Volo.Abp.Auditing.IHasCreationTime"
}
],
"methods": [
@ -649,6 +666,11 @@
"type": "System.Int32",
"name": "EntityVersion",
"summary": "A version value that is increased whenever the entity is changed."
},
{
"type": "System.DateTime",
"name": "CreationTime",
"summary": null
}
],
"contentType": "aggregateRoot",
@ -5656,6 +5678,33 @@
}
]
},
{
"returnType": "List<OrganizationUnit>",
"namespace": "Volo.Abp.Identity",
"name": "GetListByDisplayNamesAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String[]",
"name": "displayNames",
"isOptional": false
},
{
"type": "Boolean",
"name": "includeDetails",
"isOptional": true
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "List<IdentityRole>",
"namespace": "Volo.Abp.Identity",

3
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs

@ -98,18 +98,21 @@ public interface IOrganizationUnitRepository : IBasicRepository<OrganizationUnit
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeChildren = false,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<Guid>> GetMemberIdsAsync(
Guid id,
bool includeChildren = false,
CancellationToken cancellationToken = default
);
Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
string filter = null,
bool includeChildren = false,
CancellationToken cancellationToken = default
);

4
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -420,14 +420,14 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
var sourceOrganization = await OrganizationUnitRepository.GetAsync(sourceOrganizationId, cancellationToken: CancellationToken);
Logger.LogDebug($"Remove dynamic claims cache for users of organization: {sourceOrganizationId}");
var userIdList = await OrganizationUnitRepository.GetMemberIdsAsync(sourceOrganizationId, cancellationToken: CancellationToken);
var userIdList = await OrganizationUnitRepository.GetMemberIdsAsync(sourceOrganizationId, includeChildren: true, cancellationToken: CancellationToken);
await DynamicClaimCache.RemoveManyAsync(userIdList.Select(userId => AbpDynamicClaimCacheItem.CalculateCacheKey(userId, sourceOrganization.TenantId)), token: CancellationToken);
var targetOrganization = targetOrganizationId.HasValue ? await OrganizationUnitRepository.GetAsync(targetOrganizationId.Value, cancellationToken: CancellationToken) : null;
if (targetOrganization != null)
{
Logger.LogDebug($"Remove dynamic claims cache for users of organization: {targetOrganizationId}");
userIdList = await OrganizationUnitRepository.GetMemberIdsAsync(targetOrganizationId.Value, cancellationToken: CancellationToken);
userIdList = await OrganizationUnitRepository.GetMemberIdsAsync(targetOrganizationId.Value, includeChildren: true, cancellationToken: CancellationToken);
await DynamicClaimCache.RemoveManyAsync(userIdList.Select(userId => AbpDynamicClaimCacheItem.CalculateCacheKey(userId, targetOrganization.TenantId)), token: CancellationToken);
}

16
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
@ -424,9 +425,20 @@ public class EfCoreIdentityUserRepository : EfCoreRepository<IIdentityDbContext,
{
if (targetOrganizationId != null)
{
var sourceOrganization = await (await GetDbContextAsync()).Set<OrganizationUnit>().FirstOrDefaultAsync(x => x.Id == sourceOrganizationId, cancellationToken: cancellationToken);
if (sourceOrganization == null)
{
throw new EntityNotFoundException(typeof(OrganizationUnit), sourceOrganizationId);
}
var allSourceOrganizationIds = await (await GetDbContextAsync()).Set<OrganizationUnit>()
.Where(x => x.Code.StartsWith(sourceOrganization.Code))
.Select(x => x.Id).ToArrayAsync(cancellationToken: cancellationToken);
var users = await (await GetDbContextAsync()).Set<IdentityUserOrganizationUnit>().Where(x => x.OrganizationUnitId == targetOrganizationId).Select(x => x.UserId).ToArrayAsync(cancellationToken: cancellationToken);
await (await GetDbContextAsync()).Set<IdentityUserOrganizationUnit>().Where(x => x.OrganizationUnitId == sourceOrganizationId && !users.Contains(x.UserId)).ExecuteUpdateAsync(t => t.SetProperty(e => e.OrganizationUnitId, targetOrganizationId), GetCancellationToken(cancellationToken));
await (await GetDbContextAsync()).Set<IdentityUserOrganizationUnit>().Where(x => x.OrganizationUnitId == sourceOrganizationId).ExecuteDeleteAsync(GetCancellationToken(cancellationToken));
await (await GetDbContextAsync()).Set<IdentityUserOrganizationUnit>().Where(x => allSourceOrganizationIds.Contains(x.OrganizationUnitId) && !users.Contains(x.UserId)).ExecuteUpdateAsync(t => t.SetProperty(e => e.OrganizationUnitId, targetOrganizationId), GetCancellationToken(cancellationToken));
await (await GetDbContextAsync()).Set<IdentityUserOrganizationUnit>().Where(x => allSourceOrganizationIds.Contains(x.OrganizationUnitId)).ExecuteDeleteAsync(GetCancellationToken(cancellationToken));
}
else
{

46
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs

@ -208,32 +208,31 @@ public class EfCoreOrganizationUnitRepository
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeChildren = false,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, includeChildren);
return await query.IncludeDetails(includeDetails).OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<Guid>> GetMemberIdsAsync(Guid id, CancellationToken cancellationToken = default)
public virtual async Task<List<Guid>> GetMemberIdsAsync(Guid id, bool includeChildren = false, CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
return await (from userOu in dbContext.Set<IdentityUserOrganizationUnit>()
join user in dbContext.Users on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == id
select user.Id).ToListAsync(cancellationToken);
var organizationUnit = await GetAsync(id, cancellationToken: cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, null, includeChildren);
return await query.Select(x => x.Id).ToListAsync(cancellationToken);
}
public virtual async Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
string filter = null,
bool includeChildren = false,
CancellationToken cancellationToken = default)
{
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, includeChildren);
return await query.CountAsync(GetCancellationToken(cancellationToken));
}
@ -324,14 +323,33 @@ public class EfCoreOrganizationUnitRepository
dbContext.Set<IdentityUserOrganizationUnit>().RemoveRange(ouMembersQuery);
}
protected virtual async Task<IQueryable<IdentityUser>> CreateGetMembersFilteredQueryAsync(OrganizationUnit organizationUnit, string filter = null)
protected virtual async Task<IQueryable<IdentityUser>> CreateGetMembersFilteredQueryAsync(
OrganizationUnit organizationUnit,
string filter = null,
bool includeChildren = false)
{
var dbContext = await GetDbContextAsync();
var query = from userOu in dbContext.Set<IdentityUserOrganizationUnit>()
join user in dbContext.Users on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == organizationUnit.Id
select user;
IQueryable<IdentityUser> query;
if (includeChildren)
{
var childrenIds = await (await GetDbSetAsync())
.Where(ou => ou.Code.StartsWith(organizationUnit.Code))
.Select(x => x.Id)
.ToListAsync();
query = from userOu in dbContext.Set<IdentityUserOrganizationUnit>()
join user in dbContext.Users on userOu.UserId equals user.Id
where childrenIds.Contains(userOu.OrganizationUnitId)
select user;
}
else
{
query = from userOu in dbContext.Set<IdentityUserOrganizationUnit>()
join user in dbContext.Users on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == organizationUnit.Id
select user;
}
if (!filter.IsNullOrWhiteSpace())
{

24
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
@ -361,13 +362,30 @@ public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDb
public virtual async Task UpdateOrganizationAsync(Guid sourceOrganizationId, Guid? targetOrganizationId, CancellationToken cancellationToken = default)
{
var sourceOrganizationUnit = await (await GetQueryableAsync<OrganizationUnit>(cancellationToken))
.Where(x => x.Id == sourceOrganizationId)
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
if (sourceOrganizationUnit == null)
{
throw new EntityNotFoundException(typeof(OrganizationUnit), sourceOrganizationId);
}
var allSourceOrganizationIds = await (await GetQueryableAsync<OrganizationUnit>(cancellationToken))
.Where(x => x.Code.StartsWith(sourceOrganizationUnit.Code))
.Select(x => x.Id)
.ToListAsync(cancellationToken: cancellationToken);
var users = await (await GetQueryableAsync(cancellationToken))
.Where(x => x.OrganizationUnits.Any(r => r.OrganizationUnitId == sourceOrganizationId))
.Where(x => x.OrganizationUnits.Any(r => allSourceOrganizationIds.Contains(r.OrganizationUnitId)))
.ToListAsync(GetCancellationToken(cancellationToken));
foreach (var user in users)
{
user.RemoveOrganizationUnit(sourceOrganizationId);
foreach (var organizationId in allSourceOrganizationIds)
{
user.RemoveOrganizationUnit(organizationId);
}
if (targetOrganizationId.HasValue)
{
user.AddOrganizationUnit(targetOrganizationId.Value);
@ -453,7 +471,7 @@ public class MongoIdentityUserRepository : MongoDbRepository<IAbpIdentityMongoDb
{
return query.Where(x => x.Id == id);
}
if (roleId.HasValue)
{
var organizationUnitIds = (await GetQueryableAsync<OrganizationUnit>(cancellationToken))

9
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs

@ -184,18 +184,19 @@ public class MongoOrganizationUnitRepository
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeChildren = false,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
cancellationToken = GetCancellationToken(cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, includeChildren, cancellationToken);
return await query
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
}
public virtual async Task<List<Guid>> GetMemberIdsAsync(Guid id, CancellationToken cancellationToken = default)
public virtual async Task<List<Guid>> GetMemberIdsAsync(Guid id, bool includeChildren = false, CancellationToken cancellationToken = default)
{
cancellationToken = GetCancellationToken(cancellationToken);
return await (await GetQueryableAsync<IdentityUser>(cancellationToken))
@ -206,10 +207,11 @@ public class MongoOrganizationUnitRepository
public virtual async Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
string filter = null,
bool includeChildren = false,
CancellationToken cancellationToken = default)
{
cancellationToken = GetCancellationToken(cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, includeChildren, cancellationToken);
return await query.CountAsync(cancellationToken);
}
@ -277,6 +279,7 @@ public class MongoOrganizationUnitRepository
protected virtual async Task<IQueryable<IdentityUser>> CreateGetMembersFilteredQueryAsync(
OrganizationUnit organizationUnit,
string filter = null,
bool includeChildren = false,
CancellationToken cancellationToken = default)
{
return (await GetQueryableAsync<IdentityUser>(cancellationToken))

6
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs

@ -45,10 +45,10 @@ public class EditModalModel : IdentityPageModel
}
IsEditCurrentUser = CurrentUser.Id == id;
var userRoleNames = (await IdentityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Name).ToList();
var userRoleIds = (await IdentityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Id).ToList();
foreach (var role in Roles)
{
if (userRoleNames.Contains(role.Name))
if (userRoleIds.Contains(role.Id))
{
role.IsAssigned = true;
}
@ -122,6 +122,8 @@ public class EditModalModel : IdentityPageModel
public class AssignedRoleViewModel
{
public Guid Id { get; set; }
[Required]
[HiddenInput]
public string Name { get; set; }

7
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs

@ -267,6 +267,10 @@ public abstract class OrganizationUnitRepository_Tests<TStartupModule> : AbpIden
var usersCount = await _organizationUnitRepository.GetMembersCountAsync(ou);
usersCount.ShouldBeGreaterThan(1);
usersCount = await _organizationUnitRepository.GetMembersCountAsync(ou, includeChildren: true);
usersCount.ShouldBeGreaterThanOrEqualTo(2);
}
[Fact]
@ -301,6 +305,9 @@ public abstract class OrganizationUnitRepository_Tests<TStartupModule> : AbpIden
await _organizationUnitRepository.RemoveAllMembersAsync(ou);
var newCount = await _organizationUnitRepository.GetMembersCountAsync(ou);
newCount.ShouldBe(0);
newCount = await _organizationUnitRepository.GetMembersCountAsync(ou, includeChildren: true);
newCount.ShouldBe(0);
}
[Fact]

101
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs

@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using Volo.Abp.AspNetCore.Security;
@ -31,52 +30,22 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
{
var request = await GetOpenIddictServerRequestAsync(HttpContext);
// If prompt=login was specified by the client application,
// immediately return the user agent to the login page.
if (request.HasPromptValue(OpenIddictConstants.PromptValues.Login))
{
// To avoid endless login -> authorization redirects, the prompt=login flag
// is removed from the authorization request payload before redirecting the user.
var prompt = string.Join(" ", request.GetPromptValues().Remove(OpenIddictConstants.PromptValues.Login));
var parameters = Request.HasFormContentType ?
Request.Form.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList() :
Request.Query.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList();
parameters.Add(KeyValuePair.Create(OpenIddictConstants.Parameters.Prompt, new StringValues(prompt)));
return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters)
});
}
// If prompt=select_account was specified by the client application,
// We will redirect the user to the select_account page.
if (request.HasPromptValue(OpenIddictConstants.PromptValues.SelectAccount))
{
// To avoid endless login -> authorization redirects, the prompt=login flag
// is removed from the authorization request payload before redirecting the user.
var prompt = string.Join(" ", request.GetPromptValues().Remove(OpenIddictConstants.PromptValues.SelectAccount));
var parameters = Request.HasFormContentType ?
Request.Form.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList() :
Request.Query.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList();
parameters.Add(KeyValuePair.Create(OpenIddictConstants.Parameters.Prompt, new StringValues(prompt)));
var selectAccountPath = HttpContext.RequestServices.GetRequiredService<IOptions<AbpOpenIddictAspNetCoreOptions>>().Value.SelectAccountPage;
return Redirect(Url.Content($"{selectAccountPath}?RedirectUri={UrlEncoder.Default.Encode(Request.PathBase + Request.Path + QueryString.Create(parameters))}"));
}
// Retrieve the user principal stored in the authentication cookie.
// If a max_age parameter was provided, ensure that the cookie is not too old.
// If the user principal can't be extracted or the cookie is too old, redirect the user to the login page.
// Try to retrieve the user principal stored in the authentication cookie and redirect
// the user agent to the login page (or to an external provider) in the following cases:
//
// - If the user principal can't be extracted or the cookie is too old.
// - If prompt=login was specified by the client application.
// - If max_age=0 was specified by the client application (max_age=0 is equivalent to prompt=login).
// - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough.
//
// For scenarios where the default authentication handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result == null || !result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value)))
if (result is not { Succeeded: true } ||
((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null &&
TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) &&
TempData["IgnoreAuthenticationChallenge"] is null or false))
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
@ -91,13 +60,33 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
}));
}
return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
});
// To avoid endless login endpoint -> authorization endpoint redirects, a special temp data entry is
// used to skip the challenge if the user agent has already been redirected to the login endpoint.
//
// Note: this flag doesn't guarantee that the user has accepted to re-authenticate. If such a guarantee
// is needed, the existing authentication cookie MUST be deleted AND revoked (e.g using ASP.NET Core
// Identity's security stamp feature with an extremely short revalidation time span) before triggering
// a challenge to redirect the user agent to the login endpoint.
TempData["IgnoreAuthenticationChallenge"] = true;
// For scenarios where the default challenge handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
return Challenge(new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(Request.HasFormContentType ? Request.Form : Request.Query)
});
}
// If prompt=select_account was specified by the client application,
// We will redirect the user to the select_account page.
if (request.HasPromptValue(OpenIddictConstants.PromptValues.SelectAccount) && TempData["IgnoreSelectAccount"] is null or false)
{
// To avoid endless select account endpoint -> authorization endpoint redirects, a special temp data entry is
// used to skip the redirect if the user agent has already been redirected to the select account endpoint.
TempData["IgnoreSelectAccount"] = true;
var selectAccountPath = HttpContext.RequestServices.GetRequiredService<IOptions<AbpOpenIddictAspNetCoreOptions>>().Value.SelectAccountPage.RemovePostFix("/");
return Redirect(Url.Content($"{selectAccountPath}?RedirectUri={UrlEncoder.Default.Encode(Request.PathBase + Request.Path + QueryString.Create(Request.HasFormContentType ? Request.Form : Request.Query))}"));
}
// Retrieve the profile of the logged in user.
@ -111,8 +100,7 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(Request.HasFormContentType ? Request.Form : Request.Query)
});
}
}
@ -124,8 +112,7 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(Request.HasFormContentType ? Request.Form : Request.Query)
});
}

266
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.abppkg.analyze.json

@ -797,66 +797,7 @@
"isOptional": false
},
{
"type": "Guid",
"name": "client",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "List<OpenIddictToken>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
"name": "FindAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "Guid",
"name": "client",
"isOptional": false
},
{
"type": "String",
"name": "status",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "List<OpenIddictToken>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
"name": "FindAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "Guid",
"type": "Nullable<Guid>",
"name": "client",
"isOptional": false
},
@ -1036,6 +977,43 @@
}
]
},
{
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
"name": "RevokeAsync",
"summary": null,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "Nullable<Guid>",
"name": "applicationId",
"isOptional": false
},
{
"type": "String",
"name": "status",
"isOptional": false
},
{
"type": "String",
"name": "type",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
@ -1054,7 +1032,51 @@
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
"name": "RevokeByApplicationIdAsync",
"summary": null,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "applicationId",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Tokens",
"name": "RevokeBySubjectAsync",
"summary": null,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
}
@ -1344,10 +1366,20 @@
"isOptional": false
},
{
"type": "Guid",
"type": "Nullable<Guid>",
"name": "client",
"isOptional": false
},
{
"type": "String",
"name": "status",
"isOptional": false
},
{
"type": "String",
"name": "type",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
@ -1358,26 +1390,16 @@
{
"returnType": "List<OpenIddictAuthorization>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindAsync",
"name": "FindByApplicationIdAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "Guid",
"name": "client",
"isOptional": false
},
{
"type": "String",
"name": "status",
"name": "applicationId",
"isOptional": false
},
{
@ -1388,33 +1410,40 @@
]
},
{
"returnType": "List<OpenIddictAuthorization>",
"returnType": "OpenIddictAuthorization",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindAsync",
"name": "FindByIdAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "subject",
"isOptional": false
},
{
"type": "Guid",
"name": "client",
"name": "id",
"isOptional": false
},
{
"type": "String",
"name": "status",
"isOptional": false
},
"type": "CancellationToken",
"name": "cancellationToken",
"isOptional": true
}
]
},
{
"returnType": "List<OpenIddictAuthorization>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindBySubjectAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "String",
"name": "type",
"name": "subject",
"isOptional": false
},
{
@ -1427,7 +1456,7 @@
{
"returnType": "List<OpenIddictAuthorization>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindByApplicationIdAsync",
"name": "ListAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
@ -1435,8 +1464,13 @@
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "applicationId",
"type": "Nullable<Int32>",
"name": "count",
"isOptional": false
},
{
"type": "Nullable<Int32>",
"name": "offset",
"isOptional": false
},
{
@ -1447,9 +1481,9 @@
]
},
{
"returnType": "OpenIddictAuthorization",
"returnType": "Int64",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindByIdAsync",
"name": "PruneAsync",
"summary": null,
"isAsync": true,
"isPublic": true,
@ -1457,8 +1491,8 @@
"isStatic": false,
"parameters": [
{
"type": "Guid",
"name": "id",
"type": "DateTime",
"name": "date",
"isOptional": false
},
{
@ -1469,11 +1503,11 @@
]
},
{
"returnType": "List<OpenIddictAuthorization>",
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "FindBySubjectAsync",
"name": "RevokeAsync",
"summary": null,
"isAsync": true,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
@ -1483,6 +1517,21 @@
"name": "subject",
"isOptional": false
},
{
"type": "Nullable<Guid>",
"name": "applicationId",
"isOptional": false
},
{
"type": "String",
"name": "status",
"isOptional": false
},
{
"type": "String",
"name": "type",
"isOptional": false
},
{
"type": "CancellationToken",
"name": "cancellationToken",
@ -1491,23 +1540,18 @@
]
},
{
"returnType": "List<OpenIddictAuthorization>",
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "ListAsync",
"name": "RevokeByApplicationIdAsync",
"summary": null,
"isAsync": true,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "Nullable<Int32>",
"name": "count",
"isOptional": false
},
{
"type": "Nullable<Int32>",
"name": "offset",
"type": "Guid",
"name": "applicationId",
"isOptional": false
},
{
@ -1518,18 +1562,18 @@
]
},
{
"returnType": "Int64",
"returnType": "ValueTask<Int64>",
"namespace": "Volo.Abp.OpenIddict.Authorizations",
"name": "PruneAsync",
"name": "RevokeBySubjectAsync",
"summary": null,
"isAsync": true,
"isAsync": false,
"isPublic": true,
"isPrivate": false,
"isStatic": false,
"parameters": [
{
"type": "DateTime",
"name": "date",
"type": "String",
"name": "subject",
"isOptional": false
},
{

16
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionSaver.cs

@ -141,15 +141,15 @@ public class StaticPermissionSaver : IStaticPermissionSaver, ITransientDependenc
throw;
}
await unitOfWork.CompleteAsync();
}
if (newOrChangedPermissions.Any())
{
await DistributedEventBus.PublishAsync(new DynamicPermissionDefinitionsChangedEto
if (newOrChangedPermissions.Any())
{
Permissions = newOrChangedPermissions.Distinct().ToList()
});
await DistributedEventBus.PublishAsync(new DynamicPermissionDefinitionsChangedEto
{
Permissions = newOrChangedPermissions.Distinct().ToList()
});
}
await unitOfWork.CompleteAsync();
}
}

Loading…
Cancel
Save