Exemplo n.º 1
0
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
        }
Exemplo n.º 2
0
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
        }
Exemplo n.º 3
0
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,
        }
Exemplo n.º 4
0
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"
                }],
            },
        }
Exemplo n.º 5
0
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,
        }