async def put(self, username): if regex.match('^0x[a-fA-F0-9]{40}$', username): address_to_update = username elif regex.match('^[a-zA-Z][a-zA-Z0-9_]{2,59}$', username): async with self.db: row = await self.db.fetchrow("SELECT * FROM users WHERE lower(username) = lower($1)", username) if row is None: raise JSONHTTPError(404, body={'errors': [{'id': 'not_found', 'message': 'Not Found'}]}) address_to_update = row['token_id'] else: raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_username', 'message': 'Invalid Username'}]}) request_address = self.verify_request() if request_address != address_to_update: raise JSONHTTPError(401, body={'errors': [{'id': 'permission_denied', 'message': 'Permission Denied'}]}) return await self.update_user(address_to_update)
async def get(self): try: offset = int(self.get_query_argument('offset', 0)) limit = int(self.get_query_argument('limit', 10)) except ValueError: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]}) query = self.get_query_argument('query', None) apps = parse_boolean(self.get_query_argument('apps', None)) payment_address = self.get_query_argument('payment_address', None) if payment_address and not validate_address(payment_address): raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Invalid payment_address'}]}) if query is None: if payment_address: async with self.db: rows = await self.db.fetch( "SELECT * FROM users WHERE payment_address = $3 " "ORDER BY payment_address, name, username " "OFFSET $1 LIMIT $2", offset, limit, payment_address) results = [user_row_for_json(self.request, row) for row in rows] else: results = [] else: # strip punctuation query = ''.join([c for c in query if c not in string.punctuation]) # split words and add in partial matching flags query = '|'.join(['{}:*'.format(word) for word in query.split(' ') if word]) args = [offset, limit, query] where_q = [] if payment_address: where_q.append("payment_address = ${}".format(len(args) + 1)) args.append(payment_address) if apps is not None: where_q.append("is_app = ${}".format(len(args) + 1)) args.append(apps) where_q = " AND {}".format(" AND ".join(where_q)) if where_q else "" sql = ("SELECT * FROM " "(SELECT * FROM users, TO_TSQUERY($3) AS q " "WHERE (tsv @@ q){}) AS t1 " "ORDER BY TS_RANK_CD(t1.tsv, TO_TSQUERY($3)) DESC, name, username " "OFFSET $1 LIMIT $2" .format(where_q)) async with self.db: rows = await self.db.fetch(sql, *args) results = [user_row_for_json(self.request, row) for row in rows] querystring = 'query={}'.format(query) if apps is not None: querystring += '&apps={}'.format('true' if apps else 'false') if payment_address: querystring += '&payment_address={}'.format(payment_address) self.write({ 'query': querystring, 'offset': offset, 'limit': limit, 'results': results })
async def get(self, username): # check if ethereum address is given if regex.match('^0x[a-fA-F0-9]{40}$', username): sql = "SELECT * FROM users WHERE token_id = $1" args = [username] # otherwise verify that username is valid elif not regex.match('^[a-zA-Z][a-zA-Z0-9_]{2,59}$', username): raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_username', 'message': 'Invalid Username'}]}) else: sql = "SELECT * FROM users WHERE lower(username) = lower($1)" args = [username] if self.apps_only: sql += " AND is_app = $2 AND blocked = $3" args.extend([True, False]) async with self.db: row = await self.db.fetchrow(sql, *args) if row is None: raise JSONHTTPError(404, body={'errors': [{'id': 'not_found', 'message': 'Not Found'}]}) self.write(user_row_for_json(self.request, row))
async def post(self): if self.is_request_signed(): sender_token_id = self.verify_request() else: # this is an anonymous transaction sender_token_id = None try: result = await TokenEthJsonRPC( sender_token_id, self.application).send_transaction(**self.json) except JsonRPCInternalError as e: raise JSONHTTPError(500, body={'errors': [e.data]}) except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) except TypeError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) self.write({"tx_hash": result})
async def update_user_avatar(self, address): # make sure a user with the given address exists async with self.db: user = await self.db.fetchrow("SELECT * FROM users WHERE token_id = $1", address) if user is None: raise JSONHTTPError(404, body={'errors': [{'id': 'not_found', 'message': 'Not Found'}]}) files = self.request.files.values() if len(files) != 1: raise JSONHTTPError(404, body={'errors': [{'id': 'bad_arguments', 'message': 'Too many files'}]}) file = next(iter(files)) if len(file) != 1: raise JSONHTTPError(404, body={'errors': [{'id': 'bad_arguments', 'message': 'Too many files'}]}) data = file[0]['body'] mime_type = file[0]['content_type'] data, cache_hash, format = await self.run_in_executor(process_image, data, mime_type) async with self.db: await self.db.execute("INSERT INTO avatars (token_id, img, hash, format) VALUES ($1, $2, $3, $4) " "ON CONFLICT (token_id) DO UPDATE " "SET img = EXCLUDED.img, hash = EXCLUDED.hash, format = EXCLUDED.format, last_modified = (now() AT TIME ZONE 'utc')", address, data, cache_hash, format) avatar_url = "/avatar/{}.{}".format(address, 'jpg' if format == 'JPEG' else 'png') await self.db.execute("UPDATE users SET avatar = $1 WHERE token_id = $2", avatar_url, address) user = await self.db.fetchrow("SELECT * FROM users WHERE token_id = $1", address) await self.db.commit() self.write(user_row_for_json(self.request, user))
async def get(self, tx_hash): format = self.get_query_argument('format', 'rpc') try: tx = await TokenEthJsonRPC( None, self.application).get_transaction(tx_hash) except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) if tx is None: raise JSONHTTPError( 404, body={'error': [{ 'id': 'not_found', 'message': 'Not Found' }]}) if format.lower() == 'sofa': async with self.db: row = await self.db.fetchrow( "SELECT * FROM transactions where transaction_hash = $1", tx_hash) if row is not None and row['error'] is not None: tx['error'] = row['error'] payment = SofaPayment.from_transaction(tx) message = payment.render() self.set_header('Content-Type', 'text/plain') self.write(message.encode('utf-8')) else: self.write(tx)
async def post(self): token_id = self.verify_request() payload = self.json if 'addresses' not in payload or len(payload['addresses']) == 0: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) addresses = payload['addresses'] try: await TokenEthJsonRPC(token_id, self.application).unsubscribe(*addresses) except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) except TypeError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) self.set_status(204)
async def get(self, key): if self.is_request_signed(): address = self.verify_request() self.set_login_result(key, address) self.set_status(204) else: if key not in self.login_requests: self.create_new_login_future(key) address = await self.login_requests[key] if address is None: raise JSONHTTPError(400, body={'errors': [{'id': 'request_timeout', 'message': 'Login request timed out'}]}) if address is False: raise JSONHTTPError(401, body={'errors': [{'id': 'login_failed', 'message': 'Login failed'}]}) if hasattr(self, 'on_login'): f = self.on_login(address) if asyncio.iscoroutine(f): f = await f return f # else self.write({"address": address})
def process_image(data, mime_type): stream = io.BytesIO(data) try: img = Image.open(stream) except OSError: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Invalid image data'}]}) if mime_type == 'image/jpeg' and img.format == 'JPEG': format = "JPEG" subsampling = 'keep' # check exif information for orientation if hasattr(img, '_getexif'): x = img._getexif() if x and EXIF_ORIENTATION in x and x[EXIF_ORIENTATION] > 1 and x[EXIF_ORIENTATION] < 9: orientation = x[EXIF_ORIENTATION] subsampling = get_sampling(img) if orientation == 2: # Vertical Mirror img = img.transpose(Image.FLIP_LEFT_RIGHT) elif orientation == 3: # Rotation 180° img = img.transpose(Image.ROTATE_180) elif orientation == 4: # Horizontal Im img = img.transpose(Image.FLIP_TOP_BOTTOM) elif orientation == 5: # Horizontal Im + Rotation 90° CCW img = img.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_90) elif orientation == 6: # Rotation 270° img = img.transpose(Image.ROTATE_270) elif orientation == 7: # Horizontal Im + Rotation 270° img = img.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_270) elif orientation == 8: # Rotation 90° img = img.transpose(Image.ROTATE_90) save_kwargs = {'subsampling': subsampling, 'quality': 85} elif mime_type == 'image/png' and img.format == 'PNG': format = "PNG" save_kwargs = {'icc_profile': img.info.get("icc_profile")} else: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Unsupported image format'}]}) if img.size[0] > 512 or img.size[1] > 512: img.thumbnail((512, 512)) stream = io.BytesIO() img.save(stream, format=format, optimize=True, **save_kwargs) data = stream.getbuffer().tobytes() hasher = hashlib.md5() hasher.update(data) cache_hash = hasher.hexdigest() return data, cache_hash, format
async def post(self, service): token_id = self.verify_request() payload = self.json if 'registration_id' not in payload: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) # TODO: registration id verification async with self.db: await self.db.execute( "DELETE FROM push_notification_registrations WHERE service = $1 AND registration_id = $2 AND token_id = $3", service, payload['registration_id'], token_id) await self.db.commit() self.set_status(204)
async def post(self, service): token_id = self.verify_request() payload = self.json if 'registration_id' not in payload: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) # TODO: registration id verification async with self.db: await self.db.execute( "INSERT INTO push_notification_registrations (service, registration_id, token_id) " "VALUES ($1, $2, $3) ON CONFLICT (service, registration_id) DO UPDATE SET token_id = $3", service, payload['registration_id'], token_id) await self.db.commit() self.set_status(204)
async def post(self): if 'auth_token' not in self.json: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]}) token = self.json['auth_token'] idclient = IdServiceClient(use_tornado=True) try: user = await idclient.whodis(token) except: log.exception("...") user = None if user: self.set_secure_cookie("user", user['token_id']) self.set_status(204) else: raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_token', 'message': 'Invalid token'}]})
async def get(self): address = self.current_user if address: idclient = IdServiceClient(use_tornado=True) user = await idclient.get_user(address) else: raise JSONHTTPError(401) self.write({"user": user})
async def get(self): token_id = self.verify_request() try: result = await TokenEthJsonRPC( token_id, self.application).list_subscriptions() except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) except TypeError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) self.write({"subscriptions": result})
async def get(self, address): try: result = await TokenEthJsonRPC( None, self.application).get_balance(address) except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) self.write(result)
def put(self): address = self.verify_request() if self.request.headers['Content-Type'] != 'application/json' and not self.request.files: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_data', 'message': 'Expected application/json or multipart/form-data'}]}) if self.request.files: return self.update_user_avatar(address) else: return self.update_user(address)
async def get(self): if not await self.is_admin_user(): raise JSONHTTPError(401) query = self.get_query_argument('query') client = IdServiceClient(use_tornado=True) results = await client.search_user(query) self.write(results)
async def post(self): if not self.current_user: raise JSONHTTPError(401) address = self.json['address'] async with self.db: await self.db.execute( "UPDATE submissions SET request_for_featured = TRUE WHERE app_token_id = $1", address) await self.db.commit() self.set_status(204)
async def get(self, token_id): async with self.db: row = await self.db.fetchrow( "SELECT apps.*, sofa_manifests.* FROM apps " "JOIN sofa_manifests ON " "sofa_manifests.token_id = apps.token_id " "WHERE apps.token_id = $1", token_id) if row is None: raise JSONHTTPError(404, body={'errors': [{'id': 'not_found', 'message': 'Not Found'}]}) result = app_from_row(row) self.write(result)
async def post(self): if not await self.is_admin_user(): raise JSONHTTPError(401) address = self.json['address'] async with self.db: await self.db.execute( "UPDATE apps SET featured = FALSE WHERE token_id = $1", address) await self.db.commit() self.set_status(204)
async def post(self): if not await self.is_admin_user(): raise JSONHTTPError(401) address = self.json['address'] async with self.db: await self.db.execute("DELETE FROM admins WHERE token_id= $1", address) await self.db.commit() self.set_status(204)
async def get(self): if not self.current_user: raise JSONHTTPError(401) try: offset = int(self.get_query_argument('offset', 0)) limit = int(self.get_query_argument('limit', 10)) except ValueError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) async with self.db: count = await self.db.fetchrow( "SELECT count(*) FROM submissions WHERE submitter_token_id = $1", self.current_user) apps = await self.db.fetch( "SELECT apps.*, sofa_manifests.*, submissions.request_for_featured FROM submissions " "JOIN apps ON submissions.app_token_id = apps.token_id " "JOIN sofa_manifests ON apps.token_id = sofa_manifests.token_id " "WHERE submissions.submitter_token_id = $1 " "ORDER BY apps.name " "OFFSET $2 " "LIMIT $3", self.current_user, offset, limit) results = [] for row in apps: val = response_for_row(row) results.append(val) self.write({ 'offset': offset, 'limit': limit, 'apps': results, 'total': count['count'] })
async def post(self): try: if 'from' in self.json: self.json['from_address'] = self.json.pop('from') if 'to' in self.json: self.json['to_address'] = self.json.pop('to') result = await TokenEthJsonRPC( None, self.application).create_transaction_skeleton(**self.json) except JsonRPCError as e: raise JSONHTTPError(400, body={'errors': [e.data]}) except TypeError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) self.write({"tx": result})
async def on_login(self, address): num = int(data_encoder(os.urandom(16))[2:], 16) token = b62encode(num) async with self.db: row = await self.db.fetchrow("SELECT * FROM users where token_id = $1", address) if row is None: raise JSONHTTPError(401) await self.db.execute("INSERT INTO auth_tokens (token, address) VALUES ($1, $2)", token, address) await self.db.commit() self.write({'auth_token': token})
async def get(self): if not await self.is_admin_user(): raise JSONHTTPError(401) try: offset = int(self.get_query_argument('offset', 0)) limit = int(self.get_query_argument('limit', 10)) except ValueError: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) async with self.db: count = await self.db.fetchrow("SELECT count(*) FROM admins") admins = await self.db.fetch( "SELECT * FROM admins " "OFFSET $1 " "LIMIT $2", offset, limit) results = [] client = IdServiceClient(use_tornado=True) for row in admins: try: val = await client.get_user(row['token_id']) results.append(val) except HTTPError: pass self.write({ 'offset': offset, 'limit': limit, 'admins': results, 'total': count['count'] })
async def post(self): if 'reputation' not in self.application.config or 'id' not in self.application.config['reputation']: raise HTTPError(404) try: address = self.verify_request() except JSONHTTPError: raise HTTPError(404) if address != self.application.config['reputation']['id']: raise HTTPError(404) if not all(x in self.json for x in ['address', 'score', 'count']): raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]}) token_id = self.json['address'] if not validate_address(token_id): raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_address', 'message': 'Invalid Address'}]}) count = self.json['count'] count = parse_int(count) if count is None: raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_count', 'message': 'Invalid Count'}]}) score = self.json['score'] if isinstance(score, str) and validate_decimal_string(score): score = Decimal(score) if not isinstance(score, (int, float, Decimal)): raise JSONHTTPError(400, body={'errors': [{'id': 'invalid_score', 'message': 'Invalid Score'}]}) async with self.db: await self.db.execute("UPDATE apps SET reputation_score = $1, review_count = $2 WHERE token_id = $3", score, count, token_id) await self.db.commit() self.set_status(204)
async def post(self): if not await self.is_admin_user(): raise JSONHTTPError(401) address = self.json['address'] async with self.db: row = await self.db.fetchrow( "SELECT * FROM admins WHERE token_id = $1", address) if row is None: await self.db.execute( "INSERT INTO admins (token_id) VALUES ($1)", address) await self.db.commit() self.set_status(204)
async def get(self, force_featured=None): try: offset = int(self.get_query_argument('offset', 0)) limit = int(self.get_query_argument('limit', 10)) except ValueError: raise JSONHTTPError(400, body={'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]}) if force_featured: featured = True else: featured = self.get_query_argument('featured', 'false') if featured.lower() == 'false': featured = False else: featured = True query = self.get_query_argument('query', None) args = [] sql = "SELECT * FROM apps JOIN sofa_manifests ON sofa_manifests.token_id = apps.token_id " if query: # strip any punctuation query = ''.join([c for c in query if c not in string.punctuation]) args.append('%{}%'.format(query)) sql += " WHERE apps.name ILIKE $1" if featured: sql += " AND apps.featured IS TRUE" elif featured: sql += " WHERE apps.featured IS TRUE" countsql = "SELECT COUNT(*) " + sql[8:] countargs = args[:] sql += " ORDER BY apps.name OFFSET ${} LIMIT ${}".format(len(args) + 1, len(args) + 2) args.extend([offset, limit]) async with self.db: count = await self.db.fetchrow(countsql, *countargs) rows = await self.db.fetch(sql, *args) results = [app_from_row(row) for row in rows] self.write({ 'query': query or '', 'offset': offset, 'limit': limit, 'apps': results, 'featured': featured, 'total': count['count'] })
async def get(self, token): user = None async with self.db: row = await self.db.fetchrow("SELECT u.*, a.created AS auth_token_created FROM auth_tokens a " "JOIN users u ON a.address = u.token_id " "WHERE a.token = $1", token) if row is not None: # only allow tokens to be used for 10 minutes print(row) if row['auth_token_created'] + timedelta(minutes=10) > datetime.utcnow(): user = user_row_for_json(self.request, row) else: print('expired') # remove token after single use await self.db.execute("DELETE FROM auth_tokens WHERE token = $1", token) await self.db.commit() else: print('not found') if user: self.write(user) else: raise JSONHTTPError(404)
async def post(self): """Handles submitting a new app to the directory service""" if not self.current_user: raise JSONHTTPError(401) if not all(x in self.json for x in ['display_name', 'token_id']): raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'bad_arguments', 'message': 'Bad Arguments' }] }) token_id = self.json['token_id'] if not validate_address(token_id): raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'invalid_token_id', 'message': 'Invalid Arguments' }] }) # check if the user has already submitted this app async with self.db: existing = await self.db.fetchrow( "SELECT * FROM submissions WHERE submitter_token_id = $1 AND app_token_id = $2", self.current_user, self.json['token_id']) if existing: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'already_exists', 'message': 'App already exists' }] }) # check if the app has already been submitted (by someone else for e.g.) async with self.db: existing = await self.db.fetchrow( "SELECT * FROM apps WHERE token_id = $1", self.json['token_id']) # TODO: maybe this is actually ok (but it would be weird) if existing: raise JSONHTTPError(400, body={ 'errors': [{ 'id': 'already_exists', 'message': 'App already exists' }] }) client = IdServiceClient(use_tornado=True) bot = await client.get_user(self.json['token_id']) if bot is None: raise JSONHTTPError( 400, body={ 'errors': [{ 'id': 'not_found', 'message': 'Cannot find given address in the id service' }] }) username = bot['username'] payment_address = bot['payment_address'] display_name = self.json['display_name'] init_request = ['paymentAddress', 'language'] languages = ['en'] interfaces = ['ChatBot'] protocol = 'sofa-v1.0' avatar_url = self.json.get('avatar_url', None) if not avatar_url: avatar_url = 'https://token-id-service.herokuapp.com/identicon/{}.png'.format( token_id) async with self.db: await self.db.execute( "INSERT INTO apps " "(token_id, name) VALUES ($1, $2) ", token_id, display_name) await self.db.execute( "INSERT INTO sofa_manifests " "(token_id, payment_address, username, init_request, languages, interfaces, protocol, avatar_url) " "VALUES " "($1, $2, $3, $4, $5, $6, $7, $8)", token_id, payment_address, username, init_request, languages, interfaces, protocol, avatar_url) await self.db.execute( "INSERT INTO submissions " "(app_token_id, submitter_token_id) " "VALUES " "($1, $2)", token_id, self.current_user) row = await self.db.fetchrow( "SELECT * FROM apps JOIN sofa_manifests ON apps.token_id = sofa_manifests.token_id WHERE apps.token_id = $1", token_id) await self.db.commit() self.write(response_for_row(row))