def handler(event, _context): """Handle the authorization refresh. Keyword Args: event: The Lambda Event """ request = event['Records'][0]['cf']['request'] domain_name = request['headers']['host'][0]['value'] redirected_from_uri = 'https://%s' % domain_name try: parsed_qs = parse_qs(request.get('querystring')) requested_uri = parsed_qs.get('requestedUri')[0] current_nonce = parsed_qs.get('nonce')[0] # Add the requested uri path to the main redirected_from_uri += requested_uri or "" cookies = extract_and_parse_cookies(request.get('headers'), CONFIG.get('client_id')) tokens = { 'id_token': cookies.get('idToken'), 'access_token': cookies.get('accessToken'), 'refresh_token': cookies.get('refreshToken') } validate_refresh_request(current_nonce, cookies.get('nonce'), tokens) try: # Request new tokens based on the refresh_token body = { 'grant_type': 'refresh_token', 'client_id': CONFIG.get('client_id'), 'refresh_token': tokens.get('refresh_token') } res = http_post_with_retry( ('https://%s/oauth2/token' % CONFIG.get('cognito_auth_domain')), body, {'Content-Type': 'application/x-www-form-urlencoded'}) tokens['id_token'] = res.get('id_token') tokens['access_token'] = res.get('access_token') except Exception as err: # pylint: disable=broad-except LOGGER.error(err) # Otherwise clear the refresh token tokens['refresh_token'] = "" headers = { 'location': [{ 'key': 'location', 'value': redirected_from_uri }], 'set-cookie': get_cookie_headers(CONFIG.get('client_id'), CONFIG.get('oauth_scopes'), tokens, domain_name, CONFIG.get('cookie_settings')) } headers.update(CONFIG.get('cloud_front_headers')) # Redirect the user back to their requested uri # with new tokens at hand return { 'status': '307', 'statusDescription': 'Temporary Redirect', 'headers': headers } # Send a basic html error response and inform the user # why refresh was unsuccessful except Exception as err: # pylint: disable=broad-except LOGGER.info(err) LOGGER.info(traceback.print_exc()) headers = { "content-type": [{ "key": "Content-Type", "value": "text/html; charset=UTF-8" }] } headers.update(CONFIG.get('cloud_front_headers')) return { 'body': create_error_html("Bad Request", err, redirected_from_uri), 'status': '400', 'headers': headers }
def handler(event, _context): """Handle the authorization parsing. Keyword Args: event (Any): The Lambda Event """ request = event['Records'][0]['cf']['request'] domain_name = request['headers']['host'][0]['value'] redirected_from_uri = 'https://%s' % domain_name # Attempt to parse the request and retrieve authorization # tokens to integrate with our header cookies try: qsp = parse_qs(request.get('querystring')) # Authorization code given by Cognito code = qsp.get('code') # The requested URI and current nonce information state = json.loads( base64.urlsafe_b64decode(qsp.get('state')[0]).decode()) # Missing required components if not code or not state: msg = 'Invalid query string. ' + \ 'Your query string should include parameters "state" and "code"' LOGGER.info(msg) raise Exception(msg) current_nonce = state.get('nonce') requested_uri = state.get('requestedUri') redirected_from_uri = requested_uri or "/" # Get all the cookies from the headers cookies = extract_and_parse_cookies(request.get('headers'), CONFIG.get('client_id')) # Retrieve the original nonce value as well as our PKCE code original_nonce = cookies.get('nonce') pkce = cookies.get('pkce') # If we're missing one of the nonces, or they don't match, cause an error if not current_nonce or not original_nonce or current_nonce != original_nonce: # No original nonce? CSRF violation if not original_nonce: msg = "Your browser didn't send the nonce cookie along, " + \ "but it is required for security (prevent CSRF)" LOGGER.error(msg) raise Exception(msg) # Nonce's don't match msg = "Nonce Mismatch" LOGGER.error(msg) raise Exception(msg) payload = { 'grant_type': 'authorization_code', 'client_id': CONFIG.get('client_id'), 'redirect_uri': 'https://%s%s' % (domain_name, CONFIG.get('redirect_path_sign_in')), 'code': code[0], 'code_verifier': pkce } # Request tokens from our Cognito Authorization Domain tokens = http_post_with_retry( ('https://%s/oauth2/token' % CONFIG.get('cognito_auth_domain')), payload, {"Content-Type": "application/x-www-form-urlencoded"}) if not tokens: raise Exception('Was not able to obtain tokens from Cognito') headers = { 'location': [{ 'key': 'location', 'value': redirected_from_uri }], 'set-cookie': get_cookie_headers(CONFIG.get("client_id"), CONFIG.get("oauth_scopes"), tokens, domain_name, CONFIG.get('cookie_settings')) } headers.update(CONFIG.get('cloud_front_headers')) # Redirect user to the originally requested uri with the # token header cookies response = { 'status': '307', 'statusDescription': 'Temporary Redirect', 'headers': headers } return response except Exception as err: # pylint: disable=broad-except LOGGER.error(err) LOGGER.error(traceback.print_exc()) headers = CONFIG.get('cloud_front_headers') headers['content-type'] = [{ 'key': 'Content-Type', 'value': 'text/html; charset=UTF-8' }] # Inform user of bad request and reason return { 'body': create_error_html("Bad Request", err, redirected_from_uri), 'status': '400', 'headers': headers }
def handler(event, _context): """Handle the authorization parsing. Args: event (Any): The Lambda Event. _context (Any): Lambda context object. """ request = event["Records"][0]["cf"]["request"] domain_name = request["headers"]["host"][0]["value"] redirected_from_uri = "https://%s" % domain_name # Attempt to parse the request and retrieve authorization # tokens to integrate with our header cookies try: qsp = parse_qs(request.get("querystring")) # Authorization code given by Cognito code = qsp.get("code") # The requested URI and current nonce information state = json.loads(base64.urlsafe_b64decode(qsp.get("state")[0]).decode()) # Missing required components if not code or not state: msg = ( "Invalid query string. " 'Your query string should include parameters "state" and "code"' ) LOGGER.info(msg) raise Exception(msg) current_nonce = state.get("nonce") requested_uri = state.get("requestedUri") redirected_from_uri = requested_uri or "/" # Get all the cookies from the headers cookies = extract_and_parse_cookies( request.get("headers"), CONFIG.get("client_id") ) # Retrieve the original nonce value as well as our PKCE code original_nonce = cookies.get("nonce") pkce = cookies.get("pkce") # If we're missing one of the nonces, or they don't match, cause an error if not current_nonce or not original_nonce or current_nonce != original_nonce: # No original nonce? CSRF violation if not original_nonce: msg = ( "Your browser didn't send the nonce cookie along, " "but it is required for security (prevent CSRF)" ) LOGGER.error(msg) raise Exception(msg) # Nonce's don't match msg = "Nonce Mismatch" LOGGER.error(msg) raise Exception(msg) payload = { "grant_type": "authorization_code", "client_id": CONFIG.get("client_id"), "redirect_uri": "https://%s%s" % (domain_name, CONFIG.get("redirect_path_sign_in")), "code": code[0], "code_verifier": pkce, } # Request tokens from our Cognito Authorization Domain tokens = http_post_with_retry( ("https://%s/oauth2/token" % CONFIG.get("cognito_auth_domain")), payload, {"Content-Type": "application/x-www-form-urlencoded"}, ) if not tokens: raise Exception("Was not able to obtain tokens from Cognito") headers = { "location": [{"key": "location", "value": redirected_from_uri}], "set-cookie": get_cookie_headers( CONFIG.get("client_id"), CONFIG.get("oauth_scopes"), tokens, domain_name, CONFIG.get("cookie_settings"), ), } headers.update(CONFIG.get("cloud_front_headers")) # Redirect user to the originally requested uri with the # token header cookies response = { "status": "307", "statusDescription": "Temporary Redirect", "headers": headers, } return response except Exception as err: # pylint: disable=broad-except LOGGER.error(err) LOGGER.error(traceback.print_exc()) headers = CONFIG.get("cloud_front_headers") headers["content-type"] = [ {"key": "Content-Type", "value": "text/html; charset=UTF-8"} ] # Inform user of bad request and reason return { "body": create_error_html("Bad Request", err, redirected_from_uri), "status": "400", "headers": headers, }
def handler(event, _context): """Handle the authorization parsing. Args: event (Any): The Lambda Event. _context (Any): Lambda context object. """ request = event["Records"][0]["cf"]["request"] domain_name = request["headers"]["host"][0]["value"] redirected_from_uri = "https://%s" % domain_name id_token = None # Attempt to parse the request and retrieve authorization # tokens to integrate with our header cookies try: # Get all the cookies from the headers cookies = extract_and_parse_cookies(request.get("headers"), CONFIG["client_id"]) id_token = cookies["id_token"] code, pkce, requested_uri = validate_querystring_and_cookies( request, cookies) redirected_from_uri += requested_uri # Request tokens from our Cognito Authorization Domain body = { "grant_type": "authorization_code", "client_id": CONFIG["client_id"], "redirect_uri": "https://%s%s" % (domain_name, CONFIG.get("redirect_path_sign_in")), "code": code[0], "code_verifier": pkce, } tokens = http_post_with_retry( COGNITO_TOKEN_ENDPOINT, body, {"Content-Type": "application/x-www-form-urlencoded"}, ) if not tokens: raise Exception("Was not able to obtain tokens from Cognito") # Validate the token information against the Cognito JWKS # (and ensure group membership, if applicable) validate_and_check_id_token( tokens["id_token"], CONFIG["token_jwks_uri"], CONFIG["token_issuer"], CONFIG["client_id"], CONFIG.get("required_group"), ) # Redirect user to the originally requested uri with the # token header cookies response = { "status": "307", "statusDescription": "Temporary Redirect", "headers": { "location": [{ "key": "location", "value": redirected_from_uri }], "set-cookie": generate_cookie_headers( "new_tokens", CONFIG.get("client_id"), CONFIG.get("oauth_scopes"), tokens, domain_name, CONFIG.get("cookie_settings"), ), **CONFIG.get("cloud_front_headers", {}), }, } return response except Exception as err: # pylint: disable=broad-except if id_token: # ID token found; checking if it is valid try: validate_and_check_id_token( id_token, CONFIG["token_jwks_uri"], CONFIG["token_issuer"], CONFIG["client_id"], CONFIG.get("required_group"), ) # Token is valid; return user to where they came from return { "status": "307", "statusDescription": "Temporary Redirect", "headers": { "location": [{ "key": "location", "value": redirected_from_uri }], **CONFIG.get("cloud_front_headers", {}), }, } except Exception as err: # pylint: disable=broad-except LOGGER.debug("Id token not valid") LOGGER.debug(err) if isinstance(err, RequiresConfirmationError): html_params = [ "Confirm sign-in", "We need your confirmation to sign you (%s)" % str(err), redirected_from_uri, "Confirm", ] elif isinstance(err, MissingRequiredGroupError): html_params = [ "Not Authorized", "Your user is not authorized for this site. Please contact the admin.", redirected_from_uri, "Try Again", ] else: html_params = [ "Sign-in issue", "Sign-in unsuccessful because of a technical problem: %s" % str(err), redirected_from_uri, "Try Again", ] # Inform user of bad request and reason return { "body": create_error_html(*html_params), "status": "200", "headers": { **CONFIG.get("cloud_front_headers", {}), "content-type": [{ "key": "Content-Type", "value": "text/html; charset=UTF-8" }], }, }
def handler(event, _context): """Handle the authorization refresh. Args: event: The Lambda Event. _context (Any): Lambda context object. """ request = event["Records"][0]["cf"]["request"] domain_name = request["headers"]["host"][0]["value"] redirected_from_uri = "https://%s" % domain_name try: parsed_qs = parse_qs(request.get("querystring")) requested_uri = parsed_qs.get("requestedUri")[0] current_nonce = parsed_qs.get("nonce")[0] # Add the requested uri path to the main redirected_from_uri += requested_uri or "" cookies = extract_and_parse_cookies( request.get("headers"), CONFIG.get("client_id") ) tokens = { "id_token": cookies.get("idToken"), "access_token": cookies.get("accessToken"), "refresh_token": cookies.get("refreshToken"), } validate_refresh_request(current_nonce, cookies.get("nonce"), tokens) try: # Request new tokens based on the refresh_token body = { "grant_type": "refresh_token", "client_id": CONFIG.get("client_id"), "refresh_token": tokens.get("refresh_token"), } res = http_post_with_retry( ("https://%s/oauth2/token" % CONFIG.get("cognito_auth_domain")), body, {"Content-Type": "application/x-www-form-urlencoded"}, ) tokens["id_token"] = res.get("id_token") tokens["access_token"] = res.get("access_token") except Exception as err: # pylint: disable=broad-except LOGGER.error(err) # Otherwise clear the refresh token tokens["refresh_token"] = "" headers = { "location": [{"key": "location", "value": redirected_from_uri}], "set-cookie": get_cookie_headers( CONFIG.get("client_id"), CONFIG.get("oauth_scopes"), tokens, domain_name, CONFIG.get("cookie_settings"), ), } headers.update(CONFIG.get("cloud_front_headers")) # Redirect the user back to their requested uri # with new tokens at hand return { "status": "307", "statusDescription": "Temporary Redirect", "headers": headers, } # Send a basic html error response and inform the user # why refresh was unsuccessful except Exception as err: # pylint: disable=broad-except LOGGER.info(err) LOGGER.info(traceback.print_exc()) headers = { "content-type": [ {"key": "Content-Type", "value": "text/html; charset=UTF-8"} ] } headers.update(CONFIG.get("cloud_front_headers")) return { "body": create_error_html("Bad Request", err, redirected_from_uri), "status": "400", "headers": headers, }