From ee9c87a29f2358188df28e8835ac0f6a8b1f8902 Mon Sep 17 00:00:00 2001 From: Ske Date: Sat, 2 May 2020 16:25:17 +0200 Subject: [PATCH] Refactor permission utils to properly account for lack of channel access --- PluralKit.Bot/CommandSystem/Context.cs | 28 ++++++++++---- PluralKit.Bot/Commands/ServerConfig.cs | 6 +-- PluralKit.Bot/Utils/DiscordUtils.cs | 53 +++++++++++++++----------- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/PluralKit.Bot/CommandSystem/Context.cs b/PluralKit.Bot/CommandSystem/Context.cs index a37bd869..54c3e7c0 100644 --- a/PluralKit.Bot/CommandSystem/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context.cs @@ -9,6 +9,7 @@ using Autofac; using DSharpPlus; using DSharpPlus.Entities; +using DSharpPlus.Exceptions; using PluralKit.Bot.Utils; using PluralKit.Core; @@ -281,14 +282,27 @@ namespace PluralKit.Bot throw new PKError("You do not have permission to access this information."); } - public DiscordChannel MatchChannel() + public async Task MatchChannel() { - if (!MentionUtils.TryParseChannel(PeekArgument(), out var channel)) return null; - var discordChannel = _rest.GetChannelAsync(channel).GetAwaiter().GetResult(); - if (discordChannel.Type != ChannelType.Text) return null; - - PopArgument(); - return discordChannel; + if (!MentionUtils.TryParseChannel(PeekArgument(), out var channel)) + return null; + + try + { + var discordChannel = await _shard.GetChannelAsync(channel); + if (discordChannel.Type != ChannelType.Text) return null; + + PopArgument(); + return discordChannel; + } + catch (NotFoundException) + { + return null; + } + catch (UnauthorizedException) + { + return null; + } } } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index 3407b8b3..3417ee20 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -25,7 +25,7 @@ namespace PluralKit.Bot DiscordChannel channel = null; if (ctx.HasNext()) - channel = ctx.MatchChannel() ?? throw new PKSyntaxError("You must pass a #channel to set."); + channel = await ctx.MatchChannel() ?? throw new PKSyntaxError("You must pass a #channel to set."); if (channel != null && channel.GuildId != ctx.Guild.Id) throw new PKError("That channel is not in this server!"); var cfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id); @@ -48,7 +48,7 @@ namespace PluralKit.Bot else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels."); else while (ctx.HasNext()) { - var channel = ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found."); + var channel = await ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found."); if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server."); affectedChannels.Add(channel); } @@ -73,7 +73,7 @@ namespace PluralKit.Bot else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels."); else while (ctx.HasNext()) { - var channel = ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found."); + var channel = await ctx.MatchChannel() ?? throw new PKSyntaxError($"Channel \"{ctx.PopArgument().SanitizeMentions()}\" not found."); if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server."); affectedChannels.Add(channel); } diff --git a/PluralKit.Bot/Utils/DiscordUtils.cs b/PluralKit.Bot/Utils/DiscordUtils.cs index 3b0a8b88..20fad07d 100644 --- a/PluralKit.Bot/Utils/DiscordUtils.cs +++ b/PluralKit.Bot/Utils/DiscordUtils.cs @@ -15,46 +15,53 @@ namespace PluralKit.Bot public static DiscordColor Red = new DiscordColor(0xef4b3d); public static DiscordColor Gray = new DiscordColor(0x979c9f); + public static Permissions DM_PERMISSIONS = (Permissions) 0b00000_1000110_1011100110000_000000; + public static string NameAndMention(this DiscordUser user) { return $"{user.Username}#{user.Discriminator} ({user.Mention})"; } - public static async Task PermissionsIn(this DiscordChannel channel, DiscordUser user) + // We funnel all "permissions from DiscordMember" calls through here + // This way we can ensure we do the read permission correction everywhere + private static Permissions PermissionsInGuild(DiscordChannel channel, DiscordMember member) { - if (channel.Guild != null) - { - var member = await channel.Guild.GetMemberAsync(user.Id); - return member.PermissionsIn(channel); - } + var permissions = channel.PermissionsFor(member); - if (channel.Type == ChannelType.Private) - return (Permissions) 0b00000_1000110_1011100110000_000000; - - return Permissions.None; + // This method doesn't account for channels without read permissions + // If we don't have read permissions in the channel, we don't have *any* permissions + if ((permissions & Permissions.AccessChannels) != Permissions.AccessChannels) + return Permissions.None; + + return permissions; } + public static async Task PermissionsIn(this DiscordChannel channel, DiscordUser user) + { + // Just delegates to PermissionsInSync, but handles the case of a non-member User in a guild properly + // This is a separate method because it requires an async call + if (channel.Guild != null && !(user is DiscordMember)) + return PermissionsInSync(channel, await channel.Guild.GetMemberAsync(user.Id)); + return PermissionsInSync(channel, user); + } + + // Same as PermissionsIn, but always synchronous. DiscordUser must be a DiscordMember if channel is in guild. public static Permissions PermissionsInSync(this DiscordChannel channel, DiscordUser user) { - if (user is DiscordMember dm && channel.Guild != null) - return dm.PermissionsIn(channel); + if (channel.Guild != null && !(user is DiscordMember)) + throw new ArgumentException("Function was passed a guild channel but a non-member DiscordUser"); - if (channel.Type == ChannelType.Private) - return (Permissions) 0b00000_1000110_1011100110000_000000; - + if (user is DiscordMember m) return PermissionsInGuild(channel, m); + if (channel.Type == ChannelType.Private) return DM_PERMISSIONS; return Permissions.None; } public static Permissions BotPermissions(this DiscordChannel channel) { + // TODO: can we get a CurrentMember somehow without a guild context? + // at least, without somehow getting a DiscordClient reference as an arg(which I don't want to do) if (channel.Guild != null) - { - var member = channel.Guild.CurrentMember; - return channel.PermissionsFor(member); - } - - if (channel.Type == ChannelType.Private) - return (Permissions) 0b00000_1000110_1011100110000_000000; - + return PermissionsInSync(channel, channel.Guild.CurrentMember); + if (channel.Type == ChannelType.Private) return DM_PERMISSIONS; return Permissions.None; }