diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..43e9cba5de --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 7.x.x | :white_check_mark: | +| < 7.0.0 | :x: | + +## Reporting a Vulnerability + +Please don not share vulnerabilities publicly in GitHub or other platforms. +You can report security issues by sending a email to `security@abp.io` +Your report is immediately evaluated. We publish patch versions for critical vulnerabilities in a week at most. diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 1b8c972d5c..d9794a06e4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -567,7 +567,7 @@ "TotalPrice": "Total Price", "ThereIsNoInvoice": "There is no invoice", "MyOrganizations_Detail_PaymentProviderInfo": "If you have purchased your license through {0} gateway, it sends the PDF invoice to your email address, see {0} invoicing.", - "MyOrganizations_Detail_PayUInfo": "If you have purchased through the PayU gateway, click the \"Request Invoice\" button and fill in the billing information.", + "MyOrganizations_Detail_PayUInfo": "If you have purchased through the Iyzico gateway, click the \"Request Invoice\" button and fill in the billing information.", "MyOrganizations_Detail_ConclusionInfo": "Your invoice request will be concluded within {0} business days.", "ExtendYourLicense": "Extend your {0} license", "Continue": "Continue", @@ -1001,19 +1001,19 @@ "ABPSOLUTION": "ABP SOLUTION", "CreatingAnEmptySolution_ABPSOLUTION_Description": "ABP provides a well-architected, layered and production-ready startup solution based on the Domain Driven Design principles. The solution also includes a pre-configured unit and integration test projects for each layer.", "CommonLibraries": "Common Libraries", - "CommonLibraries_THEPROBLEM_Description": "Which libraries should you use to implement common requirements? The software development ecosystem is highly dynamic and it is hard to follow the latest tools, libraries, trends and approaches.", - "CommonLibraries_ABPSOLUTION_Description": "ABP pre-integrates the popular, mature and up-to-date libraries into the solution. You don't spend time integrating them and talking to each other. They properly work out of the box.", + "CommonLibraries_THEPROBLEM_Description": "Which libraries should you use to implement common requirements? The software development ecosystem is highly dynamic, making it challenging to keep up with the latest tools, libraries, trends, and approaches.", + "CommonLibraries_ABPSOLUTION_Description": "ABP pre-integrates popular, mature, and up-to-date libraries into the solution. You don't need to spend time integrating them or making them communicate with each other. They work properly out of the box.", "UITheme&Layout": "UI Theme & Layout", - "UITheme&Layout_THEPROBLEM_Description": "When it comes to the UI, there are a lot of challenges, including preparing a foundation to create a responsive, modern and flexible UI kit with a consistent look & feel and tons of features (like left/top navigation menu, header, toolbar, footer, widgets and so.).", - "UITheme&Layout_THEPROBLEM_Description2": "Even if you buy a pre-built theme, integrating it into your solution may take days of development. Upgrading such a theme is another problem. Most of the time, the theme's HTML/CSS structure is mixed with your UI code, and it is not easy to upgrade or change the theme later.", - "UITheme&Layout_ABPSOLUTION_Description": "ABP Framework provides a theming system that makes your UI code independent from the theme. Themes are isolated, and they are NuGet/NPM packages. Installing or upgrading a theme is just a minute. While you can build your theme (or integrate an existing theme), ABP Commercial offers professional and modern themes.", - "UITheme&Layout_ABPSOLUTION_Description2": "There are also UI component providers (like Telerik and DevExpress). But they only provide individual components. You are responsible for creating your own layout system. You can use such libraries in your ABP-based solutions just like in any other project.", + "UITheme&Layout_THEPROBLEM_Description": "When addressing UI concerns, a range of challenges surfaces. These include establishing the groundwork for a responsive, contemporary, and adaptable UI kit with a consistent appearance and a host of features like navigation menus, headers, toolbars, footers, widgets, and more.", + "UITheme&Layout_THEPROBLEM_Description2": "Even if you opt for a pre-designed theme, seamlessly integrating it into your project could demand days of development. An additional hurdle lies in upgrading such themes. Frequently, the theme's HTML/CSS structure becomes intertwined with your UI code, rendering future theme changes or upgrades intricate tasks. This interweaving of code and design complicates the flexibility of making adjustments down the line.", + "UITheme&Layout_ABPSOLUTION_Description": "ABP Framework offers a distinctive theming system that liberates your UI code from theme constraints. Themes exist in isolation, packaged as NuGet or NPM packages, making theme installation or upgrades a matter of minutes. While you retain the option to develop your custom theme or integrate an existing one, ABP Commercial presents a collection of polished and contemporary themes.", + "UITheme&Layout_ABPSOLUTION_Description2": "Additionally, there are UI component providers like Telerik and DevExpress. However, these providers primarily furnish individual components, placing the onus on you to establish your layout system. When working within ABP-based projects, you can seamlessly incorporate these libraries, similar to how you would in any other project.", "TestInfrastructure": "Test Infrastructure", - "TestInfrastructure_THEPROBLEM_Description": "Preparing a robust test environment takes time. You need to setup test projects in your solution, select the tools, mock the services and database, create the required base classes and utility services to reduce repeating code in the tests and so on.", - "TestInfrastructure_ABPSOLUTION_Description": "ABP Startup Templates comes with the test projects already configured for you, and you can immediately write your first unit or integration test code on day 1.", + "TestInfrastructure_THEPROBLEM_Description": "Establishing a robust testing environment is a time-consuming endeavor. It involves setting up dedicated test projects within your solution, carefully selecting the necessary tools, creating service and database mocks, crafting essential base classes and utility services to minimize redundant code across tests, and addressing various related tasks.", + "TestInfrastructure_ABPSOLUTION_Description": "ABP Startup Templates arrive pre-equipped with configured test projects, streamlining the process for you. This means that from day one, you can readily commence writing your initial unit or integration test code without delay.", "CodingStandards&Training": "Coding Standards & Training", - "CodingStandards&Training_THEPROBLEM_Description": "Once you create the development-ready solution, you typically need to train the developers to explain the system and develop it with the same conventions in a standard and consistent way. Even if you train the developers, it is hard to prepare and maintain your documentation. Over time, every developer will write the code differently, and coding standards will begin to diverge.", - "CodingStandards&Training_ABPSOLUTION_Description": "ABP solution is already well-defined and well-documented. Tutorials and best practice guides clearly explain how to make development on an ABP project.", + "CodingStandards&Training_THEPROBLEM_Description": "After you've set up the solution for development, you usually have to teach the developers how the system works and how to build it using the same agreed-upon methods. Even if you give them training, keeping the documentation up-to-date can be difficult. As time goes on, each developer might write code in their own way, causing the rules for writing code to become different from each other.", + "CodingStandards&Training_ABPSOLUTION_Description": "The ABP solution is already neatly organized and has clear explanations. Step-by-step tutorials and guides show you exactly how to work on an ABP project.", "KeepingYourSolutionUpToDate": "Keeping Your Solution Up to Date", "KeepingYourSolutionUpToDate_THEPROBLEM_Description": "After you start your development, you must keep track of the new versions of the libraries you use for upgrades & patches.", "KeepingYourSolutionUpToDate_ABPSOLUTION_Description": "We regularly update all packages to the latest versions and test them before the stable release. When you update the ABP Framework, all its dependencies are upgraded to edge technology.", @@ -1073,6 +1073,8 @@ "ABPCommunity_Description2": "It is easy to share code or even re-usable libraries between ABP developers. A code snippet that works for you will also work for others. There are a lot of samples and tutorials that you can directly implement for your application.", "ABPCommunity_Description3": "When you hire a developer who worked before with the ABP architecture will immediately understand your solution and start development in a very short time.", "WhyAbpIo_Page_Title": "Why ABP.IO Platform?", - "AbpStudio_Page_Title": "ABP Studio" + "AbpStudio_Page_Title": "ABP Studio", + "CampaignInfo": "Buy a new license or renew your existing license and get an additional 2 months at no additional cost! This offer is valid for all license plans. Ensure you take advantage of this limited-time promotion to expand your access to premium features and upgrades.", + "HurryUpLastDay": "Hurry Up! Last Day: {0}" } } diff --git a/docs/en/API/Swagger-Integration.md b/docs/en/API/Swagger-Integration.md index d9afd0e2e2..aad5c531c4 100644 --- a/docs/en/API/Swagger-Integration.md +++ b/docs/en/API/Swagger-Integration.md @@ -153,3 +153,38 @@ public override void OnApplicationInitialization(ApplicationInitializationContex ``` > Do not forget to set `OAuthClientId` and `OAuthClientSecret`. + +## Using Swagger with OIDC + +You may also want to configure swagger using **OpenIdConnect** instead of OAUTH. This is especially useful when you need to configure different metadata address than the issuer in cases such as when you deploy your application to kubernetes cluster or docker. In these cases, metadata address will be used in sign-in process to reach the valid authentication server discovery endpoint over the internet and use the internal network to validate the obtained token. + +To do that, we need to use `AddAbpSwaggerGenWithOidc` extension to configure Swagger with OAuth issuer and scopes in `ConfigureServices` method of our module: + +```csharp +context.Services.AddAbpSwaggerGenWithOidc( + configuration["AuthServer:Authority"], + scopes: new[] { "SwaggerDemo" }, + // "authorization_code" + flows: new[] { AbpSwaggerOidcFlows.AuthorizationCode }, + // When deployed on K8s, should be metadata URL of the reachable DNS over internet like https://myauthserver.company.com + discoveryEndpoint: configuration["AuthServer:Authority"], + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "SwaggerDemo API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + }); +``` + +The `flows` is a list of default oidc flows that is supported by the oidc-provider (authserver). You can see the default supported flows below: + +- `AbpSwaggerOidcFlows.AuthorizationCode`: The `"authorization_code"` flow is the **default and suggested** flow. **Doesn't require a client secret** when even there is a field for it. +- `AbpSwaggerOidcFlows.Implicit`: The deprecated `"implicit"` flow that was used for javascript applications. +- `AbpSwaggerOidcFlows.Password`: The legacy `password` flow which is also known as Resource Ownder Password flow. You need to provide a user name, password and client secret for it. +- `AbpSwaggerOidcFlows.ClientCredentials`: The `"client_credentials"` flow that is used for server to server interactions. + +You can define one or many flows which will be shown in the Authorize modal. You can set it **null which will use the default "authorization_code"** flow. + +The `discoveryEndpoint` is the reachable openid-provider endpoint for the `.well-known/openid-configuration`. You can set it to **null which will use default AuthServer:Authority** appsettings configuration. If you are deploying your applications to a kubernetes cluster or docker swarm, you should to set the `discoveryEndpoint` as real DNS that should be reachable over the internet. + +> If are having problems with seeing the authorization modal, check the browser console logs and make sure you have a correct and reachable `discoveryEndpoint` diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 77f2da8bd0..69903e1c85 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -70,7 +70,7 @@ After you have installed these NuGet packages, you need to configure your projec > You have to configure a storage for Hangfire. -2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class: +2. If you want to use hangfire's dashboard, you can add `UseAbpHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class: ````csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -79,7 +79,7 @@ After you have installed these NuGet packages, you need to configure your projec // ... others - app.UseHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() + app.UseAbpHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() app.UseConfiguredEndpoints(); } ```` @@ -128,9 +128,9 @@ You can integrate the Hangfire dashboard to [ABP authorization system](Authoriza class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application: ```csharp -app.UseHangfireDashboard("/hangfire", new DashboardOptions +app.UseAbpHangfireDashboard("/hangfire", options => { - AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() } + options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }; }); ``` @@ -146,11 +146,11 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions If you want to require an additional permission, you can pass it into the constructor as below: ```csharp -app.UseHangfireDashboard("/hangfire", new DashboardOptions +app.UseAbpHangfireDashboard("/hangfire", options => { - AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName") } + options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName") }; }); ``` -**Important**: `UseHangfireDashboard` should be called after the authentication and authorization middlewares in your `Startup` class (probably at the last line). Otherwise, +**Important**: `UseAbpHangfireDashboard` should be called after the authentication and authorization middlewares in your `Startup` class (probably at the last line). Otherwise, authorization will always fail! diff --git a/docs/en/Background-Workers-Hangfire.md b/docs/en/Background-Workers-Hangfire.md index 9506eb97a7..70c8d0cf95 100644 --- a/docs/en/Background-Workers-Hangfire.md +++ b/docs/en/Background-Workers-Hangfire.md @@ -70,7 +70,7 @@ After you have installed these NuGet packages, you need to configure your projec > You have to configure a storage for Hangfire. -2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class +2. If you want to use hangfire's dashboard, you can add `UseAbpHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class ````csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -79,7 +79,7 @@ After you have installed these NuGet packages, you need to configure your projec // ... others - app.UseHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() + app.UseAbpHangfireDashboard(); //should add to the request pipeline before the app.UseConfiguredEndpoints() app.UseConfiguredEndpoints(); } ```` diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 550ea6d9f8..2fff974f50 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -469,6 +469,8 @@ Configure(options => }); ```` +> **IMPORTANT**: Outbox sending service uses distributed locks to ensure only a single instance of your application consumes the outbox queue concurrently. Distributed locking key should be unique per database. The `config` object (in the preceding code example) has a `DatabaseName` property, which is used in the distributed lock key to ensure the uniqueness. `DatabaseName` is automatically set by the `UseDbContext` method, getting the database name from the `ConnectionStringName` attribute of the `YourDbContext` class. So, if you have multiple databases in your system, ensure that you use the same connection string name for the same database, but different connection string names for different databases. If you can't ensure that, you can manually set `config.DatabaseName` (after the `UseDbContext` line) to ensure that uniqueness. + ### Enabling event inbox Open your `DbContext` class (EF Core or MongoDB), implement the `IHasEventInbox` interface. You should end up by adding a `DbSet` property into your `DbContext` class: @@ -502,6 +504,8 @@ Configure(options => }); ```` +**IMPORTANT**: Inbox processing service uses distributed locks to ensure only a single instance of your application consumes the inbox queue concurrently. Distributed locking key should be unique per database. The `config` object (in the preceding code example) has a `DatabaseName` property, which is used in the distributed lock key to ensure the uniqueness. `DatabaseName` is automatically set by the `UseDbContext` method, getting the database name from the `ConnectionStringName` attribute of the `YourDbContext` class. So, if you have multiple databases in your system, ensure that you use the same connection string name for the same database, but different connection string names for different databases. If you can't ensure that, you can manually set `config.DatabaseName` (after the `UseDbContext` line) to ensure that uniqueness. + ### Additional Configuration > The default configuration will be enough for most cases. However, there are some options you may want to set for outbox and inbox. @@ -525,6 +529,7 @@ Here, the following properties are available on the `config` object: * `IsSendingEnabled` (default: `true`): You can set to `false` to disable sending outbox events to the actual event bus. If you disable this, events can still be added to outbox, but not sent. This can be helpful if you have multiple applications (or application instances) writing to outbox, but use one of them to send the events. * `Selector`: A predicate to filter the event (ETO) types to be used for this configuration. Should return `true` to select the event. It selects all the events by default. This is especially useful if you want to ignore some ETO types from the outbox, or want to define named outbox configurations and group events within these configurations. See the *Named Configurations* section. * `ImplementationType`: Type of the class that implements the database operations for the outbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages. +* `DatabaseName`: Unique database name for the database that is used for this outbox configuration. See the **IMPORTANT** paragraph at the end of the *Enabling event outbox* section. #### Inbox configuration @@ -546,6 +551,7 @@ Here, the following properties are available on the `config` object: * `EventSelector`: A predicate to filter the event (ETO) types to be used for this configuration. This is especially useful if you want to ignore some ETO types from the inbox, or want to define named inbox configurations and group events within these configurations. See the *Named Configurations* section. * `HandlerSelector`: A predicate to filter the event handled types (classes implementing the `IDistributedEventHandler` interface) to be used for this configuration. This is especially useful if you want to ignore some event handler types from inbox processing, or want to define named inbox configurations and group event handlers within these configurations. See the *Named Configurations* section. * `ImplementationType`: Type of the class that implements the database operations for the inbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages. +* `DatabaseName`: Unique database name for the database that is used for this outbox configuration. See the **IMPORTANT** paragraph at the end of the *Enabling event inbox* section. #### AbpEventBusBoxesOptions diff --git a/docs/en/Modules/OpenIddict.md b/docs/en/Modules/OpenIddict.md index cb9d36a30c..20098eb975 100644 --- a/docs/en/Modules/OpenIddict.md +++ b/docs/en/Modules/OpenIddict.md @@ -471,7 +471,24 @@ In **Blazor wasm** applications, add `options.ProviderOptions.DefaultScopes.Add( In **Angular** applications, add `offline_access` to **oAuthConfig** scopes in *environment.ts* file. (Angular applications already have this configuration). +## About localization +We don't localize any error messages in the OpenIddict module, Because the OAuth 2.0 specification restricts the charset you're allowed to use for the error and error_description parameters: + +> A.7. "error" Syntax +> The "error" element is defined in Sections 4.1.2.1, 4.2.2.1, 5.2, 7.2, and 8.5: + +``` +error = 1*NQSCHAR +``` + +> A.8. "error_description" Syntax +>T he "error_description" element is defined in Sections 4.1.2.1, 4.2.2.1, 5.2, and 7.2: + +``` +error-description = 1*NQSCHAR +NQSCHAR = %x20-21 / %x23-5B / %x5D-7E +``` ## Demo projects diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs index f5ce191f20..2930303efd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Components/LayoutHooks/LayoutHook.razor.cs @@ -25,8 +25,7 @@ public partial class LayoutHook : ComponentBase if (LayoutHookOptions.Value.Hooks.TryGetValue(Name, out var layoutHooks)) { layoutHooks = layoutHooks - .Where(IsComponentBase) - .WhereIf(string.IsNullOrWhiteSpace(Layout), x => x.Layout == Layout) + .Where(x => IsComponentBase(x) && (string.IsNullOrWhiteSpace(x.Layout) || x.Layout == Layout)) .ToList(); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs index 082ca59ee0..0ab4235c59 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHookViewComponent.cs @@ -16,10 +16,11 @@ public class LayoutHookViewComponent : AbpViewComponent Options = options.Value; } - public virtual IViewComponentResult Invoke(string name, string layout) + public virtual IViewComponentResult Invoke(string name, string? layout) { - var hooks = Options.Hooks.GetOrDefault(name)?.Where(x => x.Layout == layout && IsViewComponent(x)).ToArray() - ?? Array.Empty(); + var hooks = Options.Hooks.GetOrDefault(name)? + .Where(x => IsViewComponent(x) && (string.IsNullOrWhiteSpace(x.Layout) || x.Layout == layout)) + .ToArray() ?? Array.Empty(); return View( "~/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/Default.cshtml", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs index 8d5f2d4c4a..1301a5e823 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs @@ -40,11 +40,11 @@ public class AbpModelMetadataProvider : DefaultModelMetadataProvider { foreach (var validationAttribute in detail.ModelAttributes.Attributes.OfType()) { - NormalizeValidationAttrbute(validationAttribute); + NormalizeValidationAttribute(validationAttribute); } } - protected virtual void NormalizeValidationAttrbute(ValidationAttribute validationAttribute) + protected virtual void NormalizeValidationAttribute(ValidationAttribute validationAttribute) { if (validationAttribute.ErrorMessage == null) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs index 505c07a3e1..cd87c55ea4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs @@ -1,14 +1,15 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.Reflection; namespace Volo.Abp.AspNetCore.Mvc.Validation; public static class ValidationAttributeHelper { - private static readonly PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute) + private readonly static PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute) .GetProperty("ErrorMessageString", BindingFlags.Instance | BindingFlags.NonPublic)!; - private static readonly PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute) + private readonly static PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute) .GetProperty("CustomErrorMessageSet", BindingFlags.Instance | BindingFlags.NonPublic)!; public static void SetDefaultErrorMessage(ValidationAttribute validationAttribute) @@ -24,7 +25,14 @@ public static class ValidationAttributeHelper } } - validationAttribute.ErrorMessage = - ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string; + try + { + var errorMessageString = ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string; + validationAttribute.ErrorMessage = errorMessageString; + } + catch (Exception e) + { + // ignored + } } } diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Microsoft/AspNetCore/Builder/AbpHangfireApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Microsoft/AspNetCore/Builder/AbpHangfireApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..1b49510517 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Microsoft/AspNetCore/Builder/AbpHangfireApplicationBuilderExtensions.cs @@ -0,0 +1,20 @@ +using System; +using Hangfire; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundJobs.Hangfire; + +namespace Microsoft.AspNetCore.Builder; + +public static class AbpHangfireApplicationBuilderExtensions +{ + public static IApplicationBuilder UseAbpHangfireDashboard( + this IApplicationBuilder app, + string pathMatch = "/hangfire", + Action? configure = null, + JobStorage? storage = null) + { + var options = app.ApplicationServices.GetRequiredService().Get(); + configure?.Invoke(options); + return app.UseHangfireDashboard(pathMatch, options, storage); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs index bc41b21ce8..e68489290a 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Threading; using Hangfire; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -18,8 +19,16 @@ public class AbpDashboardOptionsProvider : ITransientDependency { return new DashboardOptions { - DisplayNameFunc = (dashboardContext, job) => - AbpBackgroundJobOptions.GetJob(job.Args.First().GetType()).JobName + DisplayNameFunc = (_, job) => + { + var jobName = job.ToString(); + if (job.Args.Count == 3 && job.Args.Last() is CancellationToken) + { + jobName = AbpBackgroundJobOptions.GetJob(job.Args[1].GetType()).JobName; + } + + return jobName; + } }; } } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 8602792701..3791c4202f 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -132,7 +132,7 @@ public abstract class AbstractKeyReadOnlyAppService ((IHasCreationTime)e).CreationTime); } - throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); + throw new AbpException("No sorting specified but this query requires sorting. Override the ApplySorting or the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); } /// diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs index 9f0ff3f35b..4092de5b3b 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs @@ -1,3 +1,4 @@ +using Volo.Abp.Data; using Volo.Abp.EventBus.Distributed; namespace Volo.Abp.EntityFrameworkCore.DistributedEvents; @@ -8,5 +9,6 @@ public static class EfCoreInboxConfigExtensions where TDbContext : IHasEventInbox { outboxConfig.ImplementationType = typeof(IDbContextEventInbox); + outboxConfig.DatabaseName = ConnectionStringNameAttribute.GetConnStringName(); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs index 2071a832e8..5da25e2d14 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs @@ -1,4 +1,5 @@ -using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Data; +using Volo.Abp.EventBus.Distributed; namespace Volo.Abp.EntityFrameworkCore.DistributedEvents; @@ -8,5 +9,6 @@ public static class EfCoreOutboxConfigExtensions where TDbContext : IHasEventOutbox { outboxConfig.ImplementationType = typeof(IDbContextEventOutbox); + outboxConfig.DatabaseName = ConnectionStringNameAttribute.GetConnStringName(); } } diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxConfig.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxConfig.cs index b2d90e707e..94e0e55be9 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxConfig.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/InboxConfig.cs @@ -8,6 +8,13 @@ public class InboxConfig [NotNull] public string Name { get; } + [NotNull] + public string DatabaseName { + get => _databaseName; + set => _databaseName = Check.NotNullOrWhiteSpace(value, nameof(DatabaseName)); + } + [NotNull] private string _databaseName; + public Type ImplementationType { get; set; } public Func EventSelector { get; set; } diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutboxConfig.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutboxConfig.cs index d526195ba3..0b327c0e3b 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutboxConfig.cs +++ b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutboxConfig.cs @@ -7,6 +7,13 @@ public class OutboxConfig { [NotNull] public string Name { get; } + + [NotNull] + public string DatabaseName { + get => _databaseName; + set => _databaseName = Check.NotNullOrWhiteSpace(value, nameof(DatabaseName)); + } + [NotNull] private string _databaseName; public Type ImplementationType { get; set; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index 1b4f0e4deb..14e80bd9c6 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -27,7 +27,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency protected DateTime? LastCleanTime { get; set; } - protected string DistributedLockName => "AbpInbox_" + InboxConfig.Name; + protected string DistributedLockName { get; private set; } public ILogger Logger { get; set; } protected CancellationTokenSource StoppingTokenSource { get; } protected CancellationToken StoppingToken { get; } @@ -64,6 +64,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency { InboxConfig = inboxConfig; Inbox = (IEventInbox)ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + DistributedLockName = $"AbpInbox_{InboxConfig.DatabaseName}"; Timer.Start(cancellationToken); return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 3f7e2dfd73..5f34761b17 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -22,7 +22,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency protected IEventOutbox Outbox { get; private set; } protected OutboxConfig OutboxConfig { get; private set; } protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } - protected string DistributedLockName => "AbpOutbox_" + OutboxConfig.Name; + protected string DistributedLockName { get; private set; } public ILogger Logger { get; set; } protected CancellationTokenSource StoppingTokenSource { get; } @@ -51,6 +51,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency { OutboxConfig = outboxConfig; Outbox = (IEventOutbox)ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); + DistributedLockName = $"AbpOutbox_{OutboxConfig.DatabaseName}"; Timer.Start(cancellationToken); return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs index 9c54fe840e..f7b9b838b5 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyApiDescriptionFinder.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.FileProviders.Physical; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; using Volo.Abp.Json; @@ -92,15 +94,35 @@ public class ClientProxyApiDescriptionFinder : IClientProxyApiDescriptionFinder, { if (directoryContent.IsDirectory) { - GetGenerateProxyFileInfos(fileInfoList, directoryContent.PhysicalPath); + GetGenerateProxyFileInfos(fileInfoList, GetDirectoryContentPath(path, directoryContent)); } else { if (directoryContent.Name.EndsWith("generate-proxy.json")) { - fileInfoList.Add(VirtualFileProvider.GetFileInfo(directoryContent.GetVirtualOrPhysicalPathOrNull())); + fileInfoList.Add(VirtualFileProvider.GetFileInfo(GetProxyFileInfoPath(path, directoryContent))); } } } } + + 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(); + } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs index 8b3f17c58b..9d16d9b730 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs @@ -1,3 +1,4 @@ +using Volo.Abp.Data; using Volo.Abp.EventBus.Distributed; namespace Volo.Abp.MongoDB.DistributedEvents; @@ -8,5 +9,6 @@ public static class MongoDbInboxConfigExtensions where TMongoDbContext : IHasEventInbox { outboxConfig.ImplementationType = typeof(IMongoDbContextEventInbox); + outboxConfig.DatabaseName = ConnectionStringNameAttribute.GetConnStringName(); } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs index 6b91fec861..3914c1380d 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs @@ -1,4 +1,5 @@ -using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Data; +using Volo.Abp.EventBus.Distributed; namespace Volo.Abp.MongoDB.DistributedEvents; @@ -8,5 +9,6 @@ public static class MongoDbOutboxConfigExtensions where TMongoDbContext : IHasEventOutbox { outboxConfig.ImplementationType = typeof(IMongoDbContextEventOutbox); + outboxConfig.DatabaseName = ConnectionStringNameAttribute.GetConnStringName(); } } diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js index 6edbe815be..70bf976bc4 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js @@ -28,7 +28,16 @@ var abp = abp || {}; } // Intercept .well-known request when the discoveryEndpoint is provided if (!firstRequest && oidcDiscoveryEndpoint.length !== 0 && request.url.includes(".well-known/openid-configuration")) { - request.url = oidcDiscoveryEndpoint; + if (oidcDiscoveryEndpoint.endsWith(".well-known/openid-configuration")) { + request.url = oidcDiscoveryEndpoint; + console.log(request.url); + return; + } + if (!oidcDiscoveryEndpoint.endsWith("/")) { + oidcDiscoveryEndpoint += "/" + } + request.url = oidcDiscoveryEndpoint + ".well-known/openid-configuration"; + console.log(request.url); } var antiForgeryToken = abp.security.antiForgery.getToken(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.cshtml index e24b1dd5af..9ac8ee9462 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.cshtml @@ -1,11 +1,8 @@ @page -@using System.Globalization @using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Utils @using Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers @using Volo.Abp.Users -@using Volo.Blogging @using Volo.Blogging.Areas.Blog.Helpers.TagHelpers @using Volo.Blogging.Localization @model Volo.Blogging.Pages.Members.IndexModel diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.css b/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.css index 668e566cba..3a2c2675c5 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.css +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Members/Index.css @@ -1,6 +1,3 @@ .post-desc { overflow-wrap: break-word; } -a:not(#all-posts-tab,#edit-profile-tab) { - color: unset!important; -} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 737bc2fe36..4898baf215 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -6,6 +6,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Localization; @@ -108,6 +109,12 @@ namespace Volo.Docs.Pages.Documents.Project public virtual async Task OnGetAsync() { + var displayUrl = Request.GetDisplayUrl(); + var decodedUrl = HttpUtility.UrlDecode(displayUrl); + if (decodedUrl != displayUrl) + { + return Redirect(decodedUrl); + } try { return await SetPageAsync(); diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html index 2d95ede2d7..581a5bd002 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html @@ -1,4 +1,4 @@ - +

@@ -8,7 +8,7 @@
-
+
-
-
+
+ +
{ if (typeof status !== 'number') return false; - return ( - !!this.httpErrorConfig.skipHandledErrorCodes && - this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0 - ); + if (!this.httpErrorConfig.skipHandledErrorCodes) { + return true; + } + + return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0; }; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts index 14a4e79266..9235204b99 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts @@ -20,9 +20,12 @@ export class RouterErrorHandlerService { } protected filterRouteErrors = (navigationError: NavigationError): boolean => { + if (!this.httpErrorConfig.skipHandledErrorCodes) { + return true; + } + return ( navigationError.error?.message?.indexOf('Cannot match') > -1 && - !!this.httpErrorConfig.skipHandledErrorCodes && this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 ); }; diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 1756c8002e..96d61dc74d 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -212,6 +212,7 @@ $projects = ( "framework/src/Volo.Abp.Ldap", "framework/src/Volo.Abp.Localization.Abstractions", "framework/src/Volo.Abp.MailKit", + "framework/src/Volo.Abp.Maui.Client", "framework/src/Volo.Abp.Localization", "framework/src/Volo.Abp.MemoryDb", "framework/src/Volo.Abp.MongoDB",