def _to_relation_model(row): row = db.to_relation_model(row) if board_type == 'music': # since ipfs_file is related with music contracts, move ipfs_file row into music_contracts row. row['music_contract']['ipfs_file'] = [row['ipfs_file']] del row['ipfs_file'] return row
def _get_board_comment(board_type, comment_id): table_name = db.table.comment(board_type) # if unknown board type if not table_name: return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY) statement = db.Statement(table_name).where(comment_id=comment_id, status='posted') subcomments_query_str = """ SELECT `c`.*, '!user', `u`.* FROM `{}` `c` INNER JOIN `users` `u` ON (`u`.`user_id` = `c`.`user_id`) WHERE `parent_comment_id` = :parent_comment_id AND `status` = :comment_status ORDER BY `c`.`created_at` ASC """.format(table_name) with db.engine_rdonly.connect() as connection: comment = statement.select(connection).fetchone() # if the comment does not exist if not comment: return helper.response_err(ERR.COMMON.NOT_EXIST) comment = db.to_relation_model(comment) subcomments = connection.execute( text(subcomments_query_str), parent_comment_id=comment['comment_id'], comment_status='posted') comment.update({'subcomments': db.to_relation_model_list(subcomments)}) return helper.response_ok(comment)
def _get_community_post(board_type, post_id): table_name = db.table.board(board_type) # if unknown board type if not table_name: return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY) if board_type == 'music': # if board type is music, show with related music contracts and IPFS file. additional_columns = """ , '!music_contract', `mc`.* """ inner_join = """ LEFT JOIN `{}` `mc` ON (`mc`.`post_id` = `b`.`post_id`) """.format(db.table.MUSIC_CONTRACTS, db.table.IPFS_FILES) else: additional_columns = '' inner_join = '' post_query_statement = """ SELECT `b`.* {} FROM `{}` `b` {} WHERE `b`.`post_id` = :post_id AND `b`.`status` = :status """.format(additional_columns, table_name, inner_join) ipfs_files_query_statement = """ SELECT * FROM `ipfs_files` `if` INNER JOIN `music_contracts` `mc` ON (`mc`.`ipfs_file_id` = `if`.`file_id` OR `mc`.`ipfs_file_id` = `if`.`root_id`) WHERE `mc`.`post_id` = :post_id """ tags_statement = db.Statement( db.table.tags(board_type)).columns('name').where(post_id=post_id) with db.engine_rdonly.connect() as connection: post = connection.execute(text(post_query_statement), post_id=post_id, status='posted').fetchone() # if the post does not exist, if post is None: return helper.response_err(ERR.COMMON.NOT_EXIST) post = db.to_relation_model(post) if board_type == 'music': ipfs_files = db.to_relation_model_list( connection.execute(text(ipfs_files_query_statement), post_id=post_id)) post['music_contract'].update({'ipfs_file': ipfs_files}) tags = tags_statement.select(connection) post.update({'tags': [tag['name'] for tag in tags]}) return helper.response_ok(post)
def get(self, connection, file_id): s3 = session.client('s3') statement = db.Statement(db.table.FILES).where(file_id=file_id).limit(1) file = db.to_relation_model(statement.select(connection).fetchone()) # if file does not exist, if file is None: return None return s3.get_object(Bucket=file['bucket'], Key=file['object_key'])
def decorated_func(*args, **kwargs): from modules import database as db from flask import request from modules.response import helper from modules.response import error_constants as ER # get JWT token from request header "Authorization" token = request.headers.get('Authorization', None) token = token.split(' ')[-1] if token is not None else None # decode token try: decoded_token = jwt.decode(token, JWT_SECRET_KEY, verify=True, audience=WebServerConfig.issuer) except jwt.exceptions.InvalidTokenError: return helper.response_err(ER.INVALID_SIGNATURE, ER.INVALID_SIGNATURE_MSG) address, sign_message_id = decoded_token['jti'].split('-') # get sign message for calculating hash request.connection = db.engine_rdonly.connect() sign_message_query_str = """ SELECT `u`.*, '!sign_message', `sm`.* FROM `users` `u` INNER JOIN `sign_messages` `sm` ON (`u`.`user_id` = `sm`.`user_id`) WHERE `message_id` = :sign_message_id AND `address` = :address LIMIT 1 """ user_row = request.connection.execute(text(sign_message_query_str), address=address, sign_message_id=sign_message_id).fetchone() if user_row is not None: user_row = db.to_relation_model(user_row) user_id = user_row['user_id'] sign_message = user_row['sign_message'] del user_row['sign_message'] # get hash from decoded JWT decoded_hash = decoded_token['hash'] # calculate hash from db real_hash = hashlib.md5("{}-{}-{}-{}".format(user_id, sign_message_id, user_row['address'], sign_message['private_key']) .encode('utf-8')).hexdigest() # if decoded hash is not equal to calculated hash from db, it's invalid token if decoded_hash != real_hash: return helper.response_err(ER.INVALID_SIGNATURE, ER.INVALID_SIGNATURE_MSG) # authenticated and inject user information request.user = user_row return func(*args, **kwargs)
def _get_user(address): """ Returns an user information by wallet address. If user(address) does not exist, give a random message for signing """ # if invalid address format, don't generate message if not check_address_format(address): return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG) with db.engine_rdonly.connect() as connection: user = db.statement(db.table.USERS).where(address=address).select(connection).fetchone() return helper.response_ok(db.to_relation_model(user))
def _get_my_upload_board(board_type): """ :param board_type: 'sheet' or 'streaming' :return: BasePost[] """ user = request.user with db.engine_rdonly.connect() as connect: query = db.statement(db.table.board('music')) \ .inner_join(db.table.MUSIC_CONTRACTS, 'post_id') \ .inner_join((db.table.MUSIC_PAYMENTS, db.table.MUSIC_CONTRACTS), 'contract_address') \ .where(type=board_type) \ .where_advanced(db.table.MUSIC_PAYMENTS, buyer_address=user['address']) \ .order('post_id', 'desc').select(connect) return helper.response_ok([db.to_relation_model(row) for row in query])
def _get_paper_contract(contract_address): """ Gets the paper contract information. """ paper_contract_statement = """ SELECT `p`.*, '!user', `u`.* FROM `papers` `p` LEFT JOIN `users` `u` ON (`u`.`user_id` = `p`.`user_id`) WHERE `contract_address` = :contract_address LIMIT 1 """ with db.engine_rdonly.connect() as connection: paper_contract = connection.execute( text(paper_contract_statement), contract_address=contract_address).fetchone() if not paper_contract: return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG) return helper.response_ok(db.to_relation_model(paper_contract))
def _get_community_post(board_type, post_id): table_name = db.table.board(board_type) # if unknown board type if not table_name: return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG) statement = db.Statement(table_name).where(post_id=post_id, status='posted') tags_statement = db.Statement( db.table.tags(board_type)).columns('name').where(post_id=post_id) with db.engine_rdonly.connect() as connection: post = statement.select(connection).fetchone() tags = tags_statement.select(connection) # if the post does not exist, if post is None: return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG) post = db.to_relation_model(post) post.update({'tags': [tag['name'] for tag in tags]}) return helper.response_ok(post)
def _get_user(address): """ Returns an user information by wallet address. If user(address) does not exist, give a random message for signing """ s3_base_url = 'https://s3.{region}.amazonaws.com'.format(region=s3_policy['profile']['region']) user_query_stmt = """ SELECT `u`.*, CONCAT(:s3_base_url, '/', `f`.`bucket`, '/', `f`.`object_key`) AS `profile_image` FROM `{}` `u` LEFT JOIN `{}` `f` ON (`f`.`file_id` = `u`.`profile_file_id` AND `f`.`type` = :file_type) WHERE `u`.`address` = :address LIMIT 1 """.format(db.table.USERS, db.table.FILES) # if invalid address format, don't generate message if not check_address_format(address): return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY) web3 = get_web3() web3.toChecksumAddress(address) with db.engine_rdonly.connect() as connection: user = connection.execute( text(user_query_stmt), s3_base_url=s3_base_url, address=address, file_type='profile' ).fetchone() if user is None: return helper.response_err(ERR.COMMON.NOT_EXIST) return helper.response_ok(db.to_relation_model(user))
def decorated_func(*args, **kwargs): from modules import database as db from flask import request from modules.response import helper from modules.response.error import ERR # get JWT token from request header "Authorization" token = request.headers.get('Authorization', None) token = token.split(' ')[-1] if token is not None else None # decode token try: decoded_token = jwt.decode(token, JWT_SECRET_KEY, verify=True, audience=AppConfig.issuer) except jwt.exceptions.InvalidTokenError: return helper.response_err(ERR.COMMON.INVALID_SIGNATURE) address, sign_message_id = decoded_token['jti'].split('-') # get sign message for calculating hash request.connection = db.engine_rdonly.connect() s3_base_url = 'https://s3.{region}.amazonaws.com'.format( region=s3_policy['profile']['region']) sign_message_query_str = """ SELECT `u`.*, CONCAT(:s3_base_url, '/', `f`.`bucket`, '/', `f`.`object_key`) AS `profile_image`, '!sign_message', `sm`.* FROM `users` `u` LEFT JOIN `files` `f` ON (`f`.`file_id` = `u`.`profile_file_id` AND `f`.`type` = :file_type) INNER JOIN `sign_messages` `sm` ON (`u`.`user_id` = `sm`.`user_id`) WHERE `message_id` = :sign_message_id AND `address` = :address LIMIT 1 """ user_row = request.connection.execute( text(sign_message_query_str), file_type='profile', address=address, s3_base_url=s3_base_url, sign_message_id=sign_message_id).fetchone() if user_row is not None: user_row = db.to_relation_model(user_row) user_id = user_row['user_id'] sign_message = user_row['sign_message'] del user_row['sign_message'] # get hash from decoded JWT decoded_hash = decoded_token['hash'] # calculate hash from db real_hash = hashlib.md5("{}-{}-{}-{}".format( user_id, sign_message_id, user_row['address'], sign_message['private_key']).encode('utf-8')).hexdigest() # if decoded hash is not equal to calculated hash from db, it's invalid token if decoded_hash != real_hash: return helper.response_err(ERR.COMMON.INVALID_SIGNATURE) # authenticated and inject user information request.user = user_row return func(*args, **kwargs)
def _get_paper_file(contract_address): """ Downloads a paper from a contract. json form: { "public_key": public key for encryption } """ json_form = request.get_json(force=True, silent=True) public_key = json_form.get('public_key') # not support downloading without encryption if not isinstance(public_key, str): return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG) # parse public key try: public_key = RSA.import_key(public_key) except ValueError: return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG) user_address = request.user['address'] web3 = get_web3() contract = MuzikaPaperContract(web3, contract_address=contract_address) if not contract.purchased(user_address): # if the user hasn't purchased this paper return helper.response_err(ER.AUTHENTICATION_FAILED, ER.AUTHENTICATION_FAILED_MSG) paper_statement = db.Statement( db.table.PAPERS).where(contract_address=contract_address).limit(1) with db.engine_rdonly.connect() as connection: paper = db.to_relation_model( paper_statement.select(connection).fetchone()) # if not supporting this paper contract if not paper: return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG) file_id = paper['file_id'] # if not having file if not file_id: # TODO: return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG) else: # get ipfs file hash file_hash = paper['ipfs_file_hash'] # download from bucket bucket = MuzikaS3Bucket() s3_response = bucket.get(connection, file_id) file_blob = s3_response['Body'].read() block = Block(data=file_blob, hash=file_hash) block_request = BlockRequest(block_hash=file_hash, public_key=public_key) encrypted_block = block_request.encrypt(block) response_body = encrypted_block.encrypted_key + encrypted_block.data response = make_response(response_body) response.headers['content-length'] = len(response_body) response.headers['content-type'] = 'text/plain' return response
def track_object(connection, ipfs_file_id=None, ipfs_object=None, **kwargs): if ipfs_file_id: object_query = """ SELECT `f`.*, `p`.`aes_key` FROM `{}` `f` INNER JOIN `{}` `p` ON (`f`.`file_id` = `p`.`file_id`) WHERE `f`.`file_id` = :ipfs_file_id LIMIT 1 """.format(db.table.IPFS_FILES, db.table.IPFS_FILES_PRIVATE) ipfs_object = connection.execute(text(object_query), ipfs_file_id=ipfs_file_id).fetchone() # if object does not exist if not ipfs_object: return ipfs_object = db.to_relation_model(ipfs_object) ipfs_file_id = ipfs_object['file_id'] root_ipfs_hash = ipfs_object['ipfs_hash'] file_type = ipfs_object['file_type'] aes_key = ipfs_object.get('aes_key') name = ipfs_object.get('name', '') root_id = kwargs.get('root_id') or ipfs_file_id if name == '/' or name is None: name = '' ipfs = RelayIpfs().get_connection() try: # query object links. If timeout, it will raise Timeout Error object_links = ipfs.ls(root_ipfs_hash, opts={'timeout': kwargs.get('timeout', '30s')})['Objects'][0]['Links'] except ipfsapi.exceptions.ErrorResponse: # if timeout error, set status to "disabled" db.Statement(db.table.IPFS_FILES)\ .set(status='disabled')\ .where(file_id=ipfs_file_id)\ .update(connection) return # if no object links, this object is just a file if not len(object_links): db.Statement(db.table.IPFS_FILES)\ .set(ipfs_object_type='file', root_id=root_id, status='success')\ .where(file_id=ipfs_file_id)\ .update(connection) # if having object links, this object is a directory and insert files else: db.Statement(db.table.IPFS_FILES)\ .set(ipfs_object_type='directory', root_id=root_id, name=name if name else '/', status='success')\ .where(file_id=ipfs_file_id)\ .update(connection) for link in object_links: link_object = { 'parent_id': ipfs_file_id, 'file_type': file_type, 'ipfs_hash': link['Hash'], 'encrypted': True if aes_key else False, 'name': '/'.join([name, link['Name']]), 'root_id': root_id } # if the linked object is directory if link['Type'] == 1: link_object.update({ 'ipfs_object_type': 'directory', 'status': 'pending' }) # track recursively # track_object(connection, ipfs_object=link_object, **kwargs) # if the linked object is file (Type == 2) else: link_object.update({ 'file_size': link['Size'], 'ipfs_object_type': 'file', 'status': 'success' }) # db.Statement(db.table.IPFS_FILES).set(**link_object).insert(connection) # insert an directory object in this IPFS object link_object.update({ 'file_id': db.Statement(db.table.IPFS_FILES).set( **link_object).insert(connection).lastrowid }) # if aes_key exists, insert AES KEY into the private table if aes_key: db.Statement(db.table.IPFS_FILES_PRIVATE)\ .set(file_id=link_object['file_id'], aes_key=aes_key)\ .insert(connection) link_object.update({'aes_key': aes_key}) # if directory, track recursively if link['Type'] == 1: kwargs.update({'root_id': root_id}) track_object(connection, ipfs_object=link_object, **kwargs)