Ejemplo n.º 1
0
def handle_auth_bearer_header(token):
    """
    Will handle the output from get_user_from_token in context of a chalice function. If user_id is determined,
    returns it. If user_id is not determined returns data to be returned

    :param token:
    :return: action, data
    """
    try:
        user_id = get_user_from_token(token)
    except EulaException as e:

        log.warning('user has not accepted EULA')
        if check_for_browser(app.current_request.headers):
            template_vars = {
                'title': e.payload['error_description'],
                'status_code': 403,
                'contentstring':
                f'Could not fetch data because "{e.payload["error_description"]}". Please accept EULA here: <a href="{e.payload["resolution_url"]}">{e.payload["resolution_url"]}</a> and try again.',
                'requestid': get_request_id(),
            }

            return 'return', make_html_response(template_vars, {}, 403,
                                                'error.html')
        return 'return', Response(body=e.payload, status_code=403, headers={})

    if user_id:
        log_context(user_id=user_id)
        user_profile = get_new_token_and_profile(user_id, True)
        if user_profile:
            return 'user_profile', user_profile

    return 'return', do_auth_and_return(app.current_request.context)
Ejemplo n.º 2
0
def root():
    user_profile = False
    template_vars = {'title': 'Welcome'}

    cookievars = get_cookie_vars(app.current_request.headers)
    if cookievars:
        if JWT_COOKIE_NAME in cookievars:
            # We have a JWT cookie
            user_profile = cookievars[JWT_COOKIE_NAME]

    if user_profile:
        if 'urs-user-id' in user_profile:
            log_context(user_id=user_profile['urs-user-id'])
        if os.getenv('MATURITY') == 'DEV':
            template_vars['profile'] = user_profile
    else:
        template_vars['URS_URL'] = get_urs_url(app.current_request.context)
    headers = {'Content-Type': 'text/html'}
    return make_html_response(template_vars, headers, 200, 'root.html')
Ejemplo n.º 3
0
    def __call__(self, event, context):
        resource_path = event.get('requestContext', {}).get('resourcePath')
        log_context(route=resource_path, request_id=context.aws_request_id)
        # get_jwt_field() below generates log messages, so the above log_context() sets the
        # vars for it to use while it's doing the username lookup
        userid = get_jwt_field(get_cookie_vars(event.get('headers', {}) or {}),
                               'urs-user-id')
        log_context(user_id=userid)

        resp = super().__call__(event, context)

        resp['headers'].update({'x-request-id': context.aws_request_id})
        log_context(user_id=None, route=None, request_id=None)

        return resp
Ejemplo n.º 4
0
def do_login(args, context, cookie_domain=''):

    log.debug('the query_params: {}'.format(args))

    if not args:
        template_vars = {
            'contentstring': 'No params',
            'title': 'Could Not Login'
        }
        headers = {}
        return 400, template_vars, headers

    if args.get('error', False):
        contentstring = 'An error occurred while trying to log into URS. URS says: "{}". '.format(
            args.get('error', ''))
        template_vars = {
            'contentstring': contentstring,
            'title': 'Could Not Login'
        }
        if args.get('error') == 'access_denied':
            # This happens when user doesn't agree to EULA. Maybe other times too.
            return_status = 401
            template_vars['contentstring'] = 'Be sure to agree to the EULA.'
            template_vars['error_code'] = 'EULA_failure'
        else:
            return_status = 400

        return return_status, template_vars, {}

    if 'code' not in args:
        contentstring = 'Did not get the required CODE from URS'

        template_vars = {
            'contentstring': contentstring,
            'title': 'Could not login.'
        }
        headers = {}
        return 400, template_vars, headers

    log.debug('pre-do_auth() query params: {}'.format(args))
    redir_url = get_redirect_url(context)
    auth = do_auth(args.get('code', ''), redir_url)
    log.debug('auth: {}'.format(auth))
    if not auth:
        log.debug('no auth returned from do_auth()')

        template_vars = {
            'contentstring': 'There was a problem talking to URS Login',
            'title': 'Could Not Login'
        }

        return 400, template_vars, {}

    user_id = auth['endpoint'].split('/')[-1]
    log_context(user_id=user_id)

    user_profile = get_profile(user_id, auth['access_token'])
    log.debug('Got the user profile: {}'.format(user_profile))
    if user_profile:
        log.debug('urs-access-token: {}'.format(auth['access_token']))
        if 'state' in args:
            redirect_to = args["state"]
        else:
            redirect_to = get_base_url(context)

        if 'user_groups' not in user_profile or not user_profile['user_groups']:
            user_profile['user_groups'] = []

        jwt_cookie_payload = user_profile_2_jwt_payload(
            user_id, auth['access_token'], user_profile)

        headers = {'Location': redirect_to}
        headers.update(
            make_set_cookie_headers_jwt(jwt_cookie_payload, '', cookie_domain))
        return 301, {}, headers

    template_vars = {
        'contentstring': 'Could not get user profile from URS',
        'title': 'Could Not Login'
    }
    return 400, template_vars, {}
