Permissions framework + stub DB
parent
073d2455cb
commit
38ccb6e9f6
@ -0,0 +1,49 @@
|
||||
package net.pingex.dcf.permissions;
|
||||
|
||||
import net.pingex.dcf.core.Configuration;
|
||||
import sx.blah.discord.handle.obj.IMessage;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Default behavior when a permissions provider doesn't return `true` or `false`
|
||||
*/
|
||||
public enum DefaultPermission implements Predicate<IMessage>
|
||||
{
|
||||
/**
|
||||
* Everyone is allowed to run the command.
|
||||
*/
|
||||
EVERYONE(iMessage -> true),
|
||||
|
||||
/**
|
||||
* Only the guild owner is allowed to run the command.
|
||||
*/
|
||||
GUILD_OWNER(iMessage -> iMessage.getAuthor().getID().equals(iMessage.getGuild().getOwnerID())),
|
||||
|
||||
/**
|
||||
* Only the bot owner is allowed to run the command.
|
||||
*/
|
||||
BOT_OWNER(iMessage -> iMessage.getAuthor().getID().equals(Configuration.BOT_OWNER)),
|
||||
|
||||
/**
|
||||
* Guild Owner x Bot Owner
|
||||
*/
|
||||
ANY_OWNER(iMessage -> GUILD_OWNER.test(iMessage) || BOT_OWNER.test(iMessage)),
|
||||
|
||||
/**
|
||||
* Nobody is allowed to run the command
|
||||
*/
|
||||
NONE(iMessage -> false);
|
||||
|
||||
DefaultPermission(Predicate<IMessage> predicate)
|
||||
{
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
private Predicate<IMessage> predicate;
|
||||
|
||||
@Override
|
||||
public boolean test(IMessage o)
|
||||
{
|
||||
return predicate.test(o);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.pingex.dcf.permissions;
|
||||
|
||||
import net.pingex.dcf.commands.Command;
|
||||
import sx.blah.discord.handle.obj.IGuild;
|
||||
import sx.blah.discord.handle.obj.IRole;
|
||||
import sx.blah.discord.handle.obj.IUser;
|
||||
|
||||
/**
|
||||
* The one and only permissions provider for now.
|
||||
*/
|
||||
public class DefaultPermissionsProvider implements ICommandPermissionsProvider
|
||||
{
|
||||
@Override
|
||||
public Boolean validateUser(IGuild guild, IUser user, Command command)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean validateGroup(IRole role, Command command)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package net.pingex.dcf.permissions;
|
||||
|
||||
import net.pingex.dcf.commands.Command;
|
||||
import sx.blah.discord.handle.obj.IGuild;
|
||||
import sx.blah.discord.handle.obj.IRole;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Validate individual user permission.
|
||||
* @param guild Guild where command is executed. `null` = globally
|
||||
* @param user Target user
|
||||
* @param command Command to test
|
||||
* @return `true` if the user is able to run the command, `false` if he isn't able to, `null` if there is no answer.
|
||||
*/
|
||||
Boolean validateUser(IGuild guild, IUser user, Command command);
|
||||
|
||||
/**
|
||||
* Validate a role's ability to run a command.
|
||||
* @param role Target role
|
||||
* @param command Command to test
|
||||
* @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);
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package net.pingex.dcf.permissions;
|
||||
|
||||
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 sx.blah.discord.handle.impl.events.MessageReceivedEvent;
|
||||
import sx.blah.discord.handle.obj.IUser;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Commands for the permissions package.
|
||||
*/
|
||||
public class PermissionsCommands implements IWithCommands
|
||||
{
|
||||
@Override
|
||||
public Set<Command> getCommands()
|
||||
{
|
||||
return new HashSet<>(Arrays.asList(
|
||||
IsAllowedCommand
|
||||
));
|
||||
}
|
||||
|
||||
private static final Command IsAllowedCommand =
|
||||
new Command.Builder("canRun")
|
||||
.description("Tells whether target user is allowed to run the command.")
|
||||
.enabled(true)
|
||||
.usage("Command <User#Disc|ID> [verbose]")
|
||||
.defaultPermission(DefaultPermission.BOT_OWNER)
|
||||
.build(PermissionsCommands::isAllowedImpl);
|
||||
|
||||
private static Pattern usernamePattern = Pattern.compile("^.*#\\d{4}$");
|
||||
private static Pattern idPattern = Pattern.compile("^\\d{18}$");
|
||||
|
||||
private static void isAllowedImpl(MessageReceivedEvent event, List<String> arguments)
|
||||
{
|
||||
// Parameters
|
||||
Command target = null;
|
||||
IUser userToLookup = null;
|
||||
boolean verbose = false;
|
||||
|
||||
// Argchk size
|
||||
if(arguments.size() != 2 && arguments.size() != 3)
|
||||
{
|
||||
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Invalid arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Argchk#1 - valid command
|
||||
Optional<Command> uncheckedTarget = CommandRegistry.getCommandOrAliasByName(arguments.get(0));
|
||||
if(!uncheckedTarget.isPresent()) // Command existence
|
||||
{
|
||||
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Target command not found.");
|
||||
return;
|
||||
}
|
||||
else target = uncheckedTarget.get();
|
||||
|
||||
// Argchk#2 - uid OR username#1234 OR "me"
|
||||
if(idPattern.matcher(arguments.get(1)).matches()) // uid
|
||||
{
|
||||
userToLookup = event.getClient().getUserByID(arguments.get(1));
|
||||
}
|
||||
else if(usernamePattern.matcher(arguments.get(1)).matches()) // username#1234
|
||||
{
|
||||
for(IUser iUser : event.getClient().getUsers()) // Greedy
|
||||
if(arguments.get(1).equalsIgnoreCase(iUser.getName() + "#" + iUser.getDiscriminator()))
|
||||
{
|
||||
userToLookup = iUser;
|
||||
break; // Limit greediness
|
||||
}
|
||||
}
|
||||
else if(arguments.get(1).equalsIgnoreCase("me")) // me
|
||||
{
|
||||
userToLookup = event.getMessage().getAuthor();
|
||||
}
|
||||
else // ?
|
||||
{
|
||||
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "Invalid arguments.");
|
||||
return;
|
||||
}
|
||||
if(userToLookup == null)
|
||||
{
|
||||
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "User not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Argchk#3 - verbosity
|
||||
if(arguments.size() == 3) verbose = true;
|
||||
|
||||
// ========================================
|
||||
|
||||
DiscordInteractionsUtil.sendMessage(event.getMessage().getChannel(), "STUB: Checking perms for command " + target.getName() + " and user " + userToLookup.getName());
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.pingex.dcf.permissions;
|
||||
|
||||
import net.pingex.dcf.commands.Command;
|
||||
import sx.blah.discord.handle.obj.IGuild;
|
||||
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.
|
||||
*/
|
||||
public class PermissionsHandler
|
||||
{
|
||||
private static final ICommandPermissionsProvider CURRENT_PROVIDER = new DefaultPermissionsProvider();
|
||||
|
||||
/**
|
||||
* 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 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 command Requested command, as parsed by {@code CommandHandler}
|
||||
* @return `true` if request is granted, `false` if denied.
|
||||
*/
|
||||
public static boolean canRunCommand(IMessage request, Command command)
|
||||
{
|
||||
IUser requestAuthor = request.getAuthor();
|
||||
IGuild originatedGuild;
|
||||
List<IRole> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This package contains everything related to permissions and the ability for an user to run something.
|
||||
*/
|
||||
package net.pingex.dcf.permissions;
|
Reference in New Issue