Ejemplo n.º 1
0
    def suggest_loan_ids(self,
                         resp_name,
                         itgs,
                         comment_fullname,
                         lender_username,
                         loan_id,
                         amt,
                         rpiden,
                         rpversion,
                         loan=None):
        loans = Table('loans')
        lenders = Table('lenders')
        itgs.write_cursor.execute(
            loan_format_helper.create_loans_query().where(
                lenders.username == Parameter('%s')).where(
                    loans.repaid_at.isnull()).orderby(
                        loans.created_at, order=Order.desc).limit(7).get_sql(),
            (lender_username.lower(), ))

        loans = []
        row = itgs.write_cursor.fetchone()
        while row is not None:
            loans.append(loan_format_helper.fetch_loan(row))
            row = itgs.write_cursor.fetchone()

        suggested_loans = loan_format_helper.format_loan_table(loans,
                                                               include_id=True)

        formatted_response = get_response(
            itgs,
            resp_name,
            lender_username=lender_username,
            loan_id=loan_id,
            amount=str(amt),
            loan=('Loan Not Available' if loan is None
                  else loan_format_helper.format_loan_table([loan],
                                                            include_id=True)),
            suggested_loans=suggested_loans)
        utils.reddit_proxy.send_request(itgs, rpiden, rpversion,
                                        'post_comment', {
                                            'parent': comment_fullname,
                                            'text': formatted_response
                                        })
