def validate(self, data, typeof=dict, required=[]): if not isinstance(data, typeof): raise InvalidUsage(400) for key in required: if data.get(key) is None: raise InvalidUsage(400) return data
async def post(self, request): user = await self.server.tools.authorize( request.headers.get('Authorization'), permissions=['OWNER'], bots=False) data = self.validate(await request.json(), required=['name']) if len(data['name']) > 32: raise InvalidUsage(400, 'Bot name too long') connection = await self.server.database.acquire() try: async with connection.cursor() as cur: await cur.execute('SELECT * FROM `bots` WHERE `name` = %s', (data['name'], )) if await cur.fetchone(): raise InvalidUsage(400, 'Bot name in use') bid = self.server.snowflake.generate() token = self.server.token.generate(bid) await cur.execute( 'INSERT INTO `bots` (`id`, `name`, `permissions`, `snowflake`, `secret`) VALUES (%s, %s, %s, %s, %s)', (bid, data['name'], 0, token['snowflake'], token['secret'])) finally: self.server.database.release(connection) return Response(200, {'token': token['token']})
async def get(self, request, itype, sid): bot = await self.server.tools.authorize( request.headers.get('Authorization'), bot_permissions=['OWNER'], bots=True) if itype not in self.allowed: raise InvalidUsage(400, 'Invalid Id Type') itype = ThresholdIdTypes.get(itype.upper()[:-1]) thresholds = {} connection = await self.server.database.acquire() try: async with connection.cursor() as cur: if await cur.execute( 'SELECT * FROM `thresholds` WHERE `id` = %s AND `id_type` = %s', (sid, itype.value)): found = await cur.fetchone() thresholds.update({ k.value: found[k.value] for k in PerspectiveAttributes }) else: raise InvalidUsage( 404, 'Thresholds not found for this id and id type') finally: self.server.database.release(connection) return Response(200, thresholds)
async def dispatch(self, request: Request): method = self.methods.get(request.method.lower()) if not method: raise InvalidUsage(405) args_wanted = list(inspect.signature(method).parameters.keys()) args_available = request.match_info.copy() args_available.update({'request': request}) args_unsatisfied = set(args_wanted) - set(args_available.keys()) if args_unsatisfied: raise InvalidUsage(400) for arg, arg_type in self.types.items(): if arg not in args_wanted: continue if arg_type == 'snowflake': if args_available[arg] in ['@me', '@all']: continue try: args_available[arg] = int(args_available[arg]) except: raise InvalidUsage(400, '{} is not a snowflake'.format(arg)) return await method( **{arg: args_available[arg] for arg in args_wanted})
async def middleware_handler(request): try: return await handler(request) except web.HTTPException as e: return InvalidUsage(e.status) except InvalidUsage as e: return e except Exception as e: return InvalidUsage(500, str(e))
async def get(self, request, uid): user = await self.server.tools.authorize( request.headers.get('Authorization'), bot_permissions=['OWNER']) if uid == '@me': if user['bot']: if not user.get('user'): raise InvalidUsage( 400, 'Cannot use @me with bots without specifying user id in the token' ) uid = user['user']['id'] else: uid = user['id'] if uid != user.get('user', user)['id'] and not Permissions.check_any( user.get('user', user)['permissions'], ['OWNER', 'SUPERADMIN', 'ADMIN']): raise InvalidUsage(401) requested = {} connection = await self.server.database.acquire() try: async with connection.cursor() as cur: if user['bot'] and user.get( 'user', None) and uid == user['user']['id']: requested.update(user['user']) elif not user['bot'] and uid == user['id']: requested.update({ 'id': user['id'], 'username': user['username'], 'discriminator': user['discriminator'], 'avatar_hash': user['avatar_hash'], 'refreshed': user['refreshed'], 'permissions': user['permissions'] }) else: await cur.execute('SELECT * FROM `users` WHERE `id` = %s', (uid, )) requested.update(await cur.fetchone()) #update information from discord if not requested from bot finally: self.server.database.release(connection) if not requested: raise InvalidUsage(404, 'User not found') else: requested['discriminator'] = '{:04}'.format( requested['discriminator']) return Response(200, requested)
async def get(self, request, site): method = getattr(self, 'site_{}'.format(site)) if not method: raise InvalidUsage(404, 'oauth2 type not supported') session = await self.server.router.get_session(request) return await method(request, session)
async def get(self, request): #implement using timestamp data = {} connection = await self.server.database.acquire() try: async with connection.cursor() as cur: await cur.execute( ' '.join([ 'SELECT', ', '.join([ '`count`', '`started`', ', '.join([ '`{}`'.format(attribute.value) for attribute in PerspectiveAttributes ]) ]), 'FROM `muck_averages` WHERE', '`timestamp` = %s AND `context_type` = %s AND `context_id` = %s AND `user_id` = %s' ]), [0, ContextTypes.GLOBAL.value, 0, 0]) data['scores'] = await cur.fetchone() if not data['scores']: raise InvalidUsage(404, 'No data found') data['count'] = data['scores'].pop('count') data['started'] = data['scores'].pop('started') for key in data['scores'].keys(): data['scores'][key] = round( float(data['scores'][key]) / data['count'], 10) finally: self.server.database.release(connection) return Response(200, data)
async def site_discord(self, request): if not self.oauth2.get('discord'): raise InvalidUsage(500, 'Server missing Discord Oauth2 Config') return Redirect(302, 'https://discordapp.com/oauth2/authorize', params={ 'scope': 'bot', 'client_id': self.oauth2.get('discord').get('id') })
async def put(self, request, itype, sid): bot = await self.server.tools.authorize( request.headers.get('Authorization'), bot_permissions=['OWNER'], bots=True) if itype not in self.allowed: raise InvalidUsage(400, 'Invalid Id Type') itype = ThresholdIdTypes.get(itype.upper()[:-1]) data = self.validate(await request.json()) thresholds = {} for key in data: threshold = PerspectiveAttributes.get(key.upper()) if threshold: thresholds[threshold.value] = data[key] else: raise InvalidUsage(400, 'Invalid Attribute: {}'.format(key)) connection = await self.server.database.acquire() try: async with connection.cursor() as cur: ithresholds = thresholds.items() await cur.execute( ' '.join([ 'INSERT INTO `thresholds`', '(`id`, `id_type`, {})'.format(', '.join([ '`{}`'.format(k) for k, v in ithresholds ])), 'VALUES', '(%s, %s, {})'.format(', '.join([ '%s' for a in range(len(thresholds)) ])), 'ON DUPLICATE KEY UPDATE', ', '.join([ '`{k}` = ROUND(VALUES(`{k}`), 2)'.format(k=k) for k, v in ithresholds ]) ]), [sid, itype.value] + [v for k, v in ithresholds]) finally: self.server.database.release(connection) return Response(204)
async def get(self, request, site): if not self.oauth2.get('token_uri'): raise InvalidUsage(500, 'Site not configured properly') session = await self.server.router.get_session(request) method = getattr(self, 'site_{}'.format(site)) if not method: raise InvalidUsage(404) user_id, state_id, connection = await method(request, session) state_token = self.server.token.generate(state_id) async with connection.cursor() as cur: await cur.execute( 'INSERT INTO `token_states` (id, user_id, snowflake, secret) VALUES (%s, %s, %s, %s)', (state_id, user_id, state_token['snowflake'], state_token['secret'])) self.server.database.release(connection) return Redirect(302, self.oauth2.get('token_uri'), params={'token': state_token['token']})
async def post(self, request): try: data = self.validate(await request.json()) token = self.server.token.split(data.get('token', None)) except: raise InvalidUsage(400) connection = await self.server.database.acquire() try: async with connection.cursor() as cur: await cur.execute( 'SELECT * FROM `token_states` WHERE `id` = %s', (token['user_id'], )) state = await cur.fetchone() if not state or not self.server.token.compare( token['hmac'], state['snowflake'], state['secret']): raise InvalidUsage(400) await cur.execute('SELECT `id` FROM `users` WHERE `id` = %s', state['user_id']) user = await cur.fetchone() if not user: raise InvalidUsage(500, 'lol no user found') session_token = self.server.token.generate(user['id']) await cur.execute( 'INSERT INTO `token_sessions` (`id`, `user_id`, `secret`, `last_used`) VALUES (%s, %s, %s, %s)', (session_token['snowflake'], user['id'], session_token['secret'], 0)) await cur.execute('DELETE FROM `token_states` WHERE `id` = %s', (token['user_id'], )) finally: self.server.database.release(connection) if session_token: return Response(200, {'token': session_token['token']}) else: raise InvalidUsage(400)
async def site_discord(self, request, session): if not self.oauth2.get('discord'): raise InvalidUsage(500, 'Server missing Discord oauth2 config') params = { 'scope': 'identify guilds', 'response_type': 'code', 'client_id': self.oauth2['discord'].get('id'), 'state': self.server.snowflake.generate(), 'redirect_uri': self.oauth2['discord'].get('redirect_uri') } session['state'] = params['state'] return Redirect(302, 'https://discordapp.com/api/oauth2/authorize', params)
async def get(self, request): user = await self.server.tools.authorize( request.headers.get('Authorization')) if user['bot']: raise InvalidUsage(401, 'Bots cannot use this endpoint.') connection = await self.server.database.acquire() try: async with connection.cursor() as cur: await cur.execute( 'DELETE FROM `token_sessions` WHERE `id` = %s', (user['token']['snowflake'], )) finally: self.server.database.release(connection) return Response(204)
async def get(self, request, itype, sid): if itype not in self.allowed_types: raise InvalidUsage(404) where = ['`timestamp` = %s'] values = [0] if itype == 'guilds': where.append('`context_type` = %s') values.append(ContextTypes.GUILDS.value) where.append('`context_id` = %s') values.append(sid) elif itype == 'channels': where.append('`context_type` = %s') values.append(ContextTypes.CHANNELS.value) where.append('`context_id` = %s') values.append(sid) elif itype == 'users': where.append('`user_id` = %s') values.append(sid) context_id = request.query.get('context_id', None) context_type = request.query.get('context_type', None) if context_id is None or context_type is None: if context_id is None and context_type is None: context_id = 0 context_type = ContextTypes.GLOBAL else: raise InvalidUsage(400, 'Context id and type cannot both be empty when one is passed in.') else: context_type = ContextTypes.get(context_type.upper()) if not context_type: raise InvalidUsage(400, 'Invalid Context Type') where.append('`context_type` = %s') values.append(context_type.value) where.append('`context_id` = %s') values.append(context_id) response = {} connection = await self.server.database.acquire() try: async with connection.cursor() as cur: await cur.execute( ' '.join([ 'SELECT', ', '.join([ '`count`', '`started`', ', '.join([ '`{}`'.format(attribute.value) for attribute in PerspectiveAttributes ]) ]), 'FROM `muck_averages` WHERE', ' AND '.join(where) ]), values ) response['scores'] = await cur.fetchone() if not response['scores']: raise InvalidUsage(404, 'No data found') response['count'] = response['scores'].pop('count') response['started'] = response['scores'].pop('started') for key in response['scores'].keys(): response['scores'][key] = round(float(response['scores'][key]) / response['count'], 10) finally: self.server.database.release(connection) return Response(200, response)
async def site_discord(self, request): if not self.oauth2.get('discord', None): raise InvalidUsage(500, 'Discord link not set up') return Redirect(302, self.oauth2['discord'].get('invite'))
async def site_github(self, request): if not self.oauth2.get('github', None): raise InvalidUsage(500, 'Github link not set up') return Redirect(302, self.oauth2['github'].get('url'))
async def authorize(self, token, permissions=[], bot_permissions=[], userandbot=True, bots=None): try: token = self.token.split(token) except Exception as e: raise InvalidUsage(401) response = {'bot': False, 'token': token} connection = await self.database.acquire() try: async with connection.cursor() as cur: if token['type'] == 'user': if bots is not None and bots: raise InvalidUsage(401, 'Only bots can use this endpoint.') await cur.execute('SELECT * FROM `users` WHERE `id` = %s', (token['user_id'],)) user = await cur.fetchone() if not user: raise InvalidUsage(401) await cur.execute('SELECT `id`, `secret` FROM `token_sessions` WHERE `id` = %s', (token['snowflake'],)) session = await cur.fetchone() if not session or not self.token.compare(token['hmac'], session['id'], session['secret']): raise InvalidUsage(401) await cur.execute('UPDATE `token_sessions` SET `last_used` = %s WHERE `id` = %s', (time.time(), session['id'])) response.update(user) elif token['type'] == 'bot': if bots is not None and not bots: raise InvalidUsage(401, 'Bots cannot use this endpoint.') await cur.execute('SELECT * FROM `bots` WHERE `id` = %s', (token['bot_id'],)) bot = await cur.fetchone() if not bot or not self.token.compare(token['hmac'], bot['snowflake'], bot['secret']): raise InvalidUsage(401) response.update({ 'bot': True, 'id': bot['id'], 'name': bot['name'], 'permissions': bot['permissions'] }) if token.get('user_id'): await cur.execute('SELECT * FROM `users` WHERE `id` = %s', (token['user_id'],)) user = await cur.fetchone() if not user: raise InvalidUsage(401, 'User not found') response.update({'user': user}) finally: self.database.release(connection) if not response: raise InvalidUsage(401) if bot_permissions: if response['bot'] and not Permissions.check_any(response['permissions'], bot_permissions): raise InvalidUsage(401) if permissions and not (response['bot'] and not userandbot): if response['bot']: if not response.get('user'): raise InvalidUsage(401, 'Bots cannot use this endpoint') check = Permissions.check_any(response['user']['permissions'], permissions) else: check = Permissions.check_any(response['permissions'], permissions) if not check: raise InvalidUsage(401) return response
async def get(self, request, site): method = getattr(self, 'site_{}'.format(site)) if not method: raise InvalidUsage(404) return await method(request)
async def post(self, request): bot = await self.server.tools.authorize( request.headers.get('Authorization'), bots=True) if not self.perspective: raise InvalidUsage(500, 'Server missing perspective API key') store = bool(request.query.get('store', None) == 'true') data = self.validate(await request.json(), required=['content']) data['guild_id'] = data.get('guild_id', None) data['channel_id'] = data.get('channel_id', None) if store: if not Permissions.check(bot['permissions'], 'OWNER'): raise InvalidUsage(401) data = self.validate( data, required=['message_id', 'channel_id', 'user_id', 'timestamp']) data['edited'] = bool(data.get('edited', False)) try: data['message_id'] = int(data['message_id']) data['channel_id'] = int(data['channel_id']) data['user_id'] = int(data['user_id']) data['timestamp'] = int(data['timestamp']) except: raise InvalidUsage(400) try: data['guild_id'] = data['guild_id'] and int(data['guild_id']) data['channel_id'] = data['channel_id'] and int(data['channel_id']) except: raise InvalidUsage(400) if not data['content']: #if they send in a blank content lol raise InvalidUsage(400) data['content'] = data['content'].lower() mhash = hashlib.sha256(data['content'].encode()).hexdigest() response = {} connection = await self.server.database.acquire() try: async with connection.cursor() as cur: if store: if await cur.execute( 'SELECT * FROM `muck_messages` WHERE `message_id` = %s AND `timestamp` = %s AND `edited` = %s', (data['message_id'], data['timestamp'], data['edited'])): raise InvalidUsage(400, 'Message already inside database.') await cur.execute( 'SELECT * FROM `muck_cache` WHERE `hash` = %s', (mhash, )) scores = await cur.fetchone() if not scores or scores['analyzed'] < int( time.time() - self.max_cache_timestamp): http = await self.server.httpclient.googleapi_perspective( self.perspective['token'], data['content'], self.attributes, True) if http['status'] != 200: if not http['json']: raise InvalidUsage( http['status'], 'Google\'s API errored out with this status.') else: raise InvalidUsage( http['status'], http['data']['error']['message']) http = http['data'] scores = {'hash': mhash} for key in http['attributeScores'].keys(): scores[key.lower()] = round( http['attributeScores'][key]['summaryScore'] ['value'], 10) scores['analyzed'] = int(time.time()) iscores = scores.items() await cur.execute( ' '.join([ 'INSERT INTO `muck_cache` ', '({}) '.format(', '.join([ '`{}`'.format(k) for k, v in iscores ])), 'VALUES ', '({})'.format(', '.join(['%s' for k, v in iscores])), 'ON DUPLICATE KEY UPDATE', ', '.join([ '`{k}` = VALUES(`{k}`)'.format(k=k) for k, v in iscores if k != 'hash' ]) ]), [v for k, v in iscores]) response['hash'] = scores.pop('hash', mhash) response['analyzed'] = scores.pop('analyzed') response['scores'] = scores if store: await cur.execute( 'INSERT INTO `muck_messages` (`message_id`, `guild_id`, `channel_id`, `user_id`, `hash`, `timestamp`, `edited`) VALUES (%s, %s, %s, %s, %s, %s, %s)', (data['message_id'], data['guild_id'], data['channel_id'], data['user_id'], mhash, data['timestamp'], data['edited'])) self.server.loop.create_task( self.average(data['timestamp'], data['guild_id'], data['channel_id'], data['user_id'], scores)) if not store: for ttype in ['guild', 'channel']: tid = data.get('{}_id'.format(ttype), None) if not tid: continue if await cur.execute( 'SELECT * FROM `thresholds` WHERE `id` = %s AND `id_type` = %s', (tid, ThresholdIdTypes.get(ttype.upper()).value)): thresholds = await cur.fetchone() response[ttype] = { 'id': tid, 'passed': False, 'thresholds': {} } for attribute in thresholds: if not scores.get(attribute) or not thresholds[ attribute]: continue if scores[attribute] >= thresholds[attribute]: response[ttype]['passed'] = True response[ttype]['thresholds'][ attribute] = thresholds[attribute] finally: self.server.database.release(connection) if store: return Response(204) else: return Response(200, response)
async def site_discord(self, request, session): if not self.oauth2.get('discord'): raise InvalidUsage(500, 'Server missing Discord Oauth2 Config') server_state = session.get('state') if not server_state or not request.query.get('state') or int( server_state) != int(request.query.get('state')): raise InvalidUsage(400, 'Invalid State') if request.query.get('error'): raise InvalidUsage(400, request.query['error']) if not request.query.get('code'): raise InvalidUsage(400, 'Missing Code') response = await self.server.httpclient.discord_post_oauth2_token( client_id=self.oauth2['discord'].get('id'), client_secret=self.oauth2['discord'].get('secret'), grant_type='authorization_code', code=request.query.get('code'), redirect_uri=self.oauth2['discord'].get('redirect_uri')) if response['status'] != 200: if response['json']: raise InvalidUsage(response['status'], response['data']['error']) else: raise InvalidUsage(500, 'Discord API Error') response = response['data'] scope = response.get('scope', '').split(' ') if 'identify' not in scope or 'guilds' not in scope: raise InvalidUsage(400, 'Missing Identify or Guilds in scope.') oauth2 = { 'scope': response['scope'], 'token_type': response['token_type'], 'access_token': response['access_token'], 'refresh_token': response['refresh_token'], 'expires_in': response['expires_in'], 'refreshed': int(time.time()) } token = '{} {}'.format(oauth2['token_type'], oauth2['access_token']) response = await self.server.httpclient.discord_get_users_me(token) if response['status'] != 200: if response['json']: raise InvalidUsage(response['data']['code'], response['data']['message']) else: raise InvalidUsage(500, 'Discord API Error') response = response['data'] user = { 'id': response['id'], 'username': response['username'], 'discriminator': int(response['discriminator']), 'avatar_hash': response['avatar'], 'refreshed': int(time.time()) } connection = await self.server.database.acquire() try: async with connection.cursor() as cur: if (await cur.execute('SELECT * FROM `users` WHERE `id` = %s', (user['id'], ))): old = await cur.fetchone() iuser = user.items() await cur.execute( 'UPDATE `users` SET ' + '{} '.format(', '.join( ['`{}` = {}'.format(k, '%s') for k, v in iuser])) + 'WHERE `id` = %s', [v for k, v in iuser] + [user['id']]) ioauth2 = oauth2.items() await cur.execute( 'UPDATE `oauth2` SET ' + '{} '.format(', '.join( ['`{}` = {}'.format(k, '%s') for k, v in ioauth2])) + 'WHERE `user_id` = %s', [v for k, v in ioauth2] + [user['id']]) else: oauth2['user_id'] = user['id'] iuser = user.items() ioauth2 = oauth2.items() await cur.execute( 'INSERT INTO `users` ' + '({}) '.format(', '.join( ['`{}`'.format(k) for k, v in iuser])) + 'VALUES ' + '({})'.format(', '.join(['%s' for k, v in iuser])), [v for k, v in iuser]) await cur.execute( 'INSERT INTO `oauth2` ' + '({}) '.format(', '.join( ['`{}`'.format(k) for k, v in ioauth2])) + 'VALUES ' + '({})'.format(', '.join(['%s' for k, v in ioauth2])), [v for k, v in ioauth2]) except Exception as roof: self.server.database.release(connection) raise roof return (user['id'], server_state, connection)