public static ModerationTask <T> Withdraw(string tid) { var preRet = Get(tid); var hs = ModerationHelper.Setup <T>(); var allowedActions = hs.ModerationActions; var can = allowedActions.Moderate || preRet.AuthorLocator == App.Current.Orchestrator.Person?.Locator; if (!can) { throw new AccessViolationException("Unauthorized: User isn't Author or Moderator."); } preRet.Remove(); new ModerationLog <T> { SourceId = ((Data <T>)preRet.Entry).GetDataKey(), Entry = preRet.Entry, Action = preRet.Action, AuthorLocator = preRet.AuthorLocator, ModeratorLocator = App.Current.Orchestrator.Person?.Locator, Result = States.EResult.Withdrawn }.Save(); Base.Current.Log.Info <T>($"MODERATION TASK WITHDRAWN: {tid}"); return(preRet); }
public static ModerationTask <T> Reject(string tid) { var hs = ModerationHelper.Setup <T>(); var allowedActions = hs.ModerationActions; if (!allowedActions.Moderate) { throw new AccessViolationException("Unauthorized: User isn't Moderator."); } var preRet = Get(tid); if (hs.Setup.StatusModelMember != null) { var oProbe = Data <T> .Get(preRet.SourceId); if (oProbe != null) { if (!oProbe.SetMemberValue(hs.Setup.StatusModelMember, States.ResultLabel.Rejected)) { Base.Current.Log.Warn <T>($"Could NOT change moderation status field {hs.Setup.StatusModelMember} on [{preRet.SourceId}]"); } else { oProbe.Save(); } } } preRet.Remove(); new ModerationLog <T> { SourceId = ((Data <T>)preRet.Entry).GetDataKey(), Entry = preRet.Entry, Action = preRet.Action, AuthorLocator = preRet.AuthorLocator, ModeratorLocator = App.Current.Orchestrator.Person?.Locator, Result = States.EResult.Rejected }.Save(); Base.Current.Log.Info <T>($"MODERATION TASK REJECTED: {tid}"); return(preRet); }
public static ModerationTask <T> Approve(string tid) { var hs = ModerationHelper.Setup <T>(); var allowedActions = hs.ModerationActions; if (!allowedActions.Moderate) { throw new AccessViolationException("Unauthorized: User isn't Moderator."); } var preRet = Get(tid); var oProbe = (Data <T>)preRet.Entry; if (hs.Setup.StatusModelMember != null) { if (!oProbe.SetMemberValue(hs.Setup.StatusModelMember, States.ResultLabel.Approved)) { Base.Current.Log.Warn <T>($"Could NOT change moderation status field {hs.Setup.StatusModelMember} on [{preRet.SourceId}]"); } else if (hs.Setup.ActivityModelMember != null) { oProbe.SetMemberValue(hs.Setup.ActivityModelMember, true); } } oProbe.Save(); preRet.Remove(); new ModerationLog <T> { SourceId = oProbe.GetDataKey(), Entry = oProbe, Action = preRet.Action, Result = States.EResult.Approved }.Save(); Base.Current.Log.Info <T>($"MODERATION TASK ACCEPTED: {tid} "); return(preRet); }
public virtual Dictionary <string, object> Headers <T>(ref DataAccessControl accessControl, Dictionary <string, StringValues> requestHeaders, EActionScope scope, T model) where T : Data <T> { var moderationSetup = ModerationHelper.Setup <T>(); var ctx = new Dictionary <string, object> { { "moderated", true } }; var customModerationPipeline = moderationSetup.CustomModerationPipeline; var allowedActions = moderationSetup.ModerationActions; if (customModerationPipeline != null && scope == EActionScope.Model) { allowedActions = customModerationPipeline.GetModerationActions(EActionType.Read, scope, null, model, null) ?? allowedActions; } if (allowedActions.Moderate) { ctx.Add("moderator", true); } if (allowedActions.Whitelisted) { ctx.Add("whiteListed", true); } if (allowedActions.Author) { ctx.Add("canPost", true); } if (customModerationPipeline != null) { var headerPayloadDictionary = new Dictionary <string, List <string> >(); var onInsert = customModerationPipeline.OnInsertAbstracts(); if (onInsert?.Any() == true) { headerPayloadDictionary.Add("add", onInsert); } var onUpdate = customModerationPipeline.OnUpdateAbstracts(); if (onUpdate?.Any() == true) { headerPayloadDictionary.Add("edit", onUpdate); } var onRemove = customModerationPipeline.OnRemoveAbstracts(); if (onRemove?.Any() == true) { headerPayloadDictionary.Add("del", onRemove); } if (headerPayloadDictionary.Any()) { ctx.Add("abstract", headerPayloadDictionary); } } var canShow = allowedActions.Read; ctx.Add("canShow", canShow); if (canShow) { var path = Current.Context.Request.Path.ToUriComponent(); path = path.Remove(path.Length - 1); if (path.Contains("moderation/task")) { path = path.Replace("moderation/task", ""); } ctx.Add("baseUrl", path); } var ret = new Dictionary <string, object> { { "x-zen-moderation", ctx } }; return(ret); }
public T Process <T>(EActionType type, EActionScope scope, Mutator mutator, T current, T source) where T : Data <T> { // This is the main Moderation control flow. Let's start by setting up some helper classes: var moderationSetup = ModerationHelper.Setup <T>(); var customModerationPipeline = moderationSetup.CustomModerationPipeline; var allowedActions = customModerationPipeline?.GetModerationActions(type, scope, mutator, current, source) ?? moderationSetup.ModerationActions; var currentPerson = App.Current.Orchestrator.Person?.Locator; //Log.Info<T>($"MOD {type} | {currentPerson}"); //Log.Info<T>($"MOD {type} | {allowedActions.ToJson()}"); //Log.Info<T>($"MOD {type} | {current.ToReference()}"); var currentKey = current.GetDataKey(); // What can this user do? // Before anything, check if the target entry is marked as Imported. In that case the whole Moderation pipeline is sidestepped. var importModelMember = moderationSetup.Setup.ImportModelMember; if (importModelMember != null) { if (!current.HasMember(importModelMember)) { var msg = $"Moderation Pipeline: Invalid configuration - missing [{importModelMember}] ImportModelMember"; Base.Current.Log.Warn <T>(msg); throw new InvalidOperationException(msg); } switch (type) { case EActionType.Insert: var status = current.GetMemberValue <bool>(importModelMember); if (status) { return(current); // Imported record, nothing to be done here. } break; case EActionType.Update: var newStatus = current.GetMemberValue <bool>(importModelMember); var oldStatus = source.GetMemberValue <bool>(importModelMember); if (source != null) { if (newStatus != oldStatus) { throw new ArgumentException($"Moderation Pipeline: Import flag [{importModelMember}] cannot be changed."); } } if (newStatus) { return(current); } break; } } // Let's determine the Creator of this record. string creatorLocator = null; var creatorLocatorModelMember = moderationSetup.Setup.CreatorLocatorModelMember; if (creatorLocatorModelMember != null) { if (!current.HasMember(creatorLocatorModelMember)) { var msg = $"Moderation Pipeline: Invalid configuration - missing [{creatorLocatorModelMember}] CreatorLocatorModelMember"; Base.Current.Log.Warn <T>(msg); throw new InvalidOperationException(msg); } creatorLocator = current.GetMemberValue <string>(creatorLocatorModelMember); } var isCreator = creatorLocator == currentPerson; string oRationale = null; // There are two kinds of Moderation action that this pipeline can handle. One is a Moderation Task.... var controlPayload = ModerationHelper.GetModerationControlPayload(); if (controlPayload != null) { // This is a Moderation Task operation - say, like an User trying to delete their own entry, // or a Mod changing things. We can use some extra data to help with the process. var oTaskId = controlPayload?.___ModerationTaskId; oRationale = controlPayload?.___ModerationRationale; if (oTaskId != null) { var oTask = ModerationTask <T> .Get(oTaskId); switch (type) { case EActionType.Insert: case EActionType.Update: // Let's change the payload to the new content. var _os = oTask.Entry.ToJson(); var _ns = current.ToJson(); if (_ns != _os) { oTask.Entry = current; oTask.Save(); new ModerationLog <T> { SourceId = currentKey, Entry = current, Action = type.ToString() }.Save(); Base.Current.Log.Info <T>($"Moderation Pipeline: PATCH {oTaskId}"); } // Moderator/Whitelisted? Accept it. if (allowedActions.Moderate || allowedActions.Whitelisted || moderationSetup.Setup.CreatorCanWithdraw && isCreator) { ModerationTask <T> .Approve(oTaskId); } break; case EActionType.Remove: if (allowedActions.Moderate || allowedActions.Whitelisted) { ModerationTask <T> .Withdraw(oTaskId); } else { ModerationTask <T> .Reject(oTaskId); } break; } } } // The second kind is regular models posted by users. Let's determine their fate: var mustCreateTask = false; var clearModel = false; if (!(allowedActions.Author || allowedActions.Whitelisted)) { throw new InvalidOperationException("Moderation Pipeline: Not authorized."); } switch (type) { case EActionType.Insert: case EActionType.Update: if (!(allowedActions.Moderate || allowedActions.Whitelisted)) { mustCreateTask = true; } if (allowedActions.Whitelisted) // User is Whitelisted { if (type == EActionType.Update) // If it's an update, { if (!isCreator) // Whitelisted user isn't the creator { mustCreateTask = true; } } } break; case EActionType.Remove: if (allowedActions.Moderate) { break; } if (allowedActions.Whitelisted && isCreator) { break; } if (moderationSetup.Setup.CreatorCanWithdraw && isCreator) { break; } mustCreateTask = true; break; default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } if (mustCreateTask) // { var statusModelMember = moderationSetup.Setup.StatusModelMember; if (statusModelMember != null) { if (current.HasMember(statusModelMember)) { if (!current.SetMemberValue(statusModelMember, null)) // Mark as 'Waiting Moderation'. { Base.Current.Log.Warn <T>($"Could NOT change moderation status on [{statusModelMember}] | {type}, entry {{{currentKey}}}"); } } } var moderationTask = new ModerationTask <T> { SourceId = currentKey, Entry = current, Action = type.ToString(), AuthorLocator = currentPerson, Rationale = oRationale }; moderationTask.Save(); clearModel = true; } new ModerationLog <T> { SourceId = currentKey, Entry = current, Action = type.ToString(), AuthorLocator = currentPerson, Result = mustCreateTask ? States.EResult.TaskCreated : States.EResult.Approved }.Save(); if (moderationSetup.Setup.NotifyChanges) { var ePer = new Email(); var emailContent = "A new entry has been submitted.<br/><br/>" + "Entry: " + current.ToReference() + "<br/>" + "Creator: " + App.Current.Orchestrator.Person + "<br/><br/>"; ePer.AddTo(App.Current.Orchestrator.Application.GetGroup("CUR")); ePer.AddCc(App.Current.Orchestrator.Application.GetGroup("MOD")); ePer.Title = $"{currentPerson}: {type} {Info<T>.Settings.FriendlyName}"; emailContent += !mustCreateTask ? "User was whitelisted: Action was automatically approved." : "Action was submitted for moderation."; ePer.Content = emailContent; ePer.Send(); } if (clearModel) { current = null; } return(current); }