Ejemplo n.º 2
0
def get_failure_response_or_user_id_and_perms_for_authorization(
        itgs: LazyItgs, authorization: str, check_request_cost: int,
        req_user_id: int, action_self_permission: str,
        action_other_permission: str, additional_permissions_for_result: list):
    """Check that the given authorization header is sufficient for viewing
    the given users demographics and performing the given action. This will
    either return a Response (or JSONResponse) containing the problem with
    the users authorization (check using isinstance(resp, Response)), or it
    will return (user_id, permissions), where user_id is the authorized users
    id and permissions is the subset of permissions on the user that they
    have and we checked.

    Arguments:
    - `itgs (LazyIntegrations)`: The lazy integrations to use for connecting
        to networked services to verify the authtoken.
    - `authorization (str)`: The authorization header passed by the user.
    - `check_requset_cost (int)`: The ratelimiting penalty for even checking if
        they have permission to do the request.
    - `req_user_id (int, None)`: The id of the user whose demographics
        information is being acted on. Should be None for the lookup action.
        If this is set, the view permission is implied.
    - `action_self_permission (str, None)`: The required permission if the
        authorized user is the requested user, e.g.,
        EDIT_SELF_DEMOGRAPHICS_PERMISSION. Should be omitted if `req_user_id`
        is `None`.
    - `action_other_permission (str)`: The required permission if the
        authorized user is not the requested user.
    - `additional_permissions_for_result (iterable[str])`: Permissions which we
        should check for when looking up the authtokens permissions in the
        database, even if this function doesn't use them. Ensures that if the
        token has this permission it will be returned within the permissions
        array in the success response.

    Returns (Failure Response):
    - `resp (fastapi.responses.Response)`: The response that should be
        returned to the user.

    Returns (Success Response):
    - `user_id (int)`: The primary key of the user authorized via the
        authorization header to make this request.
    - `permissions (list[str])`: The subset of permissions which the user has
        and we checked for.
    """
    authtoken_id = None
    user_id = None
    failure_type = None
    headers = {'x-request-cost': str(check_request_cost)}

    authtoken_provided = helper.get_authtoken_from_header(authorization)
    if authtoken_provided is None:
        failure_type = 401
    else:
        max_age = datetime.fromtimestamp(
            time.time() - MAX_AUTHTOKEN_AGE_FOR_DEMOGRAPHICS_SECONDS)
        auths = Table('authtokens')
        password_auths = Table('password_authentications')
        itgs.read_cursor.execute(
            Query.from_(auths).select(auths.id,
                                      auths.user_id).join(password_auths).
            on(password_auths.id == auths.source_id).where(
                auths.source_type == Parameter('%s')).where(
                    auths.token == Parameter('%s')).where(
                        auths.created_at > Parameter('%s')).where(
                            password_auths.human.eq(True)).limit(1).get_sql(),
            ('password_authentication', authtoken_provided, max_age))
        row = itgs.read_cursor.fetchone()
        if row is None:
            failure_type = 403
        else:
            (authtoken_id, user_id) = row

            revoke_key = f'auth_token_revoked-{authtoken_id}'
            if itgs.cache.get(revoke_key) is not None:
                failure_type = 403
                authtoken_id = None
                user_id = None

    if failure_type is not None:
        if not ratelimit_helper.check_ratelimit(itgs, None, [],
                                                check_request_cost):
            return Response(status_code=429, headers=headers)
        return Response(status_code=failure_type, headers=headers)

    view_permission = None
    action_permission = None
    if req_user_id == user_id:
        view_permission = VIEW_SELF_DEMOGRAPHICS_PERMISSION
        action_permission = action_self_permission
    else:
        view_permission = VIEW_OTHERS_DEMOGRAPHICS_PERMISSION
        action_permission = action_other_permission

    check_permissions = []
    if view_permission is not None:
        check_permissions.append(view_permission)

    if action_permission is not None:
        check_permissions.append(action_permission)

    for perm in additional_permissions_for_result:
        check_permissions.append(perm)

    for perm in ratelimit_helper.RATELIMIT_PERMISSIONS:
        check_permissions.add(perm)

    authtoken_perms = Table('authtoken_permissions')
    permissions = Table('permissions')
    itgs.read_cursor.execute(
        Query.from_(authtoken_perms).select(
            permissions.name).join(permissions).on(
                permissions.id == authtoken_perms.permission_id).where(
                    authtoken_perms.authtoken_id == Parameter('%s')).where(
                        permissions.name.isin([
                            Parameter('%s') for _ in check_permissions
                        ])).get_sql(), [authtoken_id, *check_permissions])

    permissions = []
    row = itgs.read_cursor.fetchone()
    while row is not None:
        permissions.append(row[0])
        row = itgs.read_cursor.fetchone()

    if not ratelimit_helper.check_ratelimit(itgs, user_id, permissions,
                                            check_request_cost):
        return Response(status_code=429, headers=headers)

    if view_permission is not None and view_permission not in permissions:
        return Response(status_code=404, headers=headers)

    if req_user_id is not None:
        demos = Table('user_demographics')
        itgs.read_cursor.execute(
            Query.from_(demos).select(1).where(
                demos.user_id == Parameter('%s')).where(
                    demos.deleted == Parameter('%s')).limit(1).get_sql(),
            (req_user_id, True))
        if itgs.read_cursor.fetchone() is not None:
            return Response(status_code=451, headers=headers)

    if action_permission is not None and action_permission not in permissions:
        return Response(status_code=403, headers=headers)

    # Enrich logs
    users = Table('users')
    itgs.read_cursor.execute(
        Query.from_(users).select(
            users.username).where(users.id == Parameter('%s')).get_sql(),
        (user_id, ))
    username = itgs.read_cursor.fetchone()[0]

    if req_user_id is not None:
        itgs.read_cursor.execute(
            Query.from_(users).select(
                users.username).where(users.id == Parameter('%s')).get_sql(),
            (req_user_id, ))
        row = itgs.read_cursor.fetchone()
        if row is not None:
            req_username = row[0]
        else:
            req_username = f'<UNKNOWN:id={req_user_id}>'
    else:
        req_username = None

    itgs.logger.print(
        Level.DEBUG if user_id == req_user_id else Level.WARN,
        ('/u/{} is exercising their ability to view user demographics '
         'information {}. (view_permission = {}, action_permission = {})'),
        username, 'via a general lookup' if req_username is None else
        f'on /u/{req_username}', view_permission, action_permission)

    if req_user_id == user_id:
        pm_key = f'demographics_helper/pms/self/{user_id}'
        if itgs.cache.get(pm_key) is None:
            itgs.cache.set(pm_key, b'1', expire=86400)
            url_root = os.environ['ROOT_DOMAIN']
            send_queue = os.environ['AMQP_REDDIT_PROXY_QUEUE']
            itgs.channel.basic_publish(
                exchange='',
                routing_key=send_queue,
                body=json.dumps({
                    'type':
                    'compose',
                    'response_queue':
                    os.environ['AMQP_RESPONSE_QUEUE'],
                    'uuid':
                    secrets.token_urlsafe(47),
                    'version_utc_seconds':
                    float(os.environ['APP_VERSION_NUMBER']),
                    'sent_at':
                    time.time(),
                    'args': {
                        'recipient':
                        username,
                        'subject':
                        'RedditLoans: Demographic Information Viewed',
                        'body':
                        ('You just exercised your ability to view or edit your own '
                         'demographic information (email, first name, last name, street '
                         f'address, city, state, and zip) on {url_root} - if this was '
                         f'not you, immediately visit {url_root} and reset your password.'
                         )
                    }
                }).encode('utf-8'))
    elif req_user_id is not None:
        pm_key = f'demographics_helper/pms/other/{user_id}'
        if itgs.cache.get(pm_key) is None:
            itgs.cache.set(pm_key, b'1', expire=3600)
            url_root = os.environ['ROOT_DOMAIN']
            send_queue = os.environ['AMQP_REDDIT_PROXY_QUEUE']
            itgs.channel.basic_publish(
                exchange='',
                routing_key=send_queue,
                body=json.dumps({
                    'type':
                    'compose',
                    'response_queue':
                    os.environ['AMQP_RESPONSE_QUEUE'],
                    'uuid':
                    secrets.token_urlsafe(47),
                    'version_utc_seconds':
                    float(os.environ['APP_VERSION_NUMBER']),
                    'sent_at':
                    time.time(),
                    'args': {
                        'recipient':
                        username,
                        'subject':
                        'RedditLoans: Demographic Information Viewed',
                        'body':
                        ('You just exercised your ability to view or edit the '
                         'demographic information (email, first name, last name, street '
                         f'address, city, state, and zip) of /u/{req_username} on {url_root} '
                         f'- if this was not you then reset your password on {url_root}, '
                         'contact /u/Tjstretchalot, reset your password on reddit and '
                         f'strip all permissions from your own account on {url_root}.\n\n'
                         f'## Respond to modmail: "Demographic Info Viewed: /u/{req_username}".'
                         )
                    }
                }).encode('utf-8'))
            itgs.channel.basic_publish(
                exchange='',
                routing_key=send_queue,
                body=json.dumps({
                    'type':
                    'compose',
                    'response_queue':
                    os.environ['AMQP_RESPONSE_QUEUE'],
                    'uuid':
                    secrets.token_urlsafe(47),
                    'version_utc_seconds':
                    float(os.environ['APP_VERSION_NUMBER']),
                    'sent_at':
                    time.time(),
                    'args': {
                        'recipient':
                        '/r/borrow',
                        'subject':
                        f'Demographic Info Viewed: /u/{req_username}',
                        'body':
                        (f'/u/{username} exercised his ability to view demographic information '
                         f'on /u/{req_username}. This pm is sent at most once per hour.\n\n '
                         f'/u/{username} should respond here very soon confirming this was him, '
                         'otherwise efforts should be made to contact him. If he cannot be '
                         f'contacted, strip his access on {url_root} by going to Account -> '
                         f'Administrate, type in {username}, fill in a reason, and click '
                         '"Revoke All Permissions". If there are other options under the '
                         '"Authentication Method ID" dropdown, for each one select it and press '
                         '"Revoke All Permissions".\n\n'
                         'Then contact /u/Tjstretchalot if he has not already responded to this '
                         'thread.')
                    }
                }).encode('utf-8'))
            itgs.channel.basic_publish(
                exchange='',
                routing_key=send_queue,
                body=json.dumps({
                    'type':
                    'compose',
                    'response_queue':
                    os.environ['AMQP_RESPONSE_QUEUE'],
                    'uuid':
                    secrets.token_urlsafe(47),
                    'version_utc_seconds':
                    float(os.environ['APP_VERSION_NUMBER']),
                    'sent_at':
                    time.time(),
                    'args': {
                        'recipient':
                        'Tjstretchalot',
                        'subject':
                        'RedditLoans: Demographic Information Viewed',
                        'body':
                        f'Check modmail! Mod user: /u/{username} viewed /u/{req_username}'
                    }
                }).encode('utf-8'))

    return (user_id, permissions)