Skip to content

Allow more control over exceptions #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The authentication success and failure displayed in the browser were revamped to be more user-friendly. `requests_auth.testing` was modified to accommodate this change:
- `tab.assert_success` `expected_message` parameter was removed.
- `tab.assert_failure` `expected_message` parameter should not be prefixed with `Unable to properly perform authentication: ` anymore and `\n` in the message should be replaced with `<br>`.
- Exceptions issued by `requests_auth` are now inheriting from `requests_auth.RequestsAuthException`, itself inheriting from `requests.RequestException`, instead of `Exception`.

### Fixed
- Type information is now provided following [PEP 561](https://www.python.org/dev/peps/pep-0561/).
2 changes: 2 additions & 0 deletions requests_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@
InvalidToken,
TokenExpiryNotProvided,
InvalidGrantRequest,
RequestsAuthException,
)
from requests_auth.version import __version__

@@ -67,6 +68,7 @@
"SupportMultiAuth",
"JsonTokenFileCache",
"TokenMemoryCache",
"RequestsAuthException",
"GrantNotProvided",
"TimeoutOccurred",
"AuthenticationFailed",
35 changes: 20 additions & 15 deletions requests_auth/_errors.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
from json import JSONDecodeError
from typing import Union

from requests import Response
from requests import Response, RequestException


class AuthenticationFailed(Exception):
class RequestsAuthException(RequestException): ...


class AuthenticationFailed(RequestsAuthException):
"""User was not authenticated."""

def __init__(self):
Exception.__init__(self, "User was not authenticated.")
RequestsAuthException.__init__(self, "User was not authenticated.")


class TimeoutOccurred(Exception):
class TimeoutOccurred(RequestsAuthException):
"""No response within timeout interval."""

def __init__(self, timeout: float):
Exception.__init__(
RequestsAuthException.__init__(
self, f"User authentication was not received within {timeout} seconds."
)


class InvalidToken(Exception):
class InvalidToken(RequestsAuthException):
"""Token is invalid."""

def __init__(self, token_name: str):
Exception.__init__(self, f"{token_name} is invalid.")
RequestsAuthException.__init__(self, f"{token_name} is invalid.")


class GrantNotProvided(Exception):
class GrantNotProvided(RequestsAuthException):
"""Grant was not provided."""

def __init__(self, grant_name: str, dictionary_without_grant: dict):
Exception.__init__(
RequestsAuthException.__init__(
self, f"{grant_name} not provided within {dictionary_without_grant}."
)


class InvalidGrantRequest(Exception):
class InvalidGrantRequest(RequestsAuthException):
"""
If the request failed client authentication or is invalid, the authorization server returns an error response as described in https://tools.ietf.org/html/rfc6749#section-5.2
"""
@@ -64,7 +67,7 @@ class InvalidGrantRequest(Exception):
}

def __init__(self, response: Union[Response, dict]):
Exception.__init__(self, InvalidGrantRequest.to_message(response))
RequestsAuthException.__init__(self, InvalidGrantRequest.to_message(response))

@staticmethod
def to_message(response: Union[Response, dict]) -> str:
@@ -114,17 +117,19 @@ def _pop(key: str) -> str:
return message


class StateNotProvided(Exception):
class StateNotProvided(RequestsAuthException):
"""State was not provided."""

def __init__(self, dictionary_without_state: dict):
Exception.__init__(
RequestsAuthException.__init__(
self, f"state not provided within {dictionary_without_state}."
)


class TokenExpiryNotProvided(Exception):
class TokenExpiryNotProvided(RequestsAuthException):
"""Token expiry was not provided."""

def __init__(self, token_body: dict):
Exception.__init__(self, f"Expiry (exp) is not provided in {token_body}.")
RequestsAuthException.__init__(
self, f"Expiry (exp) is not provided in {token_body}."
)
3 changes: 3 additions & 0 deletions tests/features/token_cache/test_json_token_file_cache.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

import pytest
import jwt
import requests

import requests_auth
import requests_auth._oauth2.tokens
@@ -78,6 +79,8 @@ def failing_dump(*args):
with pytest.raises(requests_auth.AuthenticationFailed) as exception_info:
same_cache.get_token("key1")
assert str(exception_info.value) == "User was not authenticated."
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)

assert caplog.messages == [
"Cannot save tokens.",
Original file line number Diff line number Diff line change
@@ -151,9 +151,12 @@ def test_oauth2_authorization_code_flow_uses_custom_failure(
displayed_html="FAILURE: {display_time}\n{information}",
)

with pytest.raises(requests_auth.InvalidGrantRequest):
with pytest.raises(requests_auth.InvalidGrantRequest) as exception_info:
requests.get("http://authorized_only", auth=auth)

assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)

tab.assert_failure(
"invalid_request: The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed."
)
@@ -478,6 +481,8 @@ def test_empty_token_is_invalid(
str(exception_info.value)
== "access_token not provided within {'access_token': '', 'token_type': 'example', 'expires_in': 3600, 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', 'example_parameter': 'example_value'}."
)
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)
tab.assert_success()


8 changes: 8 additions & 0 deletions tests/oauth2/implicit/test_oauth2_implicit.py
Original file line number Diff line number Diff line change
@@ -308,6 +308,8 @@ def open(self, url, new):
str(exception_info.value)
== "User authentication was not received within 0.1 seconds."
)
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)


def test_browser_error(token_cache, responses: RequestsMock, monkeypatch):
@@ -371,6 +373,8 @@ def test_empty_token_is_invalid(token_cache, browser_mock: BrowserMock):
auth=requests_auth.OAuth2Implicit("http://provide_token"),
)
assert str(exception_info.value) == " is invalid."
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)
tab.assert_success()


@@ -386,6 +390,8 @@ def test_token_without_expiry_is_invalid(token_cache, browser_mock: BrowserMock)
auth=requests_auth.OAuth2Implicit("http://provide_token"),
)
assert str(exception_info.value) == "Expiry (exp) is not provided in None."
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)
tab.assert_success()


@@ -612,6 +618,8 @@ def test_oauth2_implicit_flow_post_failure_if_state_is_not_provided(
str(exception_info.value)
== f"state not provided within {{'access_token': ['{token}']}}."
)
assert isinstance(exception_info.value, requests_auth.RequestsAuthException)
assert isinstance(exception_info.value, requests.RequestException)
tab.assert_failure(f"state not provided within {{'access_token': ['{token}']}}.")