Ejemplo n.º 1
0
def google_openid_connect():
    """Handler for the Google OpenID Connect callback.

    Reference:
    https://developers.google.com/identity/protocols/OpenIDConnect

    Returns:
        Redirect response.
    """
    error = request.args.get('error', None)

    if error:
        current_app.logger.error('OAuth2 flow error: {}'.format(error))
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     'OAuth2 flow error: {0!s}'.format(error))

    try:
        code = request.args['code']
        client_csrf_token = request.args.get('state')
        server_csrf_token = session[CSRF_KEY]
    except KeyError as e:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     'Client CSRF error, no CSRF key stored')

    if client_csrf_token != server_csrf_token:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid CSRF token')

    try:
        encoded_jwt = get_encoded_jwt_over_https(code)
    except JwtFetchError as e:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     'Jwt Fetch error, {0!s}'.format(e))

    try:
        discovery_document = get_oauth2_discovery_document()
    except DiscoveryDocumentError as e:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            'Unable to discover document, with error: {0!s}'.format(e))

    algorithm = discovery_document['id_token_signing_alg_values_supported'][0]
    expected_audience = current_app.config.get('GOOGLE_OIDC_CLIENT_ID')
    expected_domain = current_app.config.get('GOOGLE_OIDC_HOSTED_DOMAIN')
    expected_issuer = discovery_document['issuer']

    # Fetch the public key and try to validate the JWT.
    try:
        public_key = get_public_key_for_jwt(encoded_jwt,
                                            discovery_document['jwks_uri'])
        decoded_jwt = decode_jwt(encoded_jwt, public_key, algorithm,
                                 expected_audience)
        validate_jwt(decoded_jwt, expected_issuer, expected_domain)
    except (JwtValidationError, JwtKeyError) as e:
        current_app.logger.error('{}'.format(e))
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                     'Unable to validate request, with error: {0!s}'.format(e))

    validated_email = decoded_jwt.get('email')
    user_whitelist = current_app.config.get('GOOGLE_OIDC_USER_WHITELIST')

    # Check if the authenticating user is on the whitelist.
    if user_whitelist:
        if validated_email not in user_whitelist:
            return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                         'Unauthorized request, user not in whitelist')

    user = User.get_or_create(username=validated_email, name=validated_email)
    login_user(user)

    # Log the user in and setup the session.
    if current_user.is_authenticated:
        return redirect(request.args.get('next') or '/')

    return abort(HTTP_STATUS_CODE_BAD_REQUEST, 'User is not authenticated.')
