From da42b422f04be62521845cacb028aa84ec0f50f2 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Mon, 18 Apr 2016 19:31:36 +0100 Subject: [PATCH] Added Permission evaluation for channels and EvaluatedPermissions class. --- src/client/Client.js | 2 + src/client/ClientDataResolver.js | 59 +++++++++++++++++++++++++ src/structures/EvaluatedPermissions.js | 39 +++++++++++++++++ src/structures/Guild.js | 19 +++++++- src/structures/GuildMember.js | 6 +++ src/structures/ServerChannel.js | 60 +++++++++++++++++++++++++- src/util/Constants.js | 10 +++++ test/random.js | 7 +++ 8 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/client/ClientDataResolver.js create mode 100644 src/structures/EvaluatedPermissions.js diff --git a/src/client/Client.js b/src/client/Client.js index 634cc790f..9a5fe5b64 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -6,6 +6,7 @@ const Constants = require('../util/Constants'); const RESTManager = require('./rest/RestManager'); const ClientDataStore = require('../structures/DataStore/ClientDataStore'); const ClientManager = require('./ClientManager'); +const ClientDataResolver = require('./ClientDataResolver'); const WebSocketManager = require('./websocket/WebSocketManager'); class Client extends EventEmitter{ @@ -17,6 +18,7 @@ class Client extends EventEmitter{ this.store = new ClientDataStore(this); this.manager = new ClientManager(this); this.ws = new WebSocketManager(this); + this.resolver = new ClientDataResolver(this); } login(email, password) { diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js new file mode 100644 index 000000000..47ee3f9fc --- /dev/null +++ b/src/client/ClientDataResolver.js @@ -0,0 +1,59 @@ +'use strict'; + +const Structure = name => require(`../structures/${name}`); + +const User = Structure('User'); +const Message = Structure('Message'); +const Guild = Structure('Guild'); +const ServerChannel = Structure('ServerChannel'); +const TextChannel = Structure('TextChannel'); +const VoiceChannel = Structure('VoiceChannel'); +const GuildMember = Structure('GuildMember'); + +function $string(obj) { + return (typeof obj === 'string' || obj instanceof String); +} + +class ClientDataResolver { + + constructor(client) { + this.client = client; + } + + ResolveUser(user) { + if (user instanceof User) { + return user; + }else if ($string(user)) { + return this.client.store.get('users', user); + }else if (user instanceof Message) { + return user.author; + }else if (user instanceof Guild) { + return user.owner; + } + + return null; + } + + ResolveGuild(guild) { + if (guild instanceof Guild) { + return guild; + } + } + + ResolveGuildMember(guild, user) { + if (user instanceof GuildMember) { + return user; + } + + guild = this.ResolveGuild(guild); + user = this.ResolveUser(user); + + if (!guild || !user) { + return null; + } + + return guild.store.get('members', user.id); + } +} + +module.exports = ClientDataResolver; diff --git a/src/structures/EvaluatedPermissions.js b/src/structures/EvaluatedPermissions.js new file mode 100644 index 000000000..96d0a4c9d --- /dev/null +++ b/src/structures/EvaluatedPermissions.js @@ -0,0 +1,39 @@ +'use strict'; + +const Constants = require('../Util/Constants'); + +class EvaluatedPermissions { + constructor(member, permissions) { + this.member = member; + this.permissions = permissions; + } + + serialize() { + let serializedPermissions = {}; + for (let permissionName in Constants.PermissionFlags) { + serializedPermissions[permissionName] = this.hasPermission(permissionName); + } + + return serializedPermissions; + } + + hasPermission(permission, explicit) { + if (permission instanceof String || typeof permission === 'string') { + permission = Constants.PermissionFlags[permission]; + } + + if (!permission) { + throw Constants.Errors.NOT_A_PERMISSION; + } + + if (!explicit) { + if ((this.permissions & Constants.PermissionFlags.MANAGE_ROLES) > 0) { + return true; + } + } + + return ((this.permissions & permission) > 0); + } +} + +module.exports = EvaluatedPermissions; diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f6fb90a00..462edee38 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -60,12 +60,15 @@ class Guild { return this.name; } + member(user) { + return this.client.resolver.ResolveGuildMember(this, user); + } + setup(data) { this.id = data.id; this.available = !data.unavailable; this.splash = data.splash; this.region = data.region; - this.ownerID = data.owner_id; this.name = data.name; this.memberCount = data.member_count; this.large = data.large; @@ -87,6 +90,8 @@ class Guild { } } + this.owner = this.store.get('members', data.owner_id); + if (data.channels) { this.store.clear('channels'); for (let channel of data.channels) { @@ -125,6 +130,18 @@ class Guild { } } } + + get channels() { return this.store.getAsArray('channels'); } + + get $channels() { return this.store.data.channels; } + + get roles() { return this.store.getAsArray('roles'); } + + get $roles() { return this.store.data.roles; } + + get members() { return this.store.getAsArray('members'); } + + get $members() { return this.store.data.members; } } module.exports = Guild; diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index e8b531db0..265f0afe6 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -25,6 +25,12 @@ class GuildMember { get roles() { let list = []; + let everyoneRole = this.guild.store.get('roles', this.guild.id); + + if (everyoneRole) { + list.push(everyoneRole); + } + for (let roleID of this._roles) { let role = this.guild.store.get('roles', roleID); if (role) { diff --git a/src/structures/ServerChannel.js b/src/structures/ServerChannel.js index 9be3bd58a..ebd7ea8b6 100644 --- a/src/structures/ServerChannel.js +++ b/src/structures/ServerChannel.js @@ -2,6 +2,8 @@ const Channel = require('./Channel'); const PermissionOverwrites = require('./PermissionOverwrites'); +const EvaluatedPermissions = require('./EvaluatedPermissions'); +const Constants = require('../util/Constants'); class ServerChannel extends Channel{ constructor(guild, data) { @@ -15,7 +17,7 @@ class ServerChannel extends Channel{ this.position = data.position; this.name = data.name; this.lastMessageID = data.last_message_id; - + this.ow = data.permission_overwrites; if (data.permission_overwrites) { this.permissionOverwrites = []; for (let overwrite of data.permission_overwrites) { @@ -24,6 +26,62 @@ class ServerChannel extends Channel{ } } + permissionsFor(member) { + member = this.client.resolver.ResolveGuildMember(this.guild, member); + if (member) { + if (this.guild.owner.id === member.id) { + return new EvaluatedPermissions(member, Constants.ALL_PERMISSIONS); + } + + let roles = member.roles; + let permissions = 0; + let overwrites = this.overwritesFor(member, true); + + for (let role of roles) { + permissions |= role.permissions; + } + + for (let overwrite of overwrites.role.concat(overwrites.member)) { + permissions = permissions & ~overwrite.denyData; + permissions = permissions | overwrite.allowData; + } + + if (!!(permissions & (Constants.PermissionFlags.MANAGE_ROLES))) { + permissions = Constants.ALL_PERMISSIONS; + } + + return new EvaluatedPermissions(member, permissions); + } + } + + overwritesFor(member, verified) { + // for speed + if (!verified) + member = this.client.resolver.ResolveGuildMember(this.guild, member); + if (member) { + let found = []; + let memberRoles = member._roles; + + let roleOverwrites = []; + let memberOverwrites = []; + + for (let overwrite of this.permissionOverwrites) { + if (overwrite.id === member.id) { + memberOverwrites.push(overwrite); + } else if (memberRoles.indexOf(overwrite.id) > -1) { + roleOverwrites.push(overwrite); + } + } + + return { + role: roleOverwrites, + member: memberOverwrites, + }; + } + + return []; + } + toString() { return this.name; } diff --git a/src/util/Constants.js b/src/util/Constants.js index 2b1705e0a..802b1714c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -1,3 +1,5 @@ +'use strict'; + const DefaultOptions = exports.DefaultOptions = { ws: { large_threshold: 250, @@ -157,4 +159,12 @@ const PermissionFlags = exports.PermissionFlags = { USE_VAD: 1 << 25, }; +let _ALL_PERMISSIONS = 0; + +for (let key in PermissionFlags) { + _ALL_PERMISSIONS |= PermissionFlags[key]; +} + +const ALL_PERMISSIONS = exports.ALL_PERMISSIONS = _ALL_PERMISSIONS; + const DEFAULT_PERMISSIONS = exports.DEFAULT_PERMISSIONS = 36953089; diff --git a/test/random.js b/test/random.js index f55fcecac..64575e0c5 100644 --- a/test/random.js +++ b/test/random.js @@ -80,3 +80,10 @@ client.on('messageUpdate', (old, message) => { if (message.author.username === 'hydrabolt') console.log('Message updated from', old.content, 'to', message.content); }); + +client.on('message', message => { + if (message.content === '?perms?') { + console.log(message.author.username, 'asked for perms in', message.channel.name, ':'); + console.log(message.channel.permissionsFor(message.author).serialize()); + } +});