예제 #1
0
 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
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
    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'])
예제 #5
0
    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)
예제 #6
0
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))
예제 #7
0
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])
예제 #8
0
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))
예제 #9
0
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)
예제 #10
0
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))
예제 #11
0
    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)
예제 #12
0
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
예제 #13
0
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)