From 772fd97080bcfc5909d9a958158ec538cb1e39ea Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Tue, 2 Aug 2016 17:29:39 +0100 Subject: [PATCH] Implement initial command permissions system After our initial discussion on the matter (see #172) this is the system that we all seem to have agreed on. As a result, I have implemented a simple system which effectively implements permissions, while being extensible and tweakable so bot devs can decide what they want to do for permissions. As for default 'permissions', I'm not sure what the best approach would be here; bot devs are likely to implement their own permissions 'levels' and use those. I think the most we could do for now is add attributes to require certain users (by id) and certain roles (by id and possibly by name?) This would probably be the best option for now as it requires less work from us, nor do we know the *exact* approach bot devs want to take with permissions. --- .../Attributes/FilterAttribute.cs | 12 +++++++++ src/Discord.Net.Commands/Command.cs | 15 +++++++++++ src/Discord.Net.Commands/CommandError.cs | 1 + .../Context/CommandExecutionContext.cs | 25 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 src/Discord.Net.Commands/Attributes/FilterAttribute.cs create mode 100644 src/Discord.Net.Commands/Context/CommandExecutionContext.cs diff --git a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs new file mode 100644 index 000000000..6e1cfff1c --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public abstract class FilterAttribute : Attribute + { + public abstract void OnCommandExecuting(CommandExecutionContext context); + } +} diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 5729e4c81..cd17b6916 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,6 +19,7 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } + public IReadOnlyList Filters { get; } internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { @@ -37,6 +38,7 @@ namespace Discord.Commands Synopsis = synopsis.Text; Parameters = BuildParameters(methodInfo); + Filters = BuildFilters(methodInfo); _action = BuildAction(methodInfo); } @@ -52,6 +54,14 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); + var context = new CommandExecutionContext(this, parseResult, msg); + foreach (FilterAttribute filter in Filters) + { + filter.OnCommandExecuting(context); + if (context.Handled) + return ExecuteResult.FromError(CommandError.InvalidPermissions, $"Permission check for {filter.GetType().FullName} failed"); + } + try { await _action.Invoke(msg, parseResult.Values);//Note: This code may need context @@ -63,6 +73,11 @@ namespace Discord.Commands } } + private IReadOnlyList BuildFilters(MethodInfo methodInfo) + { + return methodInfo.GetCustomAttributes().ToImmutableArray(); + } + private IReadOnlyList BuildParameters(MethodInfo methodInfo) { var parameters = methodInfo.GetParameters(); diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index 135930dd9..c90d4be50 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -16,5 +16,6 @@ //Execute Exception, + InvalidPermissions } } diff --git a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs new file mode 100644 index 000000000..bec8291cc --- /dev/null +++ b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public class CommandExecutionContext + { + public Command ExecutingCommand { get; internal set; } + public ParseResult ParseResult { get; internal set; } + public IMessage Message { get; internal set; } + + public bool Handled { get; set; } + + internal CommandExecutionContext(Command command, ParseResult parseResult, IMessage message) + { + ExecutingCommand = command; + ParseResult = parseResult; + Message = message; + + Handled = false; + } + } +}