Permissions package, the following + canRun command

keep-around/d31701866686f66088b78de2e29736ae36e55a68
Pingex aka Raphaël 9 years ago
parent 38ccb6e9f6
commit 5a3ac763eb

@ -1,49 +1,62 @@
package net.pingex.dcf.permissions; package net.pingex.dcf.permissions;
import net.pingex.dcf.core.Configuration; 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; import java.util.function.Predicate;
/** /**
* Default behavior when a permissions provider doesn't return `true` or `false` * Default behavior when a permissions provider doesn't return `true` or `false`
*/ */
public enum DefaultPermission implements Predicate<IMessage> public enum DefaultPermission implements Predicate<DefaultPermission.Tuple>
{ {
/** /**
* Everyone is allowed to run the command. * Everyone is allowed to run the command.
*/ */
EVERYONE(iMessage -> true), EVERYONE(tuple -> true),
/** /**
* Only the guild owner is allowed to run the command. * 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. * 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 * 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 * Nobody is allowed to run the command
*/ */
NONE(iMessage -> false); NONE(tuple -> false);
DefaultPermission(Predicate<IMessage> predicate) DefaultPermission(Predicate<Tuple> predicate)
{ {
this.predicate = predicate; this.predicate = predicate;
} }
private Predicate<IMessage> predicate; private Predicate<Tuple> predicate;
@Override @Override
public boolean test(IMessage o) public boolean test(Tuple o)
{ {
return predicate.test(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;
}
}
} }

@ -7,11 +7,6 @@ import sx.blah.discord.handle.obj.IUser;
/** /**
* Provider of permissions, ie. the proxy between {@code PermissionsHandler} and a data storage. * 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 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. * @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); 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);
} }

@ -4,7 +4,10 @@ import net.pingex.dcf.commands.Command;
import net.pingex.dcf.commands.CommandRegistry; import net.pingex.dcf.commands.CommandRegistry;
import net.pingex.dcf.commands.IWithCommands; import net.pingex.dcf.commands.IWithCommands;
import net.pingex.dcf.util.DiscordInteractionsUtil; 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.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 sx.blah.discord.handle.obj.IUser;
import java.util.*; import java.util.*;
@ -40,6 +43,15 @@ public class PermissionsCommands implements IWithCommands
Command target = null; Command target = null;
IUser userToLookup = null; IUser userToLookup = null;
boolean verbose = false; boolean verbose = false;
IGuild targetGuild = null;
ICommandPermissionsProvider provider = PermissionsHandler.getProvider();
try
{
targetGuild = event.getMessage().getGuild();
}
catch(UnsupportedOperationException ignored) // DM NOP
{}
// Argchk size // Argchk size
if(arguments.size() != 2 && arguments.size() != 3) 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";
} }
} }

@ -6,9 +6,6 @@ import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IRole; import sx.blah.discord.handle.obj.IRole;
import sx.blah.discord.handle.obj.IUser; import sx.blah.discord.handle.obj.IUser;
import java.util.Collections;
import java.util.List;
/** /**
* Landing class of this package. * Landing class of this package.
*/ */
@ -16,54 +13,71 @@ public class PermissionsHandler
{ {
private static final ICommandPermissionsProvider CURRENT_PROVIDER = new DefaultPermissionsProvider(); 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. * Tells whether someone can run requested command.
* *
* The validator will successively test the following until something other than `null` is returned. * 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()} * - Individual user permission globally using {@code ICommandPermissionsProvider.validateUser()}
* - The user's role list using {@code ICommandPermissionsProvider.validateGroup()} * - The user's role list using {@code ICommandPermissionsProvider.validateGroup()}
* - Default behavior included in a Command object * - 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} * @param command Requested command, as parsed by {@code CommandHandler}
* @return `true` if request is granted, `false` if denied. * @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) public static boolean canRunCommand(IMessage request, Command command)
{ {
IUser requestAuthor = request.getAuthor(); IUser requestAuthor = request.getAuthor();
IGuild originatedGuild; IGuild originatedGuild;
List<IRole> rolesForUser;
try try
{ {
originatedGuild = request.getGuild(); originatedGuild = request.getGuild();
rolesForUser = requestAuthor.getRolesForGuild(originatedGuild);
} }
catch(UnsupportedOperationException uoe) // DM catch(UnsupportedOperationException uoe) // DM
{ {
originatedGuild = null; 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;
} }
return canRunCommand(requestAuthor, originatedGuild, command);
// 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);
} }
} }