diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs index 78ae28e63f..be84495336 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs @@ -28,38 +28,46 @@ namespace Volo.Abp.AspNetCore.Auditing public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - if (!ShouldWriteAuditLog(context)) - { - await next(context); - return; - } - + bool hasError = false; using (var scope = _auditingManager.BeginScope()) { try { await next(context); } + catch (Exception) + { + hasError = true; + throw; + } finally { - await scope.SaveAsync(); + if (ShouldWriteAuditLog(context, hasError)) + { + await scope.SaveAsync(); + } } } } - private bool ShouldWriteAuditLog(HttpContext httpContext) + private bool ShouldWriteAuditLog(HttpContext httpContext, bool hasError = false) { if (!Options.IsEnabled) { return false; } + if (Options.AlwaysLogOnException && hasError) + { + return true; + } + if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) { return false; } - if (!Options.IsEnabledForGetRequests && + if (!Options.IsEnabledForGetRequests && string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) { return false; diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs index da406fa490..db13bdaf74 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs @@ -30,6 +30,12 @@ namespace Volo.Abp.Auditing /// Default: true. /// public bool IsEnabledForAnonymousUsers { get; set; } + + /// + /// Audit log on exceptions. + /// Default: true. + /// + public bool AlwaysLogOnException { get; set; } public List Contributors { get; } @@ -48,6 +54,7 @@ namespace Volo.Abp.Auditing IsEnabled = true; IsEnabledForAnonymousUsers = true; HideErrors = true; + AlwaysLogOnException = true; Contributors = new List(); diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs index 070684f48a..2a9dd8bf5a 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs @@ -23,8 +23,8 @@ namespace Volo.Abp.Auditing private readonly IAuditingStore _auditingStore; public AuditingManager( - IAmbientScopeProvider ambientScopeProvider, - IAuditingHelper auditingHelper, + IAmbientScopeProvider ambientScopeProvider, + IAuditingHelper auditingHelper, IAuditingStore auditingStore, IServiceProvider serviceProvider, IOptions options) @@ -84,7 +84,7 @@ namespace Volo.Abp.Auditing { var changeGroups = auditLog.EntityChanges .Where(e => e.ChangeType == EntityChangeType.Updated) - .GroupBy(e => new {e.EntityTypeFullName, e.EntityId}) + .GroupBy(e => new { e.EntityTypeFullName, e.EntityId }) .ToList(); foreach (var changeGroup in changeGroups) @@ -141,7 +141,7 @@ namespace Volo.Abp.Auditing public DisposableSaveHandle( AuditingManager auditingManager, IDisposable scope, - AuditLogInfo auditLog, + AuditLogInfo auditLog, Stopwatch stopWatch) { _auditingManager = auditingManager; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs new file mode 100644 index 0000000000..9b4892cea4 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.Auditing; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + [Route("api/audit-test")] + [Audited] + public class AuditTestController : AbpController + { + private readonly AbpAuditingOptions _options; + + public AuditTestController(IOptions options) + { + _options = options.Value; + } + + [Route("audit-success")] + public IActionResult AuditSuccessForGetRequests() + { + return Ok(); + } + + [Route("audit-fail")] + public IActionResult AuditFailForGetRequests() + { + throw new UserFriendlyException("Exception occurred!"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs new file mode 100644 index 0000000000..733fab3f49 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using NSubstitute; +using System.Threading.Tasks; +using Volo.Abp.Auditing; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Auditing +{ + public class AuditTestController_Tests : AspNetCoreMvcTestBase + { + private readonly AbpAuditingOptions _options; + private IAuditingStore _auditingStore; + + public AuditTestController_Tests() + { + _options = ServiceProvider.GetRequiredService>().Value; + _auditingStore = ServiceProvider.GetRequiredService(); + } + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.Replace(ServiceDescriptor.Singleton(_auditingStore)); + base.ConfigureServices(context, services); + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() + { + _options.IsEnabledForGetRequests = true; + _options.AlwaysLogOnException = false; + await GetResponseAsync("api/audit-test/audit-success"); + await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + } + + [Fact] + public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() + { + _options.IsEnabled = true; + _options.AlwaysLogOnException = true; + + try + { + await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.Forbidden); + } + catch { } + + await _auditingStore.Received().SaveAsync(Arg.Any()); //Won't work, save happens out of scope + } + } +} diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts index 56d0874ebd..8fd11bd2cb 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts @@ -1,4 +1,5 @@ import { ABP } from '../models/common'; +import { Config } from '../models/config'; export class PatchRouteByName { static readonly type = '[Config] Patch Route By Name'; @@ -16,3 +17,8 @@ export class AddRoute { static readonly type = '[Config] Add Route'; constructor(public payload: Omit) {} } + +export class SetEnvironment { + static readonly type = '[Config] Set Environment'; + constructor(public environment: Config.Environment) {} +} diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index 506b278634..cf97e42b0f 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -1,8 +1,12 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngxs/store'; +import { + AddRoute, + GetAppConfiguration, + PatchRouteByName, + SetEnvironment, +} from '../actions/config.actions'; import { ConfigState } from '../states'; -import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; -import { ABP } from '../models'; @Injectable({ providedIn: 'root', @@ -61,4 +65,8 @@ export class ConfigStateService { dispatchAddRoute(...args: ConstructorParameters) { return this.store.dispatch(new AddRoute(...args)); } + + dispatchSetEnvironment(...args: ConstructorParameters) { + return this.store.dispatch(new SetEnvironment(...args)); + } } diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index debbc9b8a0..ff964cfe92 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -2,7 +2,12 @@ import { Action, createSelector, Selector, State, StateContext, Store } from '@n import { of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; -import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; +import { + GetAppConfiguration, + PatchRouteByName, + AddRoute, + SetEnvironment, +} from '../actions/config.actions'; import { SetLanguage } from '../actions/session.actions'; import { ABP } from '../models/common'; import { Config } from '../models/config'; @@ -291,6 +296,13 @@ export class ConfigState { flattedRoutes, }); } + + @Action(SetEnvironment) + setEnvironment({ patchState }: StateContext, environment: Config.Environment) { + return patchState({ + environment, + }); + } } function patchRouteDeep( diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts index f359518099..231f627fa1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts @@ -1,16 +1,20 @@ import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; import { TableSortDirective } from '../directives/table-sort.directive'; +import { TableComponent } from '../components/table/table.component'; +import { DummyLocalizationPipe } from './table.component.spec'; +import { PaginationComponent } from '../components'; describe('TableSortDirective', () => { let spectator: SpectatorDirective; let directive: TableSortDirective; const createDirective = createDirectiveFactory({ directive: TableSortDirective, + declarations: [TableComponent, DummyLocalizationPipe, PaginationComponent], }); beforeEach(() => { spectator = createDirective( - ``, + ``, ); directive = spectator.directive; }); @@ -21,7 +25,7 @@ describe('TableSortDirective', () => { test('should change table value', () => { expect(directive.value).toEqual([1, 4, 2]); - const table = spectator.query(Table); + const table = spectator.query(TableComponent); expect(table.value).toEqual([1, 2, 4]); }); }); diff --git a/npm/preview-publish.ps1 b/npm/preview-publish.ps1 index f10173226b..d448378750 100644 --- a/npm/preview-publish.ps1 +++ b/npm/preview-publish.ps1 @@ -4,7 +4,7 @@ param( npm install -$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) +$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) + '-1' $rootFolder = (Get-Item -Path "./" -Verbose).FullName if(-Not $Version) { @@ -16,7 +16,6 @@ $commands = ( "npm install", "npm run publish-packages -- --nextVersion $Version --preview", "cd ../../", - "yarn", "yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish --dist-tag preview" ) diff --git a/npm/publish.ps1 b/npm/publish.ps1 index 69f9ece982..a510527401 100644 --- a/npm/publish.ps1 +++ b/npm/publish.ps1 @@ -16,7 +16,6 @@ $commands = ( "npm install", "npm run publish-packages -- --nextVersion $Version", "cd ../../", - "yarn", "yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish", "yarn update:templates", "yarn gulp:app",