From 5a3ac763eb6a8b4cac9253e35be1b055ff519c0a Mon Sep 17 00:00:00 2001 From: Pingex Date: Sat, 10 Sep 2016 20:25:03 +0200 Subject: [PATCH] Permissions package, the following + canRun command --- .../dcf/permissions/DefaultPermission.java | 33 +++++--- .../ICommandPermissionsProvider.java | 22 ++++-- .../dcf/permissions/PermissionsCommands.java | 78 ++++++++++++++++++- .../dcf/permissions/PermissionsHandler.java | 72 ++++++++++------- 4 files changed, 160 insertions(+), 45 deletions(-) diff --git a/src/main/java/net/pingex/dcf/permissions/DefaultPermission.java b/src/main/java/net/pingex/dcf/permissions/DefaultPermission.java index 2432bf8..6c44c6b 100644 --- a/src/main/java/net/pingex/dcf/permissions/DefaultPermission.java +++ b/src/main/java/net/pingex/dcf/permissions/DefaultPermission.java @@ -1,49 +1,62 @@ package net.pingex.dcf.permissions; import net.pingex.dcf.core.Configuration; -import sx.blah.discord.handle.obj.IMessage; +import sx.blah.discord.handle.obj.IGuild; +import sx.blah.discord.handle.obj.IUser; import java.util.function.Predicate; /** * Default behavior when a permissions provider doesn't return `true` or `false` */ -public enum DefaultPermission implements Predicate +public enum DefaultPermission implements Predicate { /** * Everyone is allowed to run the command. */ - EVERYONE(iMessage -> true), + EVERYONE(tuple -> true), /** * Only the guild owner is allowed to run the command. */ - GUILD_OWNER(iMessage -> iMessage.getAuthor().getID().equals(iMessage.getGuild().getOwnerID())), + GUILD_OWNER(tuple -> tuple.guild != null && tuple.user.getID().equals(tuple.guild.getOwnerID())), /** * Only the bot owner is allowed to run the command. */ - BOT_OWNER(iMessage -> iMessage.getAuthor().getID().equals(Configuration.BOT_OWNER)), + BOT_OWNER(tuple -> tuple.user.getID().equals(Configuration.BOT_OWNER)), /** * Guild Owner x Bot Owner */ - ANY_OWNER(iMessage -> GUILD_OWNER.test(iMessage) || BOT_OWNER.test(iMessage)), + ANY_OWNER(tuple -> GUILD_OWNER.test(tuple) || BOT_OWNER.test(tuple)), /** * Nobody is allowed to run the command */ - NONE(iMessage -> false); + NONE(tuple -> false); - DefaultPermission(Predicate predicate) + DefaultPermission(Predicate predicate) { this.predicate = predicate; } - private Predicate predicate; + private Predicate predicate; @Override - public boolean test(IMessage o) + public boolean test(Tuple o) { return predicate.test(o); } + + public static class Tuple + { + IUser user; + IGuild guild; + + public Tuple(IUser user, IGuild guild) + { + this.user = user; + this.guild = guild; + } + } } diff --git a/src/main/java/net/pingex/dcf/permissions/ICommandPermissionsProvider.java b/src/main/java/net/pingex/dcf/permissions/ICommandPermissionsProvider.java index 11f8d8c..59f8af4 100644 --- a/src/main/java/net/pingex/dcf/permissions/ICommandPermissionsProvider.java +++ b/src/main/java/net/pingex/dcf/permissions/ICommandPermissionsProvider.java @@ -7,11 +7,6 @@ import sx.blah.discord.handle.obj.IUser; /** * Provider of permissions, ie. the proxy between {@code PermissionsHandler} and a data storage. - * - * The validator will successively test the following until something other than `null` is returned. - * - Individual user permission using {@code validateUser()} - * - The user's role using {@code validateGroup()} - * - Default behavior */ public interface ICommandPermissionsProvider { @@ -31,4 +26,21 @@ public interface ICommandPermissionsProvider * @return `true` if the role is able to run the command, `false` if he isn't able to, `null` if there is no answer. */ Boolean validateGroup(IRole role, Command command); + + /** + * Set an user's permission to access a command. + * @param guild On which guild does the permission apply ? Set to `null` for global range. + * @param user Target user. + * @param command Target command. + * @param value `null` to remove the key, `false` to deny, `true` to allow. + */ + void setUserPermission(IGuild guild, IUser user, Command command, Boolean value); + + /** + * Set an group's permission to access a command. + * @param role Target role. + * @param command Target command. + * @param value `null` to remove the key, `false` to deny, `true` to allow. + */ + void setGroupPermissions(IRole role, Command command, Boolean value); } diff --git a/src/main/java/net/pingex/dcf/permissions/PermissionsCommands.java b/src/main/java/net/pingex/dcf/permissions/PermissionsCommands.java index 370636e..322f23f 100644 --- a/src/main/java/net/pingex/dcf/permissions/PermissionsCommands.java +++ b/src/main/java/net/pingex/dcf/permissions/PermissionsCommands.java @@ -4,7 +4,10 @@ import net.pingex.dcf.commands.Command; import net.pingex.dcf.commands.CommandRegistry; import net.pingex.dcf.commands.IWithCommands; import net.pingex.dcf.util.DiscordInteractionsUtil; +import org.apache.commons.lang3.StringUtils; import sx.blah.discord.handle.impl.events.MessageReceivedEvent; +import sx.blah.discord.handle.obj.IGuild; +import sx.blah.discord.handle.obj.IRole; import sx.blah.discord.handle.obj.IUser; import java.util.*; @@ -40,6 +43,15 @@ public class PermissionsCommands implements IWithCommands Command target = null; IUser userToLookup = null; boolean verbose = false; + IGuild targetGuild = null; + ICommandPermissionsProvider provider = PermissionsHandler.getProvider(); + + try + { + targetGuild = event.getMessage().getGuild(); + } + catch(UnsupportedOperationException ignored) // DM NOP + {} // Argchk size if(arguments.size() != 2 && arguments.size() != 3) @@ -91,6 +103,70 @@ public class PermissionsCommands implements IWithCommands // ======================================== - DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "STUB: Checking perms for command " + target.getName() + " and user " + userToLookup.getName()); + if(verbose) + { + StringBuilder builder = new StringBuilder() + .append("**Permission walkthrough**\n") + .append("```\n"); + + builder.append("User: ").append(userToLookup.getName()).append("#").append(userToLookup.getDiscriminator()).append("\n"); + builder.append("UID: ").append(userToLookup.getID()).append("\n"); + if(targetGuild != null) + { + builder.append("Guild: ").append(targetGuild.getName()).append("\n"); + builder.append("Guild ID: ").append(targetGuild.getID()).append("\n"); + } + builder.append("Command: ").append(target.getName()).append("\n"); + + builder.append("\n"); + + if(targetGuild != null) + builder.append("UID Guild check: ").append(booleanToString(provider.validateUser(targetGuild, userToLookup, target))).append("\n"); + builder.append("UID Global check: ").append(booleanToString(provider.validateUser(null, userToLookup, target))).append("\n"); + + if(targetGuild != null) + { + builder.append("Role check:\n"); + for(IRole i : targetGuild.getRolesForUser(userToLookup)) + builder.append("> ").append(StringUtils.rightPad(i.getName() + ":", 16)) + .append(booleanToString(provider.validateGroup(i, target))).append("\n"); + } + + builder.append("Default behavior: ") + .append(booleanToString(target.getDefaultPermission().test(new DefaultPermission.Tuple(userToLookup, targetGuild)))) + .append(" (").append(target.getDefaultPermission().toString()).append(")").append("\n"); + + builder.append("```"); + DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), builder.toString()); + } + else + { + StringBuilder builder = new StringBuilder() + .append("**Permission summary**\n") + .append("```\n"); + + builder.append("User: ").append(userToLookup.getName()).append("#").append(userToLookup.getDiscriminator()).append("\n"); + builder.append("UID: ").append(userToLookup.getID()).append("\n"); + if(targetGuild != null) + { + builder.append("Guild: ").append(targetGuild.getName()).append("\n"); + builder.append("Guild ID: ").append(targetGuild.getID()).append("\n"); + } + builder.append("Command: ").append(target.getName()).append("\n"); + builder.append("Has Access: ").append(PermissionsHandler.canRunCommand(userToLookup, targetGuild, target) ? "Yes" : "No").append("\n"); + + builder.append("```"); + DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), builder.toString()); + } + } + + /** + * Quick helper function to convert a bool to a String. + * @param input Input boolean, can be `null` + * @return "Grant" => `true`, "Deny" => `false`, "N/A" => `null` + */ + private static String booleanToString(Boolean input) + { + return input != null ? (input ? "Grant" : "Deny") : "N/A"; } } diff --git a/src/main/java/net/pingex/dcf/permissions/PermissionsHandler.java b/src/main/java/net/pingex/dcf/permissions/PermissionsHandler.java index df456e4..30762c8 100644 --- a/src/main/java/net/pingex/dcf/permissions/PermissionsHandler.java +++ b/src/main/java/net/pingex/dcf/permissions/PermissionsHandler.java @@ -6,9 +6,6 @@ import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IRole; import sx.blah.discord.handle.obj.IUser; -import java.util.Collections; -import java.util.List; - /** * Landing class of this package. */ @@ -16,54 +13,71 @@ public class PermissionsHandler { private static final ICommandPermissionsProvider CURRENT_PROVIDER = new DefaultPermissionsProvider(); + /** + * Get provider used to check permissions. + */ + public static ICommandPermissionsProvider getProvider() + { + return CURRENT_PROVIDER; + } + /** * Tells whether someone can run requested command. * * The validator will successively test the following until something other than `null` is returned. - * - Individual user permission on the called guild using {@code ICommandPermissionsProvider.validateUser()} + * - Individual user permission on the guild using {@code ICommandPermissionsProvider.validateUser()} * - Individual user permission globally using {@code ICommandPermissionsProvider.validateUser()} * - The user's role list using {@code ICommandPermissionsProvider.validateGroup()} * - Default behavior included in a Command object * - * @param request Request message, includes author, guild, and so on. + * @param targetUser User to check against + * @param targetGuild Guild to check against * @param command Requested command, as parsed by {@code CommandHandler} * @return `true` if request is granted, `false` if denied. */ + public static boolean canRunCommand(IUser targetUser, IGuild targetGuild, Command command) + { + // First check: user permission for guild, skipped if DM + if(targetGuild != null) + { + Boolean canUserGuildRun = CURRENT_PROVIDER.validateUser(targetGuild, targetUser, command); + if(canUserGuildRun != null) return canUserGuildRun; + } + + // Second check: user permission globally + Boolean canUserRun = CURRENT_PROVIDER.validateUser(null, targetUser, command); + if(canUserRun != null) return canUserRun; + + // Third check: user role permissions, skipped if DM + if(targetGuild != null && targetGuild.getRolesForUser(targetUser) != null) + for(IRole i : targetGuild.getRolesForUser(targetUser)) + { + Boolean canRoleRun = CURRENT_PROVIDER.validateGroup(i, command); + if(canRoleRun != null) return canRoleRun; + } + + // Fourth check: default behavior + return command.getDefaultPermission().test(new DefaultPermission.Tuple(targetUser, targetGuild)); + } + + /** + * Shortcut to the other {@code canRunCommand()} method. + * @param request Incoming message + * @param command Command to check against + * @return `true` if request is granted, `false` if denied. + */ public static boolean canRunCommand(IMessage request, Command command) { IUser requestAuthor = request.getAuthor(); IGuild originatedGuild; - List rolesForUser; try { originatedGuild = request.getGuild(); - rolesForUser = requestAuthor.getRolesForGuild(originatedGuild); } catch(UnsupportedOperationException uoe) // DM { originatedGuild = null; - rolesForUser = Collections.emptyList(); - } - - // First check: user permission for guild, skipped if DM - if(originatedGuild != null) - { - Boolean canUserGuildRun = CURRENT_PROVIDER.validateUser(originatedGuild, requestAuthor, command); - if(canUserGuildRun != null) return canUserGuildRun; } - - // Second check: user permission globally - Boolean canUserRun = CURRENT_PROVIDER.validateUser(null, requestAuthor, command); - if(canUserRun != null) return canUserRun; - - // Third check: user role permissions, auto-skipped if DM - for(IRole i : rolesForUser) - { - Boolean canRoleRun = CURRENT_PROVIDER.validateGroup(i, command); - if(canRoleRun != null) return canRoleRun; - } - - // Fourth check: default behavior - return command.getDefaultPermission().test(request); + return canRunCommand(requestAuthor, originatedGuild, command); } }