using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { public sealed class AuditService : ScopeRepositoryService, IAuditService { private readonly Lazy _isAvailable; public AuditService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(provider, repositoryFactory, logger, eventMessagesFactory) { _isAvailable = new Lazy(DetermineIsAvailable); } public void Add(AuditType type, string comment, int userId, int objectId) { using (var uow = UowProvider.GetUnitOfWork()) { var repo = RepositoryFactory.CreateAuditRepository(uow); repo.AddOrUpdate(new AuditItem(objectId, comment, type, userId)); uow.Commit(); } } /// /// Returns paged items in the audit trail for a given entity /// /// /// /// /// /// /// By default this will always be ordered descending (newest first) /// /// /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter /// so we need to do that here /// /// /// Optional filter to be applied /// /// public IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery customFilter = null) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); if (entityId == Constants.System.Root || entityId <= 0) { totalRecords = 0; return Enumerable.Empty(); } using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateAuditRepository(uow); var query = Query.Builder.Where(x => x.Id == entityId); return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter); } } /// /// Returns paged items in the audit trail for a given user /// /// /// /// /// /// /// By default this will always be ordered descending (newest first) /// /// /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter /// so we need to do that here /// /// /// Optional filter to be applied /// /// public IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery customFilter = null) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); if (userId < 0) { totalRecords = 0; return Enumerable.Empty(); } using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateAuditRepository(uow); var query = Query.Builder.Where(x => x.UserId == userId); return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter); } } /// public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDate, int affectedUserId, string affectedDetails, string eventType, string eventDetails) { if (performingUserId < 0) throw new ArgumentOutOfRangeException(nameof(performingUserId)); if (string.IsNullOrWhiteSpace(perfomingDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails)); if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType)); if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails)); //validate the eventType - must contain a forward slash, no spaces, no special chars var eventTypeParts = eventType.ToCharArray(); if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false) throw new ArgumentException(nameof(eventType) + " must contain only alphanumeric characters, hyphens and at least one '/' defining a category"); var entry = new AuditEntry { PerformingUserId = performingUserId, PerformingDetails = perfomingDetails, PerformingIp = performingIp, EventDate = eventDate, AffectedUserId = affectedUserId, AffectedDetails = affectedDetails, EventType = eventType, EventDetails = eventDetails }; if (_isAvailable.Value == false) return entry; using (var uow = UowProvider.GetUnitOfWork()) { var repository = RepositoryFactory.CreateAuditEntryRepository(uow); repository.AddOrUpdate(entry); uow.Commit(); } return entry; } /// public IEnumerable Get() { if (_isAvailable.Value == false) return Enumerable.Empty(); using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateAuditEntryRepository(uow); return repository.GetAll(); } } /// public IEnumerable GetPage(long pageIndex, int pageCount, out long records) { if (_isAvailable.Value == false) { records = 0; return Enumerable.Empty(); } using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateAuditEntryRepository(uow); return repository.GetPage(pageIndex, pageCount, out records); } } /// /// Determines whether the repository is available. /// private bool DetermineIsAvailable() { using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) { var repository = RepositoryFactory.CreateAuditEntryRepository(uow); return repository.IsAvailable(); } } } }