Ejemplo n.º 2
0
def validate_api_token():
    """Handler for logging in using an authenticated session for the API.

    Returns:
        A simple page indicating the user is authenticated.
    """
    try:
        token = oauth2.rfc6749.tokens.get_token_from_header(request)
    except AttributeError:
        token = None

    if not token:
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                     'Request not authenticated.')

    id_token = request.args.get('id_token')
    if not id_token:
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED, 'No ID token supplied.')

    client_id = current_app.config.get('GOOGLE_OIDC_API_CLIENT_ID')
    if not client_id:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            'No OIDC API client ID defined in the configuration file.')

    # Authenticating session, see more details here:
    # https://www.oauth.com/oauth2-servers/signing-in-with-google/\
    #     verifying-the-user-info/
    # Sending a request to Google to verify that the access token
    # is valid, to be able to validate the session.
    data = {'access_token': token}
    bearer_token_response = requests.post(TOKEN_URI, data=data)
    if bearer_token_response.status_code != HTTP_STATUS_CODE_OK:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     'Unable to validate access token.')
    bearer_token_json = bearer_token_response.json()

    data = {'id_token': id_token}
    token_response = requests.post(TOKEN_URI, data=data)
    token_json = token_response.json()

    verified = token_json.get('email_verified', False)
    if not verified:
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                     'Session not authenticated or account not verified')

    if bearer_token_json.get('azp', 'a') != token_json.get('azp', 'x'):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            'Auth token and client tokens don\'t match, azp differs.')

    if bearer_token_json.get('email', 'a') != token_json.get('email', 'b'):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            'Auth token and client tokens don\'t match, email differs.')

    try:
        discovery_document = get_oauth2_discovery_document()
    except DiscoveryDocumentError as e:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            'Unable to discover document, with error: {0!s}'.format(e))

    expected_issuer = discovery_document['issuer']
    try:
        validate_jwt(token_json, expected_issuer)
    except (ImportError, NameError, UnboundLocalError):
        raise
    except (JwtValidationError, JwtKeyError, Exception) as e:  # pylint: disable=broad-except
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            'Unable to validate the JWT token, with error: {0!s}.'.format(e))

    read_client_id = token_json.get('aud', '')
    if read_client_id != client_id:
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            'Client ID {0:s} does not match server configuration for '
            'client'.format(read_client_id))

    read_scopes = bearer_token_json.get('scope', '').split()
    if not set(read_scopes) == set(SCOPES):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            'Client scopes differ from what they should be (email, openid, '
            'profile) = {} VS {}'.format(SCOPES, read_scopes))

    validated_email = token_json.get('email')

    # Check if the authenticating user is part of the allowed domains.
    domain_whitelist = current_app.config.get('GOOGLE_OIDC_HOSTED_DOMAIN')
    if domain_whitelist:
        _, _, domain = validated_email.partition('@')
        if domain.lower() != domain_whitelist.lower():
            return abort(
                HTTP_STATUS_CODE_UNAUTHORIZED,
                'Domain {0:s} is not allowed to authenticate against this '
                'instance.'.format(domain))

    user_whitelist = current_app.config.get('GOOGLE_OIDC_USER_WHITELIST')
    # Check if the authenticating user is on the whitelist.
    if user_whitelist:
        if validated_email not in user_whitelist:
            return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                         'Unauthorized request, user not in whitelist')

    user = User.get_or_create(username=validated_email, name=validated_email)
    login_user(user)

    # Log the user in and setup the session.
    if current_user.is_authenticated:
        return """
<h1>Authenticated</h1>
        """

    return abort(HTTP_STATUS_CODE_BAD_REQUEST, 'User is not authenticated.')
Ejemplo n.º 3
0
def google_openid_connect():
    """Handler for the Google OpenID Connect callback.

    Reference:
    https://developers.google.com/identity/protocols/OpenIDConnect

    Returns:
        Redirect response.
    """
    error = request.args.get("error", None)

    if error:
        current_app.logger.error("OAuth2 flow error: {}".format(error))
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     "OAuth2 flow error: {0!s}".format(error))

    try:
        code = request.args["code"]
        client_csrf_token = request.args.get("state")
        server_csrf_token = session[CSRF_KEY]
    except KeyError as e:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     "Client CSRF error, no CSRF key stored")

    if client_csrf_token != server_csrf_token:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST, "Invalid CSRF token")

    try:
        encoded_jwt = get_encoded_jwt_over_https(code)
    except JwtFetchError as e:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     "Jwt Fetch error, {0!s}".format(e))

    try:
        discovery_document = get_oauth2_discovery_document()
    except DiscoveryDocumentError as e:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            "Unable to discover document, with error: {0!s}".format(e),
        )

    algorithm = discovery_document["id_token_signing_alg_values_supported"][0]
    expected_audience = current_app.config.get("GOOGLE_OIDC_CLIENT_ID")
    expected_domain = current_app.config.get("GOOGLE_OIDC_HOSTED_DOMAIN")
    expected_issuer = discovery_document["issuer"]

    # Fetch the public key and try to validate the JWT.
    try:
        public_key = get_public_key_for_jwt(encoded_jwt,
                                            discovery_document["jwks_uri"])
        decoded_jwt = decode_jwt(encoded_jwt, public_key, algorithm,
                                 expected_audience)
        validate_jwt(decoded_jwt, expected_issuer, expected_domain)
    except (JwtValidationError, JwtKeyError) as e:
        current_app.logger.error("{}".format(e))
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Unable to validate request, with error: {0!s}".format(e),
        )

    validated_email = decoded_jwt.get("email")
    allowed_users = current_app.config.get("GOOGLE_OIDC_ALLOWED_USERS")

    # Check if the authenticating user is allowed.
    if allowed_users:
        if validated_email not in allowed_users:
            return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                         "Unauthorized request, user not allowed")

    user = User.get_or_create(username=validated_email, name=validated_email)
    login_user(user)

    # Log the user in and setup the session.
    if current_user.is_authenticated:
        return redirect(request.args.get("next") or "/")

    return abort(HTTP_STATUS_CODE_BAD_REQUEST, "User is not authenticated.")
