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 <madsr@hey.com>
This commit is contained in:
Kenn Jacobsen
2024-05-01 12:07:06 +02:00
committed by GitHub
parent c8180d508b
commit 374d699fd9
16 changed files with 349 additions and 558 deletions

View File

@@ -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
{
}

View File

@@ -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<AuditLogResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100)
{
PagedModel<IAuditItem> result = await _auditService.GetItemsByKeyAsync(id, skip, take, orderDirection, sinceDate);
IEnumerable<AuditLogResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items);
var viewModel = new PagedViewModel<AuditLogResponseModel>
{
Total = result.Total,
Items = mapped,
};
return Ok(viewModel);
}
}

View File

@@ -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<AuditLogResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> ByType(CancellationToken cancellationToken, AuditType logType, DateTime? sinceDate = null, int skip = 0, int take = 100)
{
IAuditItem[] result = _auditService.GetLogs(logType, sinceDate).ToArray();
IEnumerable<AuditLogResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Skip(skip).Take(take));
var viewModel = new PagedViewModel<AuditLogResponseModel>
{
Total = result.Length,
Items = mapped,
};
return await Task.FromResult(Ok(viewModel));
}
}

View File

@@ -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<AuditLogWithUsernameResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> CurrentUser(CancellationToken cancellationToken, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100)
{
IUser user = CurrentUser(_backOfficeSecurityAccessor);
PagedModel<IAuditItem> result = await _auditService.GetPagedItemsByUserAsync(
user.Key,
skip,
take,
orderDirection,
null,
sinceDate);
IEnumerable<AuditLogWithUsernameResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogWithUsernameViewModels(result.Items);
var viewModel = new PagedViewModel<AuditLogWithUsernameResponseModel>
{
Total = result.Total,
Items = mapped,
};
return Ok(viewModel);
}
}

View File

@@ -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<AuditLogResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IAuditItem> result = await _auditService.GetItemsByKeyAsync(id, UmbracoObjectTypes.Document, skip, take, orderDirection, sinceDate);
IEnumerable<AuditLogResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items);
var viewModel = new PagedViewModel<AuditLogResponseModel>
{
Total = result.Total,
Items = mapped,
};
return Ok(viewModel);
}
}

View File

@@ -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<AuditLogResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IAuditItem> result = await _auditService.GetItemsByKeyAsync(id, UmbracoObjectTypes.Media, skip, take, orderDirection, sinceDate);
IEnumerable<AuditLogResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items);
var viewModel = new PagedViewModel<AuditLogResponseModel>
{
Total = result.Total,
Items = mapped,
};
return Ok(viewModel);
}
}

View File

@@ -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<AuditLogResponseModel> CreateAuditLogViewModel(IEnumerable<IAuditItem> auditItems) => auditItems.Select(CreateAuditLogViewModel);
public IEnumerable<AuditLogWithUsernameResponseModel> CreateAuditLogWithUsernameViewModels(IEnumerable<IAuditItem> auditItems) => auditItems.Select(CreateAuditLogWithUsernameViewModel);
private AuditLogWithUsernameResponseModel CreateAuditLogWithUsernameViewModel(IAuditItem auditItem)
{
AuditLogWithUsernameResponseModel target = CreateResponseModel<AuditLogWithUsernameResponseModel>(auditItem, out IUser user);
target.UserAvatars = user.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator);
target.UserName = user.Name;
return target;
}
private AuditLogResponseModel CreateAuditLogViewModel(IAuditItem auditItem)
=> CreateResponseModel<AuditLogResponseModel>(auditItem, out _);
private T CreateResponseModel<T>(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,

View File

@@ -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<AuditLogResponseModel> CreateAuditLogViewModel(IEnumerable<IAuditItem> auditItems);
IEnumerable<AuditLogWithUsernameResponseModel> CreateAuditLogWithUsernameViewModels(IEnumerable<IAuditItem> auditItems);
}

View File

@@ -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",

View File

@@ -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; }

View File

@@ -1,8 +0,0 @@
namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs;
public class AuditLogEntity
{
public Guid? Id { get; set; }
public string? Type { get; set; }
}

View File

@@ -1,8 +0,0 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs;
/// <inheritdoc />
public class AuditLogResponseModel : AuditLogBaseModel
{
}

View File

@@ -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<string>();
}

View File

@@ -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<bool> _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<IEntityService>()
)
{
}
[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<bool>(DetermineIsAvailable);
}
@@ -200,6 +238,7 @@ public sealed class AuditService : RepositoryService, IAuditService
public async Task<PagedModel<IAuditItem>> 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<int> keyToIdAttempt = _entityService.GetId(entityKey, entityType);
if (keyToIdAttempt.Success is false)
{
return await Task.FromResult(new PagedModel<IAuditItem> { Items = Enumerable.Empty<IAuditItem>(), Total = 0 });
}
using (ScopeProvider.CreateCoreScope(autoComplete: true))
{
IUser user = await _userService.GetRequiredUserAsync(entityKey);
IQuery<IAuditItem> query = Query<IAuditItem>().Where(x => x.UserId == user.Id);
IQuery<IAuditItem> query = Query<IAuditItem>().Where(x => x.Id == keyToIdAttempt.Result);
IQuery<IAuditItem>? customFilter = sinceDate.HasValue ? Query<IAuditItem>().Where(x => x.CreateDate >= sinceDate) : null;
PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize);
IEnumerable<IAuditItem> auditItems = _auditRepository.GetPagedResultsByQuery(query, pageNumber, pageSize, out var totalRecords, orderDirection, auditTypeFilter, customFilter);
return await Task.FromResult(new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords });
return new PagedModel<IAuditItem> { Items = auditItems, Total = totalRecords };
}
}

View File

@@ -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<IAuditItem>? customFilter = null);
/// <summary>
/// Returns paged items in the audit trail for a given user
/// Returns paged items in the audit trail for a given entity
/// </summary>
/// <param name="entityKey">The key of the user</param>
/// <param name="skip">The amount of entries to skip</param>
/// <param name="take">The amount of entiries to take</param>
/// <param name="totalRecords">The total amount of entires</param>
/// <param name="entityKey">The key of the entity</param>
/// <param name="entityType">The entity type</param>
/// <param name="skip">The amount of audit trail entries to skip</param>
/// <param name="take">The amount of audit trail entries to take</param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
@@ -96,6 +95,7 @@ public interface IAuditService : IService
/// <returns></returns>
Task<PagedModel<IAuditItem>> GetItemsByKeyAsync(
Guid entityKey,
UmbracoObjectTypes entityType,
int skip,
int take,
Direction orderDirection = Direction.Descending,

View File

@@ -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;
///
/// </summary>
[TestFixture]
public class ByKeyAuditLogControllerTests : ManagementApiTest<ByKeyAuditLogController>
public class AllCultureControllerTests : ManagementApiTest<AllCultureController>
{
protected override Expression<Func<ByKeyAuditLogController, object>> MethodSelector =>
x => x.ByKey(CancellationToken.None, Constants.Security.SuperUserKey, Direction.Ascending, null, 0, 100);
protected override Expression<Func<AllCultureController, object>> MethodSelector =>
x => x.GetAll(CancellationToken.None, 0, 100);
[Test]
public virtual async Task As_Admin_I_Have_Access()