Ejemplo n.º 5
0
def dynamic_url():
    t = [time.time()]
    custom_headers = {}
    log.debug('attempting to GET a thing')
    restore_bucket_vars()
    log.debug(f'b_map: {b_map}')
    t.append(time.time())

    if 'proxy' in app.current_request.uri_params:
        path, bucket, filename, custom_headers = process_request(
            app.current_request.uri_params['proxy'], b_map)
        log.debug('path, bucket, filename, custom_headers: {}'.format(
            (path, bucket, filename, custom_headers)))
        if not bucket:
            template_vars = {
                'contentstring': 'File not found',
                'title': 'File not found',
                'requestid': get_request_id(),
            }
            headers = {}
            return make_html_response(template_vars, headers, 404,
                                      'error.html')
    else:
        path, bucket, filename = (None, None, None)

    cookievars = get_cookie_vars(app.current_request.headers)
    user_profile = None
    if cookievars:
        log.debug('cookievars: {}'.format(cookievars))
        if JWT_COOKIE_NAME in cookievars:
            # this means our cookie is a jwt and we don't need to go digging in the session db
            user_profile = cookievars[JWT_COOKIE_NAME]
        else:
            log.warning('jwt cookie not found')
            # Not kicking user out just yet. We might be dealing with a public bucket
    t.append(time.time())  # 2
    # Check for public bucket
    pub_bucket = check_public_bucket(bucket, b_map, filename)
    t.append(time.time())  # 3
    if pub_bucket:
        log.debug("Accessing public bucket {0}".format(path))
    elif not user_profile:
        if 'Authorization' in app.current_request.headers and app.current_request.headers[
                'Authorization'].split()[0].lower() == 'bearer':
            # we will deal with "bearer" auth here. "Basic" auth will be handled by do_auth_and_return()
            log.debug('we got an Authorization header. {}'.format(
                app.current_request.headers['Authorization']))
            token = app.current_request.headers['Authorization'].split()[1]
            action, data = handle_auth_bearer_header(token)

            if action == 'return':
                # Not a successful event.
                return data

            user_profile = data
            user_id = user_profile['uid']
            log_context(user_id=user_id)
            log.debug(f'User {user_id} has user profile: {user_profile}')
            jwt_payload = user_profile_2_jwt_payload(user_id, token,
                                                     user_profile)
            log.debug(f"Encoding JWT_PAYLOAD: {jwt_payload}")
            custom_headers.update(
                make_set_cookie_headers_jwt(jwt_payload, '',
                                            os.getenv('COOKIE_DOMAIN', '')))

        else:
            return do_auth_and_return(app.current_request.context)

    t.append(time.time())  # 4
    # Check that the bucket is either NOT private, or user belongs to that group
    private_check = check_private_bucket(
        bucket, b_map, filename)  # NOTE: Is an optimization attempt worth it
    # if we're asking for a public file and we
    # omit this check?
    log.debug('private check: {}'.format(private_check))
    t.append(time.time())  # 5
    u_in_g, new_user_profile = user_in_group(private_check, cookievars,
                                             user_profile, False)
    t.append(time.time())  # 6

    new_jwt_cookie_headers = {}
    if new_user_profile:
        log.debug(
            f"We got new profile from user_in_group() {new_user_profile}")
        user_profile = new_user_profile
        jwt_cookie_payload = user_profile_2_jwt_payload(
            get_jwt_field(cookievars, 'urs-user-id'),
            get_jwt_field(cookievars, 'urs-access-token'), user_profile)
        new_jwt_cookie_headers.update(
            make_set_cookie_headers_jwt(jwt_cookie_payload, '',
                                        os.getenv('COOKIE_DOMAIN', '')))

    log.debug('user_in_group: {}'.format(u_in_g))

    if private_check and not u_in_g:
        template_vars = {
            'contentstring': 'This data is not currently available.',
            'title': 'Could not access data',
            'requestid': get_request_id(),
        }
        return make_html_response(template_vars, new_jwt_cookie_headers, 403,
                                  'error.html')

    if not filename:  # Maybe this belongs up above, right after setting the filename var?
        log.warning(
            "Request was made to directory listing instead of object: {0}".
            format(path))

        template_vars = {
            'contentstring': 'Request does not appear to be valid.',
            'title': 'Request Not Serviceable',
            'requestid': get_request_id(),
        }

        return make_html_response(template_vars, new_jwt_cookie_headers, 404,
                                  'error.html')

    custom_headers.update(new_jwt_cookie_headers)
    log.debug(
        f'custom headers before try download from bucket: {custom_headers}')
    t.append(time.time())  # 7

    log.debug('timing for dynamic_url()')
    log.debug('ET for restore_bucket_vars(): {}s'.format(t[1] - t[0]))
    log.debug('ET for check_public_bucket(): {}s'.format(t[3] - t[2]))
    log.debug('ET for possible auth header handling: {}s'.format(t[4] - t[3]))
    log.debug('ET for user_in_group(): {}s'.format(t[6] - t[5]))
    log.debug('ET for total: {}s'.format(t[7] - t[0]))

    return try_download_from_bucket(bucket, filename, user_profile,
                                    custom_headers)