Ejemplo n.º 4
0
def google_openid_connect():
    """Handler for the Google OpenID Connect callback.

    Reference:
    https://developers.google.com/identity/protocols/OpenIDConnect

    Returns:
        Redirect response.
    """
    error = request.args.get('error', None)

    if error:
        current_app.logger.error('OAuth2 flow error: {}'.format(error))
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)

    try:
        code = request.args['code']
        client_csrf_token = request.args.get('state')
        server_csrf_token = session[CSRF_KEY]
    except KeyError:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)

    if client_csrf_token != server_csrf_token:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid CSRF token')

    try:
        encoded_jwt = get_encoded_jwt_over_https(code)
    except JwtFetchError:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)

    try:
        discovery_document = get_oauth2_discovery_document()
    except DiscoveryDocumentError:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)

    algorithm = discovery_document['id_token_signing_alg_values_supported'][0]
    expected_audience = current_app.config.get('GOOGLE_OIDC_CLIENT_ID')
    expected_domain = current_app.config.get('GOOGLE_OIDC_HOSTED_DOMAIN')
    expected_issuer = discovery_document['issuer']

    # Fetch the public key and try to validate the JWT.
    try:
        public_key = get_public_key_for_jwt(
            encoded_jwt, discovery_document['jwks_uri'])
        validated_jwt = validate_jwt(
            encoded_jwt, public_key, algorithm, expected_audience,
            expected_issuer, expected_domain)
    except (JwtValidationError, JwtKeyError) as e:
        current_app.logger.error('{}'.format(e))
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED)

    validated_email = validated_jwt.get('email')
    user_whitelist = current_app.config.get('GOOGLE_OIDC_USER_WHITELIST')

    # Check if the authenticating user is on the whitelist.
    if user_whitelist:
        if validated_email not in user_whitelist:
            return abort(HTTP_STATUS_CODE_UNAUTHORIZED)

    user = User.get_or_create(username=validated_email, name=validated_email)
    login_user(user)

    # Log the user in and setup the session.
    if current_user.is_authenticated:
        return redirect(request.args.get('next') or '/')

    return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Ejemplo n.º 5
