From 8af64a6944e7d045944ee1fc63c64821ce9a1def Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Fri, 7 Aug 2020 00:39:52 +0300 Subject: [PATCH] Improve permission utils, make them bigint-aware --- .../commands/InitReactionRolesCmd.ts | 2 +- .../plugins/Utility/commands/ContextCmd.ts | 2 +- .../src/plugins/Utility/commands/InfoCmd.ts | 2 +- .../Utility/commands/MessageInfoCmd.ts | 2 +- .../src/plugins/Utility/commands/SourceCmd.ts | 3 +- backend/src/utils/canReadChannel.ts | 13 ++--- backend/src/utils/hasChannelPermissions.ts | 17 ------ backend/src/utils/hasDiscordPermissions.ts | 16 ++++++ .../src/utils/memberHasChannelPermissions.ts | 13 +++-- backend/src/utils/readChannelPermissions.ts | 7 +++ backend/src/utils/verifyPermissions.ts | 54 +++++++++++++++++++ 11 files changed, 97 insertions(+), 34 deletions(-) delete mode 100644 backend/src/utils/hasChannelPermissions.ts create mode 100644 backend/src/utils/hasDiscordPermissions.ts create mode 100644 backend/src/utils/readChannelPermissions.ts create mode 100644 backend/src/utils/verifyPermissions.ts diff --git a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts index 4675c0d1..4647336d 100644 --- a/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts +++ b/backend/src/plugins/ReactionRoles/commands/InitReactionRolesCmd.ts @@ -21,7 +21,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({ }, async run({ message: msg, args, pluginData }) { - if (!canReadChannel(args.message.channel, msg.member.id)) { + if (!canReadChannel(args.message.channel, msg.member)) { sendErrorMessage(pluginData, msg.channel, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/ContextCmd.ts b/backend/src/plugins/Utility/commands/ContextCmd.ts index a024ff5b..4cbfefc1 100644 --- a/backend/src/plugins/Utility/commands/ContextCmd.ts +++ b/backend/src/plugins/Utility/commands/ContextCmd.ts @@ -30,7 +30,7 @@ export const ContextCmd = utilityCmd({ const channel = args.channel || args.message.channel; const messageId = args.messageId || args.message.messageId; - if (!canReadChannel(channel, msg.member.id)) { + if (!canReadChannel(channel, msg.member)) { sendErrorMessage(pluginData, msg.channel, "Message context not found"); return; } diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 313301ff..37bf2968 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -61,7 +61,7 @@ export const InfoCmd = utilityCmd({ // 4. Message const messageTarget = await resolveMessageTarget(pluginData, value); if (messageTarget) { - if (canReadChannel(messageTarget.channel, message.author.id)) { + if (canReadChannel(messageTarget.channel, message.member)) { const embed = await getMessageInfoEmbed(pluginData, messageTarget.channel.id, messageTarget.messageId); if (embed) { message.channel.createMessage({ embed }); diff --git a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts index 7072c72c..582bc731 100644 --- a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts +++ b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts @@ -15,7 +15,7 @@ export const MessageInfoCmd = utilityCmd({ }, async run({ message, args, pluginData }) { - if (!canReadChannel(args.message.channel, message.author.id)) { + if (!canReadChannel(args.message.channel, message.member)) { sendErrorMessage(pluginData, message.channel, "Unknown message"); return; } diff --git a/backend/src/plugins/Utility/commands/SourceCmd.ts b/backend/src/plugins/Utility/commands/SourceCmd.ts index 3ebc5b71..c3698c94 100644 --- a/backend/src/plugins/Utility/commands/SourceCmd.ts +++ b/backend/src/plugins/Utility/commands/SourceCmd.ts @@ -4,7 +4,6 @@ import { errorMessage } from "../../../utils"; import { getBaseUrl, sendErrorMessage } from "../../../pluginUtils"; import moment from "moment-timezone"; import { Constants, TextChannel } from "eris"; -import { hasChannelPermissions } from "../../../utils/hasChannelPermissions"; import { canReadChannel } from "../../../utils/canReadChannel"; export const SourceCmd = utilityCmd({ @@ -18,7 +17,7 @@ export const SourceCmd = utilityCmd({ }, async run({ message: cmdMessage, args, pluginData }) { - if (!canReadChannel(args.message.channel, cmdMessage.member.id)) { + if (!canReadChannel(args.message.channel, cmdMessage.member)) { sendErrorMessage(pluginData, cmdMessage.channel, "Unknown message"); return; } diff --git a/backend/src/utils/canReadChannel.ts b/backend/src/utils/canReadChannel.ts index 3f4de5dd..f27823e4 100644 --- a/backend/src/utils/canReadChannel.ts +++ b/backend/src/utils/canReadChannel.ts @@ -1,10 +1,7 @@ -import { Constants, GuildChannel } from "eris"; -import { hasChannelPermissions } from "./hasChannelPermissions"; +import { Constants, GuildChannel, Member } from "eris"; +import { memberHasChannelPermissions } from "./memberHasChannelPermissions"; +import { readChannelPermissions } from "./readChannelPermissions"; -export function canReadChannel(channel: GuildChannel, memberId: string) { - const channelPermissions = channel.permissionsOf(memberId); - return hasChannelPermissions(channelPermissions, [ - Constants.Permissions.readMessages, - Constants.Permissions.readMessageHistory, - ]); +export function canReadChannel(channel: GuildChannel, member: Member) { + return memberHasChannelPermissions(member, channel, readChannelPermissions); } diff --git a/backend/src/utils/hasChannelPermissions.ts b/backend/src/utils/hasChannelPermissions.ts deleted file mode 100644 index 1dfe4088..00000000 --- a/backend/src/utils/hasChannelPermissions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Constants, Permission } from "eris"; - -export function hasChannelPermissions(channelPermissions: Permission, permissions: number | number[]) { - if (Boolean(channelPermissions.allow & Constants.Permissions.administrator)) { - return true; - } - - if (!Array.isArray(permissions)) { - permissions = [permissions]; - } - - for (const permission of permissions) { - if (!(channelPermissions.allow & permission)) return false; - } - - return true; -} diff --git a/backend/src/utils/hasDiscordPermissions.ts b/backend/src/utils/hasDiscordPermissions.ts new file mode 100644 index 00000000..6bc9c7aa --- /dev/null +++ b/backend/src/utils/hasDiscordPermissions.ts @@ -0,0 +1,16 @@ +import { Constants, Permission } from "eris"; + +/** + * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission + * @param requiredPermissions Bitmask of required permissions + */ +export function hasDiscordPermissions(resolvedPermissions: Permission, requiredPermissions: number | bigint) { + const allowedPermissions = BigInt(resolvedPermissions.allow); + const nRequiredPermissions = BigInt(requiredPermissions); + + if (Boolean(allowedPermissions & BigInt(Constants.Permissions.administrator))) { + return true; + } + + return Boolean(allowedPermissions & nRequiredPermissions); +} diff --git a/backend/src/utils/memberHasChannelPermissions.ts b/backend/src/utils/memberHasChannelPermissions.ts index 7e2f7d42..c8707b72 100644 --- a/backend/src/utils/memberHasChannelPermissions.ts +++ b/backend/src/utils/memberHasChannelPermissions.ts @@ -1,8 +1,15 @@ import { Constants, GuildChannel, Member, Permission } from "eris"; import { PluginData } from "knub"; -import { hasChannelPermissions } from "./hasChannelPermissions"; +import { hasDiscordPermissions } from "./hasDiscordPermissions"; -export function memberHasChannelPermissions(member: Member, channel: GuildChannel, permissions: number | number[]) { +/** + * @param requiredPermissions Bitmask of required permissions + */ +export function memberHasChannelPermissions( + member: Member, + channel: GuildChannel, + requiredPermissions: number | bigint, +) { const memberChannelPermissions = channel.permissionsOf(member.id); - return hasChannelPermissions(memberChannelPermissions, permissions); + return hasDiscordPermissions(memberChannelPermissions, requiredPermissions); } diff --git a/backend/src/utils/readChannelPermissions.ts b/backend/src/utils/readChannelPermissions.ts new file mode 100644 index 00000000..21120c51 --- /dev/null +++ b/backend/src/utils/readChannelPermissions.ts @@ -0,0 +1,7 @@ +import { Constants } from "eris"; + +/** + * Bitmask of permissions required to read messages in a channel + */ +export const readChannelPermissions = + BigInt(Constants.Permissions.readMessages) | BigInt(Constants.Permissions.readMessageHistory); diff --git a/backend/src/utils/verifyPermissions.ts b/backend/src/utils/verifyPermissions.ts new file mode 100644 index 00000000..e6f8088b --- /dev/null +++ b/backend/src/utils/verifyPermissions.ts @@ -0,0 +1,54 @@ +import { Constants, Permission } from "eris"; +import { PluginData } from "knub"; +import { hasDiscordPermissions } from "./hasDiscordPermissions"; +import { LogsPlugin } from "../plugins/Logs/LogsPlugin"; +import { LogType } from "../data/LogType"; + +const defaultErrorText = `Missing permissions.`; + +const camelCaseToTitleCase = str => + str + .replace(/([a-z])([A-Z])/g, "$1 $2") + .split(" ") + .map(w => w[0].toUpperCase() + w.slice(1)) + .join(" "); + +const permissionNumberToName: Map = new Map(); +for (const key in Constants.Permissions) { + permissionNumberToName.set(BigInt(Constants.Permissions[key]), camelCaseToTitleCase(key)); +} + +/** + * + * @param resolvedPermissions A Permission object from e.g. GuildChannel#permissionsOf() or Member#permission + * @param requiredPermissions Bitmask of required permissions + * @param errorText Custom error text + */ +export function verifyPermissions( + pluginData: PluginData, + resolvedPermissions: Permission, + requiredPermissions: number | bigint, + errorText?: string, +) { + const nRequiredPermissions = BigInt(requiredPermissions); + + if (!hasDiscordPermissions(resolvedPermissions, nRequiredPermissions)) { + const requiredPermissionNames = []; + for (const [permissionNumber, permissionName] of permissionNumberToName.entries()) { + if (nRequiredPermissions & permissionNumber) { + requiredPermissionNames.push(permissionName); + } + } + + const logs = pluginData.getPlugin(LogsPlugin); + logs.log(LogType.BOT_ALERT, { + body: `${errorText || + defaultErrorText} Please ensure I have the following permissions: **${requiredPermissionNames.join( + "**, **", + )}**`.trim(), + }); + return false; + } + + return true; +}