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)
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')
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
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, {}
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)
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)
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')