0
def validate_api_token():
    """Handler for logging in using an authenticated session for the API.

    Returns:
        A simple page indicating the user is authenticated.
    """
    ALLOWED_CLIENT_IDS = []

    try:
        token = oauth2.rfc6749.tokens.get_token_from_header(request)
    except AttributeError:
        token = None

    if not token:
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                     "Request not authenticated.")

    id_token = request.args.get("id_token")
    if not id_token:
        return abort(HTTP_STATUS_CODE_UNAUTHORIZED, "No ID token supplied.")

    client_ids = set()
    primary_client_id = current_app.config.get("GOOGLE_OIDC_CLIENT_ID")
    legacy_api_client_id = current_app.config.get("GOOGLE_OIDC_API_CLIENT_ID")
    api_client_ids = current_app.config.get("GOOGLE_OIDC_API_CLIENT_IDS", [])

    if primary_client_id:
        client_ids.add(primary_client_id)

    if legacy_api_client_id:
        client_ids.add(legacy_api_client_id)

    if api_client_ids:
        client_ids.update(api_client_ids)

    ALLOWED_CLIENT_IDS = list(client_ids)

    if not ALLOWED_CLIENT_IDS:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            "No OIDC client IDs defined in the configuration file.",
        )

    # Authenticating session, see more details here:
    # https://www.oauth.com/oauth2-servers/signing-in-with-google/\
    #     verifying-the-user-info/
    # Sending a request to Google to verify that the access token
    # is valid, to be able to validate the session.
    data = {"access_token": token}
    bearer_token_response = requests.post(TOKEN_URI, data=data)
    if bearer_token_response.status_code != HTTP_STATUS_CODE_OK:
        return abort(HTTP_STATUS_CODE_BAD_REQUEST,
                     "Unable to validate access token.")
    bearer_token_json = bearer_token_response.json()

    data = {"id_token": id_token}
    token_response = requests.post(TOKEN_URI, data=data)
    token_json = token_response.json()

    verified = token_json.get("email_verified", False)
    if not verified:
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Session not authenticated or account not verified",
        )

    if bearer_token_json.get("azp", "a") != token_json.get("azp", "x"):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Auth token and client tokens don't match, azp differs.",
        )

    if bearer_token_json.get("email", "a") != token_json.get("email", "b"):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Auth token and client tokens don't match, email differs.",
        )

    try:
        discovery_document = get_oauth2_discovery_document()
    except DiscoveryDocumentError as e:
        return abort(
            HTTP_STATUS_CODE_BAD_REQUEST,
            "Unable to discover document, with error: {0!s}".format(e),
        )

    expected_issuer = discovery_document["issuer"]
    # pylint: disable=broad-except
    try:
        validate_jwt(token_json, expected_issuer)
    except (ImportError, NameError, UnboundLocalError):
        raise
    except (
            JwtValidationError,
            JwtKeyError,
            Exception,
    ) as e:
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Unable to validate the JWT token, with error: {0!s}.".format(e),
        )

    read_client_id = token_json.get("aud", "")
    if read_client_id not in ALLOWED_CLIENT_IDS:
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Client ID {0:s} does not match server configuration for "
            "client".format(read_client_id),
        )

    read_scopes = bearer_token_json.get("scope", "").split()
    if not set(read_scopes) == set(SCOPES):
        return abort(
            HTTP_STATUS_CODE_UNAUTHORIZED,
            "Client scopes differ from what they should be (email, openid, "
            "profile) = {} VS {}".format(SCOPES, read_scopes),
        )

    validated_email = token_json.get("email")

    # Check if the authenticating user is part of the allowed domains.
    hosted_domains = current_app.config.get("GOOGLE_OIDC_HOSTED_DOMAIN")
    if hosted_domains:
        _, _, domain = validated_email.partition("@")
        if domain.lower() != hosted_domains.lower():
            return abort(
                HTTP_STATUS_CODE_UNAUTHORIZED,
                "Domain {0:s} is not allowed to authenticate against this "
                "instance.".format(domain),
            )

    allowed_users = current_app.config.get("GOOGLE_OIDC_ALLOWED_USERS")
    # TODO: Remove that after a 6 months, this following check is to ensure
    # compatibility of config file
    if not allowed_users:
        current_app.logger.warning("Warning, GOOGLE_OIDC_USER_WHITELIST has "
                                   "been deprecated. Please update "
                                   "timesketch.conf.")
        allowed_users = current_app.config.get("GOOGLE_OIDC_USER_WHITELIST",
                                               [])

    # Check if the authenticating user is on the allow list.
    if allowed_users:
        if validated_email not in allowed_users:
            return abort(HTTP_STATUS_CODE_UNAUTHORIZED,
                         "Unauthorized request, user not allowed")

    user = User.get_or_create(username=validated_email, name=validated_email)
    login_user(user, remember=True)

    # Log the user in and setup the session.
    if current_user.is_authenticated:
        return """
<h1>Authenticated</h1>
        """

    return abort(HTTP_STATUS_CODE_BAD_REQUEST, "User is not authenticated.")