Closed otavioferraz closed 1 year ago
After a quick further inspection into the current implementation of the UpdateDocumentAssetCommandHandler, it seems that the deleteOldFileCommand execution is being done without passing through the execution context it was injected by the command handler (Line 71).
An easy fix would be replace the line 71 by the one below:
await _commandExecutor.ExecuteAsync(deleteOldFileCommand, executionContext);
Can this fix be implemented on the next minor release?
Hi @otavioferraz, yes that looks like the issue. I'll see if I can find time to put in a release with the fix soon, but until then you can easily integrate the fix yourself by copying the UpdateDocumentAssetCommandHandler code into your solution, applying the fix and then registering it with the DI container as an override:
public class ExampleOverrideRegistration : IDependencyRegistration
{
public void Register(IContainerRegister container)
{
container.Register<ICommandHandler<UpdateDocumentAssetCommand>, UpdateDocumentAssetCommandHandlerFix>(RegistrationOptions.Override());
}
}
Full code below:
namespace ExampleFix;
using Cofoundry.Core.Data;
using Cofoundry.Core.DependencyInjection;
using Cofoundry.Core.MessageAggregator;
using Cofoundry.Core.Validation;
using Cofoundry.Domain.CQS;
using Cofoundry.Domain.Data;
using Microsoft.EntityFrameworkCore;
public class UpdateDocumentAssetCommandHandlerFix
: ICommandHandler<UpdateDocumentAssetCommand>
, IPermissionRestrictedCommandHandler<UpdateDocumentAssetCommand>
{
private readonly CofoundryDbContext _dbContext;
private readonly EntityAuditHelper _entityAuditHelper;
private readonly EntityTagHelper _entityTagHelper;
private readonly DocumentAssetCommandHelper _documentAssetCommandHelper;
private readonly ITransactionScopeManager _transactionScopeFactory;
private readonly IMessageAggregator _messageAggregator;
private readonly ICommandExecutor _commandExecutor;
public UpdateDocumentAssetCommandHandlerFix(
CofoundryDbContext dbContext,
EntityAuditHelper entityAuditHelper,
EntityTagHelper entityTagHelper,
DocumentAssetCommandHelper documentAssetCommandHelper,
ITransactionScopeManager transactionScopeFactory,
IMessageAggregator messageAggregator,
ICommandExecutor commandExecutor
)
{
_dbContext = dbContext;
_entityAuditHelper = entityAuditHelper;
_entityTagHelper = entityTagHelper;
_documentAssetCommandHelper = documentAssetCommandHelper;
_transactionScopeFactory = transactionScopeFactory;
_messageAggregator = messageAggregator;
_commandExecutor = commandExecutor;
}
public async Task ExecuteAsync(UpdateDocumentAssetCommand command, IExecutionContext executionContext)
{
bool hasNewFile = command.File != null;
var documentAsset = await _dbContext
.DocumentAssets
.Include(a => a.DocumentAssetTags)
.ThenInclude(a => a.Tag)
.FilterById(command.DocumentAssetId)
.SingleOrDefaultAsync();
documentAsset.Title = command.Title;
documentAsset.Description = command.Description ?? string.Empty;
documentAsset.FileName = FilePathHelper.CleanFileName(command.Title);
if (string.IsNullOrWhiteSpace(documentAsset.FileName))
{
throw ValidationErrorException.CreateWithProperties("Document title is empty or does not contain any safe file path characters.", nameof(command.Title));
}
_entityTagHelper.UpdateTags(documentAsset.DocumentAssetTags, command.Tags, executionContext);
_entityAuditHelper.SetUpdated(documentAsset, executionContext);
using (var scope = _transactionScopeFactory.Create(_dbContext))
{
if (hasNewFile)
{
var deleteOldFileCommand = new QueueAssetFileDeletionCommand()
{
EntityDefinitionCode = DocumentAssetEntityDefinition.DefinitionCode,
FileNameOnDisk = documentAsset.FileNameOnDisk,
FileExtension = documentAsset.FileExtension
};
await _commandExecutor.ExecuteAsync(deleteOldFileCommand, executionContext);
await _documentAssetCommandHelper.SaveFile(command.File, documentAsset);
documentAsset.FileUpdateDate = executionContext.ExecutionDate;
}
await _dbContext.SaveChangesAsync();
scope.QueueCompletionTask(() => OnTransactionComplete(documentAsset, hasNewFile));
await scope.CompleteAsync();
}
}
private Task OnTransactionComplete(DocumentAsset documentAsset, bool hasNewFile)
{
return _messageAggregator.PublishAsync(new DocumentAssetUpdatedMessage()
{
DocumentAssetId = documentAsset.DocumentAssetId,
HasFileChanged = hasNewFile
});
}
public IEnumerable<IPermissionApplication> GetPermissions(UpdateDocumentAssetCommand command)
{
yield return new DocumentAssetUpdatePermission();
}
}
public class ExampleOverrideRegistration : IDependencyRegistration
{
public void Register(IContainerRegister container)
{
container.Register<ICommandHandler<UpdateDocumentAssetCommand>, UpdateDocumentAssetCommandHandlerFix>(RegistrationOptions.Override());
}
}
Fixed, will be release in 0.11.4
0.11.4 has now been released with the fix.
Hi there!
I'm trying to implement an immediate background task (through Hangfire) that is called by a PageAdded/PagePublished message handler and generates a Word or Pdf document of a page when it is published. The documents are stored in the document assets repository via execution of AddDocumentAssetCommand or UpdateDocumentAssetCommand (when a document already exists for that page/version in the repository and the page is re-published - in order to maintain only one document per page/version).
The code is working fine with AddDocumentAssetCommand, but is throwing a PermissionValidationFailedException with UpdateDocumentAssetCommand - ImageAssetDeletePermission required. I've tried to execute the code with Elevated Permissions and by creating a System User Execution Context and both fails.
These are the details of the exception:
Cofoundry.Domain.PermissionValidationFailedException Permission Validation Check Failed. Permission Type: Cofoundry.Domain.ImageAssetDeletePermission. UserId:
Cofoundry.Domain.PermissionValidationFailedException: Permission Validation Check Failed. Permission Type: Cofoundry.Domain.ImageAssetDeletePermission. UserId: at Cofoundry.Domain.Internal.PermissionValidationService.EnforcePermission(IPermissionApplication permission, IUserContext userContext) at Cofoundry.Domain.Internal.PermissionValidationService.EnforcePermission(IEnumerable
1 permissions, IUserContext userContext) at Cofoundry.Domain.Internal.ExecutePermissionValidationService.Validate[TCommand](TCommand command, ICommandHandler
1 commandHandler, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteCommandAsync[TCommand](TCommand command, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteCommandAsync[TCommand](TCommand command, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteAsync(ICommand command, IExecutionContext executionContext) at Cofoundry.Domain.Internal.UpdateDocumentAssetCommandHandler.ExecuteAsync(UpdateDocumentAssetCommand command, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteCommandAsync[TCommand](TCommand command, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteCommandAsync[TCommand](TCommand command, IExecutionContext executionContext) at Cofoundry.Domain.CQS.Internal.CommandExecutor.ExecuteAsync(ICommand command, IExecutionContext executionContext) at OtavioFerraz.WebApp.Cofoundry.MessageHandlers.ResumePage.BackgroundTasks.ResumeDocumentGenerateBackgroundTask.AddOrUpdateDocumentAsync(String title, StreamFileSource file) in /Users/otavioferraz/Projects/OtavioFerraz/OtavioFerraz.WebApp/Cofoundry/MessageHandlers/ResumePage/BackgroundTasks/ResumeDocumentGenerateBackgroundTask.cs:line 131 at OtavioFerraz.WebApp.Cofoundry.MessageHandlers.ResumePage.BackgroundTasks.ResumeDocumentGenerateBackgroundTask.GenerateDocAsync(Int32 pageId) in /Users/otavioferraz/Projects/OtavioFerraz/OtavioFerraz.WebApp/Cofoundry/MessageHandlers/ResumePage/BackgroundTasks/ResumeDocumentGenerateBackgroundTask.cs:line 55 at System.Runtime.CompilerServices.TaskAwaiter.GetResult()This is the code for the BackgroundTask:
public async Task GenerateDocAsync(int pageId) { var resumePage = await _domainRepository.ExecuteQueryAsync(new GetResumePageDetailsByIdQuery(pageId)); EntityNotFoundException.ThrowIfNull(resumePage, pageId);
I assume, for some reason, the elevated permissions or system user execution context are not being carried over to the QueueAssetFileDeletionCommand, which requires both ImageAssetDeletePermission and DocumentAssetDeletePermission. Or should it be implemented a different way?
Thanks in advance!