Ejemplo n.º 6
0
def try_download_head(bucket, filename):
    t = [time.time()]
    client = get_data_dl_s3_client()
    t.append(time.time())
    # Check for range request
    range_header = get_range_header_val()
    try:
        if not range_header:
            download = client.get_object(Bucket=bucket, Key=filename)
        else:
            log.info("Downloading range {0}".format(range_header))
            download = client.get_object(Bucket=bucket,
                                         Key=filename,
                                         Range=range_header)
        t.append(time.time())
    except ClientError as e:
        log.warning("Could not get head for s3://{0}/{1}: {2}".format(
            bucket, filename, e))
        # cumulus uses this log message for metrics purposes.

        template_vars = {
            'contentstring': 'File not found',
            'title': 'File not found',
            'requestid': get_request_id(),
        }
        headers = {}
        cumulus_log_message(
            'failure', 404, 'HEAD', {
                'reason': 'Could not find requested data',
                's3': f'{bucket}/{filename}'
            })
        return make_html_response(template_vars, headers, 404, 'error.html')
    log.debug(download)

    response_headers = {'Content-Type': download['ContentType']}
    for header in download['ResponseMetadata']['HTTPHeaders']:
        name = header_map[header] if header in header_map else header
        value = download['ResponseMetadata']['HTTPHeaders'][
            header] if header != 'server' else 'egress'
        log.debug("setting header {0} to {1}.".format(name, value))
        response_headers[name] = value

    # Try Redirecting to HEAD. There should be a better way.
    user_id = get_jwt_field(get_cookie_vars(app.current_request.headers),
                            'urs-user-id')
    log_context(user_id=user_id)

    # Generate URL
    t.append(time.time())
    creds, offset = get_role_creds(user_id=user_id)
    url_lifespan = 3600 - offset
    bucket_region = client.get_bucket_location(
        Bucket=bucket)['LocationConstraint']
    bucket_region = 'us-east-1' if not bucket_region else bucket_region
    t.append(time.time())
    presigned_url = get_presigned_url(creds, bucket, filename, bucket_region,
                                      url_lifespan, user_id, 'HEAD')
    t.append(time.time())
    s3_host = urlparse(presigned_url).netloc

    # Return a redirect to a HEAD
    log.debug("Presigned HEAD URL host was {0}".format(s3_host))
    log.debug('timing for try_download_head()')
    log.debug('ET for get_data_dl_s3_client(): {}s'.format(t[1] - t[0]))
    log.debug('ET for client.get_object(): {}s'.format(t[2] - t[1]))
    log.debug('ET for get_role_creds(): {}s'.format(t[4] - t[3]))
    log.debug('ET for get_presigned_url(): {}s'.format(t[5] - t[4]))

    return make_redirect(presigned_url, {}, 303)
