public async Task <bool> HandleIncomingMessage(MessageCreateEvent message, MessageContext ctx, Guild guild, Channel channel, bool allowAutoproxy, PermissionSet botPermissions) { if (!ShouldProxy(channel, message, ctx)) { return(false); } var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, guild.Id, null); if (autoproxySettings.AutoproxyMode == AutoproxyMode.Latch && IsUnlatch(message)) { // "unlatch" await _repo.UpdateAutoproxy(ctx.SystemId.Value, guild.Id, null, new() { AutoproxyMember = null }); return(false); } var rootChannel = await _cache.GetRootChannel(message.ChannelId); List <ProxyMember> members; // Fetch members and try to match to a specific member using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime)) members = (await _repo.GetProxyMembers(message.Author.Id, message.GuildId !.Value)).ToList(); if (!_matcher.TryMatch(ctx, autoproxySettings, members, out var match, message.Content, message.Attachments.Length > 0, allowAutoproxy)) { return(false); } // this is hopefully temporary, so not putting it into a separate method if (message.Content != null && message.Content.Length > 2000) { throw new PKError("PluralKit cannot proxy messages over 2000 characters in length."); } // Permission check after proxy match so we don't get spammed when not actually proxying if (!CheckBotPermissionsOrError(botPermissions, rootChannel.Id)) { return(false); } // this method throws, so no need to wrap it in an if statement CheckProxyNameBoundsOrError(match.Member.ProxyName(ctx)); // Check if the sender account can mention everyone/here + embed links // we need to "mirror" these permissions when proxying to prevent exploits var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, message); var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone); var allowEmbeds = senderPermissions.HasFlag(PermissionSet.EmbedLinks); // Everything's in order, we can execute the proxy! await ExecuteProxy(message, ctx, autoproxySettings, match, allowEveryone, allowEmbeds); return(true); }
// todo: move this somewhere else private async Task <PermissionSet> GetPermissionsInLogChannel(Channel channel) { var guild = await _cache.TryGetGuild(channel.GuildId.Value); if (guild == null) { guild = await _rest.GetGuild(channel.GuildId.Value); } var guildMember = await _cache.TryGetSelfMember(channel.GuildId.Value); if (guildMember == null) { guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser()); } var perms = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember); return(perms); }
public async Task <bool> HandleIncomingMessage(Shard shard, MessageCreateEvent message, MessageContext ctx, Guild guild, Channel channel, bool allowAutoproxy, PermissionSet botPermissions) { if (!ShouldProxy(channel, message, ctx)) { return(false); } // Fetch members and try to match to a specific member await using var conn = await _db.Obtain(); List <ProxyMember> members; using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime)) members = (await _repo.GetProxyMembers(conn, message.Author.Id, message.GuildId !.Value)).ToList(); if (!_matcher.TryMatch(ctx, members, out var match, message.Content, message.Attachments.Length > 0, allowAutoproxy)) { return(false); } // Permission check after proxy match so we don't get spammed when not actually proxying if (!await CheckBotPermissionsOrError(botPermissions, message.ChannelId)) { return(false); } // this method throws, so no need to wrap it in an if statement CheckProxyNameBoundsOrError(match.Member.ProxyName(ctx)); // Check if the sender account can mention everyone/here + embed links // we need to "mirror" these permissions when proxying to prevent exploits var senderPermissions = PermissionExtensions.PermissionsFor(guild, channel, message); var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone); var allowEmbeds = senderPermissions.HasFlag(PermissionSet.EmbedLinks); // Everything's in order, we can execute the proxy! await ExecuteProxy(shard, conn, message, ctx, match, allowEveryone, allowEmbeds); return(true); }
public static async Task <bool> CheckPermissionsInGuildChannel(this Context ctx, Channel channel, PermissionSet neededPerms) { // this is a quick hack, should probably do it properly eventually var guild = await ctx.Cache.TryGetGuild(channel.GuildId.Value); if (guild == null) { guild = await ctx.Rest.GetGuild(channel.GuildId.Value); } if (guild == null) { return(false); } var guildMember = ctx.Member; if (ctx.Guild?.Id != channel.GuildId) { guildMember = await ctx.Rest.GetGuildMember(channel.GuildId.Value, ctx.Author.Id); if (guildMember == null) { return(false); } } var userPermissions = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, guildMember); if ((userPermissions & neededPerms) == 0) { return(false); } return(true); }
public async Task PermCheckGuild(Context ctx) { Guild guild; GuildMemberPartial senderGuildUser = null; if (ctx.Guild != null && !ctx.HasNext()) { guild = ctx.Guild; senderGuildUser = ctx.Member; } else { var guildIdStr = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a server ID or run this command in a server."); if (!ulong.TryParse(guildIdStr, out var guildId)) { throw new PKSyntaxError($"Could not parse {guildIdStr.AsCode()} as an ID."); } guild = await _rest.GetGuild(guildId); if (guild != null) { senderGuildUser = await _rest.GetGuildMember(guildId, ctx.Author.Id); } if (guild == null || senderGuildUser == null) { throw Errors.GuildNotFound(guildId); } } var requiredPermissions = new [] { PermissionSet.ViewChannel, PermissionSet.SendMessages, PermissionSet.AddReactions, PermissionSet.AttachFiles, PermissionSet.EmbedLinks, PermissionSet.ManageMessages, PermissionSet.ManageWebhooks }; // Loop through every channel and group them by sets of permissions missing var permissionsMissing = new Dictionary <ulong, List <Channel> >(); var hiddenChannels = 0; foreach (var channel in await _rest.GetGuildChannels(guild.Id)) { var botPermissions = _bot.PermissionsIn(channel.Id); var userPermissions = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, senderGuildUser.Roles); if ((userPermissions & PermissionSet.ViewChannel) == 0) { // If the user can't see this channel, don't calculate permissions for it // (to prevent info-leaking, mostly) // Instead, count how many hidden channels and show the user (so they don't get confused) hiddenChannels++; continue; } // We use a bitfield so we can set individual permission bits in the loop // TODO: Rewrite with proper bitfield math ulong missingPermissionField = 0; foreach (var requiredPermission in requiredPermissions) { if ((botPermissions & requiredPermission) == 0) { missingPermissionField |= (ulong)requiredPermission; } } // If we're not missing any permissions, don't bother adding it to the dict // This means we can check if the dict is empty to see if all channels are proxyable if (missingPermissionField != 0) { permissionsMissing.TryAdd(missingPermissionField, new List <Channel>()); permissionsMissing[missingPermissionField].Add(channel); } } // Generate the output embed var eb = new EmbedBuilder() .Title($"Permission check for **{guild.Name}**"); if (permissionsMissing.Count == 0) { eb.Description($"No errors found, all channels proxyable :)").Color(DiscordUtils.Green); } else { foreach (var(missingPermissionField, channels) in permissionsMissing) { // Each missing permission field can have multiple missing channels // so we extract them all and generate a comma-separated list var missingPermissionNames = ((PermissionSet)missingPermissionField).ToPermissionString(); var channelsList = string.Join("\n", channels .OrderBy(c => c.Position) .Select(c => $"#{c.Name}")); eb.Field(new($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000))); eb.Color(DiscordUtils.Red); } } if (hiddenChannels > 0) { eb.Footer(new($"{"channel".ToQuantity(hiddenChannels)} were ignored as you do not have view access to them.")); } // Send! :) await ctx.Reply(embed : eb.Build()); }
public async Task PermCheckChannel(Context ctx) { if (!ctx.HasNext()) { throw new PKSyntaxError("You need to specify a channel."); } var error = "Channel not found or you do not have permissions to access it."; // todo: this breaks if channel is not in cache and bot does not have View Channel permissions var channel = await ctx.MatchChannel(); if (channel == null || channel.GuildId == null) { throw new PKError(error); } var guild = await _rest.GetGuildOrNull(channel.GuildId.Value); if (guild == null) { throw new PKError(error); } var guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser()); if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel)) { throw new PKError(error); } var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember); var webhookPermissions = PermissionExtensions.EveryonePermissions(guild, channel); // We use a bitfield so we can set individual permission bits ulong missingPermissions = 0; foreach (var requiredPermission in requiredPermissions) { if ((botPermissions & requiredPermission) == 0) { missingPermissions |= (ulong)requiredPermission; } } if ((webhookPermissions & PermissionSet.UseExternalEmojis) == 0) { missingPermissions |= (ulong)PermissionSet.UseExternalEmojis; } // Generate the output embed var eb = new EmbedBuilder() .Title($"Permission check for **{channel.Name}**"); if (missingPermissions == 0) { eb.Description("No issues found, channel is proxyable :)"); } else { var missing = ""; foreach (var permission in requiredPermissions) { if (((ulong)permission & missingPermissions) == (ulong)permission) { missing += $"\n- **{permission.ToPermissionString()}**"; } } if (((ulong)PermissionSet.UseExternalEmojis & missingPermissions) == (ulong)PermissionSet.UseExternalEmojis) { missing += $"\n- **{PermissionSet.UseExternalEmojis.ToPermissionString()}**"; } eb.Description($"Missing permissions:\n{missing}"); } await ctx.Reply(embed : eb.Build()); }