Skip to content
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

Allow more control over exceptions #86

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
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
Expand Up @@ -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/).
Expand Down
2 changes: 2 additions & 0 deletions requests_auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
InvalidToken,
TokenExpiryNotProvided,
InvalidGrantRequest,
RequestsAuthException,
)
from requests_auth.version import __version__

Expand Down Expand Up @@ -67,6 +68,7 @@
"SupportMultiAuth",
"JsonTokenFileCache",
"TokenMemoryCache",
"RequestsAuthException",
"GrantNotProvided",
"TimeoutOccurred",
"AuthenticationFailed",
Expand Down
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
"""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Up @@ -4,6 +4,7 @@

import pytest
import jwt
import requests

import requests_auth
import requests_auth._oauth2.tokens
Expand Down Expand Up @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
Expand Down Expand Up @@ -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()


Expand Down
8 changes: 8 additions & 0 deletions tests/oauth2/implicit/test_oauth2_implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()


Expand All @@ -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()


Expand Down Expand Up @@ -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}']}}.")


Expand Down