using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement { internal class PublicAccessService : RepositoryService, IPublicAccessService { private readonly IPublicAccessRepository _publicAccessRepository; public PublicAccessService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IPublicAccessRepository publicAccessRepository) : base(provider, loggerFactory, eventMessagesFactory) { _publicAccessRepository = publicAccessRepository; } /// /// Gets all defined entries and associated rules /// /// public IEnumerable GetAll() { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { return _publicAccessRepository.GetMany(); } } /// /// Gets the entry defined for the content item's path /// /// /// Returns null if no entry is found public PublicAccessEntry GetEntryForContent(IContent content) { return GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id)); } /// /// Gets the entry defined for the content item based on a content path /// /// /// Returns null if no entry is found /// /// NOTE: This method get's called *very* often! This will return the results from cache /// public PublicAccessEntry GetEntryForContent(string contentPath) { //Get all ids in the path for the content item and ensure they all // parse to ints that are not -1. var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => int.TryParse(x, out int val) ? val : -1) .Where(x => x != -1) .ToList(); //start with the deepest id ids.Reverse(); using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { //This will retrieve from cache! var entries = _publicAccessRepository.GetMany().ToList(); foreach (var id in ids) { var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); if (found != null) return found; } } return null; } /// /// Returns true if the content has an entry for it's path /// /// /// public Attempt IsProtected(IContent content) { var result = GetEntryForContent(content); return Attempt.If(result != null, result); } /// /// Returns true if the content has an entry based on a content path /// /// /// public Attempt IsProtected(string contentPath) { var result = GetEntryForContent(contentPath); return Attempt.If(result != null, result); } /// /// Adds a rule /// /// /// /// /// public Attempt> AddRule(IContent content, string ruleType, string ruleValue) { var evtMsgs = EventMessagesFactory.Get(); PublicAccessEntry entry; using (var scope = ScopeProvider.CreateScope()) { entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); if (entry == null) return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); if (existingRule == null) { entry.AddRule(ruleValue, ruleType); } else { //If they are both the same already then there's nothing to update, exit return OperationResult.Attempt.Succeed(evtMsgs, entry); } var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs, entry); } _publicAccessRepository.Save(entry); scope.Complete(); scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs, entry); } /// /// Removes a rule /// /// /// /// public Attempt RemoveRule(IContent content, string ruleType, string ruleValue) { var evtMsgs = EventMessagesFactory.Get(); PublicAccessEntry entry; using (var scope = ScopeProvider.CreateScope()) { entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); if (entry == null) return Attempt.Fail(); // causes rollback // causes rollback var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); if (existingRule == null) return Attempt.Fail(); // causes rollback // causes rollback entry.RemoveRule(existingRule); var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); } _publicAccessRepository.Save(entry); scope.Complete(); scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); } /// /// Saves the entry /// /// public Attempt Save(PublicAccessEntry entry) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) { var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); } _publicAccessRepository.Save(entry); scope.Complete(); scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); } /// /// Deletes the entry and all associated rules /// /// public Attempt Delete(PublicAccessEntry entry) { var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) { var deletingNotification = new PublicAccessEntryDeletingNotification(entry, evtMsgs); if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); } _publicAccessRepository.Delete(entry); scope.Complete(); scope.Notifications.Publish(new PublicAccessEntryDeletedNotification(entry, evtMsgs).WithStateFrom(deletingNotification)); } return OperationResult.Attempt.Succeed(evtMsgs); } } }