From 374d699fd98f17427b226c3bb2df18a7dff69042 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 1 May 2024 12:07:06 +0200 Subject: [PATCH] Move audit log endpoints to their respective silos and clean up (#16170) * Move audit log endpoints to their respective silos and clean up * Fix failing integration tests --------- Co-authored-by: Mads Rasmussen --- .../AuditLog/AuditLogControllerBase.cs | 10 - .../AuditLog/ByKeyAuditLogController.cs | 43 -- .../AuditLog/ByTypeAuditLogController.cs | 39 -- .../AuditLog/CurrentUserAuditLogController.cs | 55 -- .../Document/GetAuditLogDocumentController.cs | 60 +++ .../Media/GetAuditLogMediaController.cs | 59 +++ .../Factories/AuditLogPresentationFactory.cs | 46 +- .../Factories/IAuditLogPresentationFactory.cs | 4 +- src/Umbraco.Cms.Api.Management/OpenApi.json | 471 ++++++------------ .../AuditLogResponseModel.cs} | 6 +- .../ViewModels/AuditLogs/AuditLogEntity.cs | 8 - .../AuditLogs/AuditLogResponseModel.cs | 8 - .../AuditLogWithUsernameResponseModel.cs | 11 - src/Umbraco.Core/Services/AuditService.cs | 65 ++- src/Umbraco.Core/Services/IAuditService.cs | 12 +- ...rTests.cs => AllCultureControllerTests.cs} | 10 +- 16 files changed, 349 insertions(+), 558 deletions(-) delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Document/GetAuditLogDocumentController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Media/GetAuditLogMediaController.cs rename src/Umbraco.Cms.Api.Management/ViewModels/{AuditLogs/AuditLogBaseModel.cs => AuditLog/AuditLogResponseModel.cs} (66%) delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogEntity.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs rename tests/Umbraco.Tests.Integration/ManagementApi/Policies/{ByKeyAuditLogControllerTests.cs => AllCultureControllerTests.cs} (73%) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs deleted file mode 100644 index 08d6b36d4c..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.Routing; - -namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; - -[VersionedApiBackOfficeRoute("audit-log")] -[ApiExplorerSettings(GroupName = "Audit Log")] -public class AuditLogControllerBase : ManagementApiControllerBase -{ -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs deleted file mode 100644 index ca8e7c32a3..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.Common.Authorization; - -namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; - -[ApiVersion("1.0")] -[Authorize(Policy = AuthorizationPolicies.SectionAccessContentOrMedia)] -public class ByKeyAuditLogController : AuditLogControllerBase -{ - private readonly IAuditService _auditService; - private readonly IAuditLogPresentationFactory _auditLogPresentationFactory; - - public ByKeyAuditLogController(IAuditService auditService, IAuditLogPresentationFactory auditLogPresentationFactory) - { - _auditService = auditService; - _auditLogPresentationFactory = auditLogPresentationFactory; - } - - [HttpGet("{id:guid}")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task ByKey(CancellationToken cancellationToken, Guid id, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) - { - PagedModel result = await _auditService.GetItemsByKeyAsync(id, skip, take, orderDirection, sinceDate); - IEnumerable mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items); - var viewModel = new PagedViewModel - { - Total = result.Total, - Items = mapped, - }; - - return Ok(viewModel); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs deleted file mode 100644 index d2dd392bba..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; - -[ApiVersion("1.0")] -public class ByTypeAuditLogController : AuditLogControllerBase -{ - private readonly IAuditService _auditService; - private readonly IAuditLogPresentationFactory _auditLogPresentationFactory; - - public ByTypeAuditLogController(IAuditService auditService, IAuditLogPresentationFactory auditLogPresentationFactory) - { - _auditService = auditService; - _auditLogPresentationFactory = auditLogPresentationFactory; - } - - [HttpGet("type/{logType}")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task ByType(CancellationToken cancellationToken, AuditType logType, DateTime? sinceDate = null, int skip = 0, int take = 100) - { - IAuditItem[] result = _auditService.GetLogs(logType, sinceDate).ToArray(); - IEnumerable mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Skip(skip).Take(take)); - var viewModel = new PagedViewModel - { - Total = result.Length, - Items = mapped, - }; - - return await Task.FromResult(Ok(viewModel)); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs deleted file mode 100644 index 62a75981bc..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; - -[ApiVersion("1.0")] -public class CurrentUserAuditLogController : AuditLogControllerBase -{ - private readonly IAuditService _auditService; - private readonly IAuditLogPresentationFactory _auditLogPresentationFactory; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - - public CurrentUserAuditLogController( - IAuditService auditService, - IAuditLogPresentationFactory auditLogPresentationFactory, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor) - { - _auditService = auditService; - _auditLogPresentationFactory = auditLogPresentationFactory; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - } - - [HttpGet] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task CurrentUser(CancellationToken cancellationToken, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) - { - IUser user = CurrentUser(_backOfficeSecurityAccessor); - PagedModel result = await _auditService.GetPagedItemsByUserAsync( - user.Key, - skip, - take, - orderDirection, - null, - sinceDate); - - IEnumerable mapped = _auditLogPresentationFactory.CreateAuditLogWithUsernameViewModels(result.Items); - var viewModel = new PagedViewModel - { - Total = result.Total, - Items = mapped, - }; - - return Ok(viewModel); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/GetAuditLogDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/GetAuditLogDocumentController.cs new file mode 100644 index 0000000000..206d2d2252 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/GetAuditLogDocumentController.cs @@ -0,0 +1,60 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.AuditLog; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security.Authorization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +[ApiVersion("1.0")] +public class GetAuditLogDocumentController : DocumentControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IAuditService _auditService; + private readonly IAuditLogPresentationFactory _auditLogPresentationFactory; + + public GetAuditLogDocumentController( + IAuthorizationService authorizationService, + IAuditService auditService, + IAuditLogPresentationFactory auditLogPresentationFactory) + { + _authorizationService = authorizationService; + _auditService = auditService; + _auditLogPresentationFactory = auditLogPresentationFactory; + } + + [MapToApiVersion("1.0")] + [HttpGet("{id:guid}/audit-log")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task GetAuditLog(CancellationToken cancellationToken, Guid id, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + ContentPermissionResource.WithKeys(ActionProtect.ActionLetter, id), + AuthorizationPolicies.ContentPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + PagedModel result = await _auditService.GetItemsByKeyAsync(id, UmbracoObjectTypes.Document, skip, take, orderDirection, sinceDate); + IEnumerable mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items); + var viewModel = new PagedViewModel + { + Total = result.Total, + Items = mapped, + }; + + return Ok(viewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/GetAuditLogMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/GetAuditLogMediaController.cs new file mode 100644 index 0000000000..445bf86b9f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/GetAuditLogMediaController.cs @@ -0,0 +1,59 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.AuditLog; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security.Authorization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +[ApiVersion("1.0")] +public class GetAuditLogMediaController : MediaControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IAuditService _auditService; + private readonly IAuditLogPresentationFactory _auditLogPresentationFactory; + + public GetAuditLogMediaController( + IAuthorizationService authorizationService, + IAuditService auditService, + IAuditLogPresentationFactory auditLogPresentationFactory) + { + _authorizationService = authorizationService; + _auditService = auditService; + _auditLogPresentationFactory = auditLogPresentationFactory; + } + + [MapToApiVersion("1.0")] + [HttpGet("{id:guid}/audit-log")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task GetAuditLog(CancellationToken cancellationToken, Guid id, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + MediaPermissionResource.WithKeys(id), + AuthorizationPolicies.MediaPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + PagedModel result = await _auditService.GetItemsByKeyAsync(id, UmbracoObjectTypes.Media, skip, take, orderDirection, sinceDate); + IEnumerable mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items); + var viewModel = new PagedViewModel + { + Total = result.Total, + Items = mapped, + }; + + return Ok(viewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/AuditLogPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/AuditLogPresentationFactory.cs index 6bde8ac27a..d000a00255 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/AuditLogPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/AuditLogPresentationFactory.cs @@ -1,10 +1,6 @@ using Umbraco.Cms.Api.Management.ViewModels; -using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Api.Management.ViewModels.AuditLog; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; @@ -13,57 +9,25 @@ namespace Umbraco.Cms.Api.Management.Factories; public class AuditLogPresentationFactory : IAuditLogPresentationFactory { private readonly IUserService _userService; - private readonly AppCaches _appCaches; - private readonly MediaFileManager _mediaFileManager; - private readonly IImageUrlGenerator _imageUrlGenerator; - private readonly IEntityService _entityService; private readonly IUserIdKeyResolver _userIdKeyResolver; - public AuditLogPresentationFactory(IUserService userService, AppCaches appCaches, MediaFileManager mediaFileManager, IImageUrlGenerator imageUrlGenerator, IEntityService entityService, IUserIdKeyResolver userIdKeyResolver) + public AuditLogPresentationFactory(IUserService userService, IUserIdKeyResolver userIdKeyResolver) { _userService = userService; - _appCaches = appCaches; - _mediaFileManager = mediaFileManager; - _imageUrlGenerator = imageUrlGenerator; - _entityService = entityService; _userIdKeyResolver = userIdKeyResolver; } public IEnumerable CreateAuditLogViewModel(IEnumerable auditItems) => auditItems.Select(CreateAuditLogViewModel); - public IEnumerable CreateAuditLogWithUsernameViewModels(IEnumerable auditItems) => auditItems.Select(CreateAuditLogWithUsernameViewModel); - - private AuditLogWithUsernameResponseModel CreateAuditLogWithUsernameViewModel(IAuditItem auditItem) - { - AuditLogWithUsernameResponseModel target = CreateResponseModel(auditItem, out IUser user); - - target.UserAvatars = user.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); - target.UserName = user.Name; - return target; - } - private AuditLogResponseModel CreateAuditLogViewModel(IAuditItem auditItem) - => CreateResponseModel(auditItem, out _); - - private T CreateResponseModel(IAuditItem auditItem, out IUser user) - where T : AuditLogBaseModel, new() { Guid userKey = _userIdKeyResolver.GetAsync(auditItem.UserId).GetAwaiter().GetResult(); - user = _userService.GetAsync(userKey).GetAwaiter().GetResult() - ?? throw new ArgumentException($"Could not find user with id {auditItem.UserId}"); + IUser user = _userService.GetAsync(userKey).GetAwaiter().GetResult() + ?? throw new ArgumentException($"Could not find user with id {auditItem.UserId}"); - IEntitySlim? entitySlim = _entityService.Get(auditItem.Id); - - return new T + return new AuditLogResponseModel { Comment = auditItem.Comment, - Entity = auditItem.EntityType is not null || entitySlim is not null - ? new AuditLogEntity - { - Id = entitySlim?.Key, - Type = auditItem.EntityType - } - : null, LogType = auditItem.AuditType, Parameters = auditItem.Parameters, Timestamp = auditItem.CreateDate, diff --git a/src/Umbraco.Cms.Api.Management/Factories/IAuditLogPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IAuditLogPresentationFactory.cs index 2e540b2c32..8b2f4713dd 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IAuditLogPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IAuditLogPresentationFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Api.Management.ViewModels.AuditLog; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Api.Management.Factories; @@ -6,6 +6,4 @@ namespace Umbraco.Cms.Api.Management.Factories; public interface IAuditLogPresentationFactory { IEnumerable CreateAuditLogViewModel(IEnumerable auditItems); - - IEnumerable CreateAuditLogWithUsernameViewModels(IEnumerable auditItems); } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index ef6fbdd47e..9272bd6c6d 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -6,220 +6,6 @@ "version": "Latest" }, "paths": { - "/umbraco/management/api/v1/audit-log": { - "get": { - "tags": [ - "Audit Log" - ], - "operationId": "GetAuditLog", - "parameters": [ - { - "name": "orderDirection", - "in": "query", - "schema": { - "$ref": "#/components/schemas/DirectionModel" - } - }, - { - "name": "sinceDate", - "in": "query", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 0 - } - }, - { - "name": "take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 100 - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PagedAuditLogWithUsernameResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/audit-log/{id}": { - "get": { - "tags": [ - "Audit Log" - ], - "operationId": "GetAuditLogById", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "orderDirection", - "in": "query", - "schema": { - "$ref": "#/components/schemas/DirectionModel" - } - }, - { - "name": "sinceDate", - "in": "query", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 0 - } - }, - { - "name": "take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 100 - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PagedAuditLogResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - }, - "403": { - "description": "The authenticated user do not have access to this resource" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/audit-log/type/{logType}": { - "get": { - "tags": [ - "Audit Log" - ], - "operationId": "GetAuditLogTypeByLogType", - "parameters": [ - { - "name": "logType", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/AuditTypeModel" - } - }, - { - "name": "sinceDate", - "in": "query", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 0 - } - }, - { - "name": "take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 100 - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PagedAuditLogResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, "/umbraco/management/api/v1/culture": { "get": { "tags": [ @@ -7340,6 +7126,85 @@ ] } }, + "/umbraco/management/api/v1/document/{id}/audit-log": { + "get": { + "tags": [ + "Document" + ], + "operationId": "GetDocumentByIdAuditLog", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "orderDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DirectionModel" + } + }, + { + "name": "sinceDate", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PagedAuditLogResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/document/{id}/copy": { "post": { "tags": [ @@ -15276,6 +15141,85 @@ ] } }, + "/umbraco/management/api/v1/media/{id}/audit-log": { + "get": { + "tags": [ + "Media" + ], + "operationId": "GetMediaByIdAuditLog", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "orderDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DirectionModel" + } + }, + { + "name": "sinceDate", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PagedAuditLogResponseModel" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/media/{id}/move": { "put": { "tags": [ @@ -32466,21 +32410,6 @@ }, "additionalProperties": false }, - "AuditLogEntityModel": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid", - "nullable": true - }, - "type": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, "AuditLogResponseModel": { "required": [ "logType", @@ -32496,14 +32425,6 @@ } ] }, - "entity": { - "oneOf": [ - { - "$ref": "#/components/schemas/AuditLogEntityModel" - } - ], - "nullable": true - }, "timestamp": { "type": "string", "format": "date-time" @@ -32522,58 +32443,6 @@ }, "additionalProperties": false }, - "AuditLogWithUsernameResponseModel": { - "required": [ - "logType", - "timestamp", - "user", - "userAvatars" - ], - "type": "object", - "properties": { - "user": { - "oneOf": [ - { - "$ref": "#/components/schemas/ReferenceByIdModel" - } - ] - }, - "entity": { - "oneOf": [ - { - "$ref": "#/components/schemas/AuditLogEntityModel" - } - ], - "nullable": true - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "logType": { - "$ref": "#/components/schemas/AuditTypeModel" - }, - "comment": { - "type": "string", - "nullable": true - }, - "parameters": { - "type": "string", - "nullable": true - }, - "userName": { - "type": "string", - "nullable": true - }, - "userAvatars": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, "AuditTypeModel": { "enum": [ "New", @@ -39067,30 +38936,6 @@ }, "additionalProperties": false }, - "PagedAuditLogWithUsernameResponseModel": { - "required": [ - "items", - "total" - ], - "type": "object", - "properties": { - "total": { - "type": "integer", - "format": "int64" - }, - "items": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/AuditLogWithUsernameResponseModel" - } - ] - } - } - }, - "additionalProperties": false - }, "PagedCultureReponseModel": { "required": [ "items", diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLog/AuditLogResponseModel.cs similarity index 66% rename from src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/AuditLog/AuditLogResponseModel.cs index 77060c9107..d17c4609df 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLog/AuditLogResponseModel.cs @@ -1,13 +1,11 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +namespace Umbraco.Cms.Api.Management.ViewModels.AuditLog; -public class AuditLogBaseModel +public class AuditLogResponseModel { public ReferenceByIdModel User { get; set; } = new(); - public AuditLogEntity? Entity { get; set; } - public DateTimeOffset Timestamp { get; set; } public AuditType LogType { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogEntity.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogEntity.cs deleted file mode 100644 index 0d010dd0bd..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogEntity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; - -public class AuditLogEntity -{ - public Guid? Id { get; set; } - - public string? Type { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs deleted file mode 100644 index 7b0f43b4e1..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; - -/// -public class AuditLogResponseModel : AuditLogBaseModel -{ -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs deleted file mode 100644 index f5d940ea72..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; - -public class AuditLogWithUsernameResponseModel : AuditLogBaseModel -{ - public string? UserName { get; set; } - - public string[] UserAvatars { get; set; } = Array.Empty(); -} diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index 39951d6bd7..0281066dbf 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -2,16 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Exceptions; -using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement; @@ -19,10 +14,11 @@ public sealed class AuditService : RepositoryService, IAuditService { private readonly IAuditEntryRepository _auditEntryRepository; private readonly IUserService _userService; - private readonly IEntityRepository _entityRepository; private readonly IAuditRepository _auditRepository; + private readonly IEntityService _entityService; private readonly Lazy _isAvailable; + [Obsolete("Use the non-obsolete constructor. Will be removed in V15.")] public AuditService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, @@ -31,12 +27,54 @@ public sealed class AuditService : RepositoryService, IAuditService IAuditEntryRepository auditEntryRepository, IUserService userService, IEntityRepository entityRepository) + : this( + provider, + loggerFactory, + eventMessagesFactory, + auditRepository, + auditEntryRepository, + userService, + StaticServiceProvider.Instance.GetRequiredService() + ) + { + } + + [Obsolete("Use the non-obsolete constructor. Will be removed in V15.")] + public AuditService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IAuditRepository auditRepository, + IAuditEntryRepository auditEntryRepository, + IUserService userService, + IEntityRepository entityRepository, + IEntityService entityService) + : this( + provider, + loggerFactory, + eventMessagesFactory, + auditRepository, + auditEntryRepository, + userService, + entityService + ) + { + } + + public AuditService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IAuditRepository auditRepository, + IAuditEntryRepository auditEntryRepository, + IUserService userService, + IEntityService entityService) : base(provider, loggerFactory, eventMessagesFactory) { _auditRepository = auditRepository; _auditEntryRepository = auditEntryRepository; _userService = userService; - _entityRepository = entityRepository; + _entityService = entityService; _isAvailable = new Lazy(DetermineIsAvailable); } @@ -200,6 +238,7 @@ public sealed class AuditService : RepositoryService, IAuditService public async Task> GetItemsByKeyAsync( Guid entityKey, + UmbracoObjectTypes entityType, int skip, int take, Direction orderDirection = Direction.Descending, @@ -216,16 +255,20 @@ public sealed class AuditService : RepositoryService, IAuditService throw new ArgumentOutOfRangeException(nameof(take)); } + Attempt keyToIdAttempt = _entityService.GetId(entityKey, entityType); + if (keyToIdAttempt.Success is false) + { + return await Task.FromResult(new PagedModel { Items = Enumerable.Empty(), Total = 0 }); + } + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - IUser user = await _userService.GetRequiredUserAsync(entityKey); - - IQuery query = Query().Where(x => x.UserId == user.Id); + IQuery query = Query().Where(x => x.Id == keyToIdAttempt.Result); IQuery? customFilter = sinceDate.HasValue ? Query().Where(x => x.CreateDate >= sinceDate) : null; PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); IEnumerable auditItems = _auditRepository.GetPagedResultsByQuery(query, pageNumber, pageSize, out var totalRecords, orderDirection, auditTypeFilter, customFilter); - return await Task.FromResult(new PagedModel { Items = auditItems, Total = totalRecords }); + return new PagedModel { Items = auditItems, Total = totalRecords }; } } diff --git a/src/Umbraco.Core/Services/IAuditService.cs b/src/Umbraco.Core/Services/IAuditService.cs index 9bbad6f76c..b80fdb3b3e 100644 --- a/src/Umbraco.Core/Services/IAuditService.cs +++ b/src/Umbraco.Core/Services/IAuditService.cs @@ -1,6 +1,5 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -76,12 +75,12 @@ public interface IAuditService : IService IQuery? customFilter = null); /// - /// Returns paged items in the audit trail for a given user + /// Returns paged items in the audit trail for a given entity /// - /// The key of the user - /// The amount of entries to skip - /// The amount of entiries to take - /// The total amount of entires + /// The key of the entity + /// The entity type + /// The amount of audit trail entries to skip + /// The amount of audit trail entries to take /// /// By default this will always be ordered descending (newest first) /// @@ -96,6 +95,7 @@ public interface IAuditService : IService /// Task> GetItemsByKeyAsync( Guid entityKey, + UmbracoObjectTypes entityType, int skip, int take, Direction orderDirection = Direction.Descending, diff --git a/tests/Umbraco.Tests.Integration/ManagementApi/Policies/ByKeyAuditLogControllerTests.cs b/tests/Umbraco.Tests.Integration/ManagementApi/Policies/AllCultureControllerTests.cs similarity index 73% rename from tests/Umbraco.Tests.Integration/ManagementApi/Policies/ByKeyAuditLogControllerTests.cs rename to tests/Umbraco.Tests.Integration/ManagementApi/Policies/AllCultureControllerTests.cs index 1973d400e7..e041b425bb 100644 --- a/tests/Umbraco.Tests.Integration/ManagementApi/Policies/ByKeyAuditLogControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/ManagementApi/Policies/AllCultureControllerTests.cs @@ -1,9 +1,7 @@ using System.Linq.Expressions; using System.Net; -using System.Net.Http.Headers; using NUnit.Framework; -using Umbraco.Cms.Api.Management.Controllers.AuditLog; -using Umbraco.Cms.Core; +using Umbraco.Cms.Api.Management.Controllers.Culture; namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies; @@ -11,10 +9,10 @@ namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies; /// /// [TestFixture] -public class ByKeyAuditLogControllerTests : ManagementApiTest +public class AllCultureControllerTests : ManagementApiTest { - protected override Expression> MethodSelector => - x => x.ByKey(CancellationToken.None, Constants.Security.SuperUserKey, Direction.Ascending, null, 0, 100); + protected override Expression> MethodSelector => + x => x.GetAll(CancellationToken.None, 0, 100); [Test] public virtual async Task As_Admin_I_Have_Access()