Ejemplo n.º 7
0
def try_download_from_bucket(bucket, filename, user_profile, headers: dict):
    # Attempt to pull userid from profile
    user_id = None
    if isinstance(user_profile, dict):
        if 'urs-user-id' in user_profile:
            user_id = user_profile['urs-user-id']
        elif 'uid' in user_profile:
            user_id = user_profile['uid']
    log.info("User Id for download is {0}".format(user_id))
    log_context(user_id=user_id)

    t0 = time.time()
    is_in_region = check_in_region_request(
        app.current_request.context['identity']['sourceIp'])
    t1 = time.time()
    creds, offset = get_role_creds(user_id, is_in_region)
    t2 = time.time()
    session = get_role_session(creds=creds, user_id=user_id)
    t3 = time.time()

    try:
        bucket_region = get_bucket_region(session, bucket)
        t4 = time.time()
    except ClientError as e:
        try:
            code = e.response['ResponseMetadata']['HTTPStatusCode']
        except (AttributeError, KeyError, IndexError):
            code = 400
        log.debug(f'response: {e.response}')
        log.error(
            f'ClientError while {user_id} tried downloading {bucket}/{filename}: {e}'
        )
        cumulus_log_message('failure', code, 'GET', {
            'reason': 'ClientError',
            's3': f'{bucket}/{filename}'
        })
        template_vars = {
            'contentstring': 'There was a problem accessing download data.',
            'title': 'Data Not Available',
            'requestid': get_request_id(),
        }

        headers = {}
        return make_html_response(template_vars, headers, code, 'error.html')

    log.debug('this region: {}'.format(
        os.getenv('AWS_DEFAULT_REGION', 'env var doesnt exist')))
    if bucket_region != os.getenv('AWS_DEFAULT_REGION'):
        log.warning(
            "bucket {0} is in region {1}, we are in region {2}! " +
            "This is double egress in Proxy mode!".format(
                bucket, bucket_region, os.getenv('AWS_DEFAULT_REGION')))
    client = get_bc_config_client(user_id)

    log.debug('timing for try_download_from_bucket(): ')
    log.debug('ET for check_in_region_request(): {}s'.format(t1 - t0))
    log.debug('ET for get_role_creds(): {}s'.format(t2 - t1))
    log.debug('ET for get_role_session(): {}s'.format(t3 - t2))
    log.debug('ET for get_bucket_region(): {}s'.format(t4 - t3))
    log.debug('ET for total: {}'.format(t4 - t0))

    log.info("Attempting to download s3://{0}/{1}".format(bucket, filename))

    try:
        # Make sure this file exists, don't ACTUALLY download
        range_header = get_range_header_val()
        if not range_header:
            if not os.getenv("SUPPRESS_HEAD"):
                client.head_object(Bucket=bucket, Key=filename)
            redirheaders = {}
        else:
            if not os.getenv("SUPPRESS_HEAD"):
                client.head_object(Bucket=bucket,
                                   Key=filename,
                                   Range=range_header)
            redirheaders = {'Range': range_header}

        expires_in = 3600 - offset
        redirheaders['Cache-Control'] = 'private, max-age={0}'.format(
            expires_in - 60)
        if isinstance(headers, dict):
            log.debug(f'adding {headers} to redirheaders {redirheaders}')
            redirheaders.update(headers)

        # Generate URL
        presigned_url = get_presigned_url(creds, bucket, filename,
                                          bucket_region, expires_in, user_id)
        s3_host = urlparse(presigned_url).netloc
        log.debug("Presigned URL host was {0}".format(s3_host))

        return make_redirect(presigned_url, redirheaders, 303)

    except ClientError as e:
        # Watch for bad range request:
        if e.response['ResponseMetadata']['HTTPStatusCode'] == 416:
            # cumulus uses this log message for metrics purposes.
            log.error(
                f"Invalid Range 416, Could not get range {get_range_header_val()} s3://{bucket}/{filename}: {e}"
            )
            cumulus_log_message(
                'failure', 416, 'GET', {
                    'reason': 'Invalid Range',
                    's3': f'{bucket}/{filename}',
                    'range': get_range_header_val()
                })
            return Response(body='Invalid Range', status_code=416, headers={})

        # cumulus uses this log message for metrics purposes.
        log.warning("Could not download s3://{0}/{1}: {2}".format(
            bucket, filename, e))
        template_vars = {
            'contentstring': 'Could not find requested data.',
            'title': 'Data Not Available',
            'requestid': get_request_id(),
        }
        headers = {}
        cumulus_log_message(
            'failure', 404, 'GET', {
                'reason': 'Could not find requested data',
                's3': f'{bucket}/{filename}'
            })
        return make_html_response(template_vars, headers, 404, 'error.html')