Skip to content

Commit

Permalink
Merge pull request #118 from aliev/feature/remove-user-dependency
Browse files Browse the repository at this point in the history
feat: remove user
  • Loading branch information
aliev authored Feb 9, 2025
2 parents b36943a + f42764d commit 2a3bcbf
Show file tree
Hide file tree
Showing 24 changed files with 454 additions and 369 deletions.
38 changes: 19 additions & 19 deletions aioauth/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
"""

from http import HTTPStatus
from typing import Generic, Optional
from typing import Optional
from urllib.parse import urljoin
from .requests import Request

from .collections import HTTPHeaderDict
from .constances import default_headers
from .types import ErrorType, UserType
from .types import ErrorType


class OAuth2Error(Generic[UserType], Exception):
class OAuth2Error(Exception):
"""Base exception that all other exceptions inherit from."""

error: ErrorType
Expand All @@ -30,7 +30,7 @@ class OAuth2Error(Generic[UserType], Exception):

def __init__(
self,
request: Request[UserType],
request: Request,
description: Optional[str] = None,
headers: Optional[HTTPHeaderDict] = None,
state: Optional[str] = None,
Expand All @@ -52,7 +52,7 @@ def __init__(
super().__init__(f"({self.error}) {self.description}")


class MethodNotAllowedError(Generic[UserType], OAuth2Error[UserType]):
class MethodNotAllowedError(OAuth2Error):
"""
The request is valid, but the method trying to be accessed is not
available to the resource owner.
Expand All @@ -63,7 +63,7 @@ class MethodNotAllowedError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "method_is_not_allowed"


class InvalidRequestError(Generic[UserType], OAuth2Error[UserType]):
class InvalidRequestError(OAuth2Error):
"""
The request is missing a required parameter, includes an invalid
parameter value, includes a parameter more than once, or is
Expand All @@ -73,7 +73,7 @@ class InvalidRequestError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "invalid_request"


class InvalidClientError(Generic[UserType], OAuth2Error[UserType]):
class InvalidClientError(OAuth2Error):
"""
Client authentication failed (e.g. unknown client, no client
authentication included, or unsupported authentication method).
Expand Down Expand Up @@ -108,14 +108,14 @@ def __init__(
self.headers["WWW-Authenticate"] = "Basic " + ", ".join(auth_values)


class InsecureTransportError(Generic[UserType], OAuth2Error[UserType]):
class InsecureTransportError(OAuth2Error):
"""An exception will be thrown if the current request is not secure."""

description = "OAuth 2 MUST utilize https."
error: ErrorType = "insecure_transport"


class UnsupportedGrantTypeError(Generic[UserType], OAuth2Error[UserType]):
class UnsupportedGrantTypeError(OAuth2Error):
"""
The authorization grant type is not supported by the authorization
server.
Expand All @@ -124,7 +124,7 @@ class UnsupportedGrantTypeError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "unsupported_grant_type"


class UnsupportedResponseTypeError(Generic[UserType], OAuth2Error[UserType]):
class UnsupportedResponseTypeError(OAuth2Error):
"""
The authorization server does not support obtaining an authorization
code using this method.
Expand All @@ -133,7 +133,7 @@ class UnsupportedResponseTypeError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "unsupported_response_type"


class InvalidGrantError(Generic[UserType], OAuth2Error[UserType]):
class InvalidGrantError(OAuth2Error):
"""
The provided authorization grant (e.g. authorization code, resource
owner credentials) or refresh token is invalid, expired, revoked, does
Expand All @@ -146,14 +146,14 @@ class InvalidGrantError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "invalid_grant"


class MismatchingStateError(Generic[UserType], OAuth2Error[UserType]):
class MismatchingStateError(OAuth2Error):
"""Unable to securely verify the integrity of the request and response."""

description = "CSRF Warning! State not equal in request and response."
error: ErrorType = "mismatching_state"


class UnauthorizedClientError(Generic[UserType], OAuth2Error[UserType]):
class UnauthorizedClientError(OAuth2Error):
"""
The authenticated client is not authorized to use this authorization
grant type.
Expand All @@ -162,7 +162,7 @@ class UnauthorizedClientError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "unauthorized_client"


class InvalidScopeError(Generic[UserType], OAuth2Error[UserType]):
class InvalidScopeError(OAuth2Error):
"""
The requested scope is invalid, unknown, or malformed, or
exceeds the scope granted by the resource owner.
Expand All @@ -173,7 +173,7 @@ class InvalidScopeError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "invalid_scope"


class ServerError(Generic[UserType], OAuth2Error[UserType]):
class ServerError(OAuth2Error):
"""
The authorization server encountered an unexpected condition that
prevented it from fulfilling the request. (This error code is needed
Expand All @@ -185,7 +185,7 @@ class ServerError(Generic[UserType], OAuth2Error[UserType]):
status_code: HTTPStatus = HTTPStatus.BAD_REQUEST


class TemporarilyUnavailableError(Generic[UserType], OAuth2Error[UserType]):
class TemporarilyUnavailableError(OAuth2Error):
"""
The authorization server is currently unable to handle the request
due to a temporary overloading or maintenance of the server.
Expand All @@ -196,15 +196,15 @@ class TemporarilyUnavailableError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "temporarily_unavailable"


class InvalidRedirectURIError(Generic[UserType], OAuth2Error[UserType]):
class InvalidRedirectURIError(OAuth2Error):
"""
The requested redirect URI is missing or not allowed.
"""

error: ErrorType = "invalid_request"


class UnsupportedTokenTypeError(Generic[UserType], OAuth2Error[UserType]):
class UnsupportedTokenTypeError(OAuth2Error):
"""
The authorization server does not support the revocation of the presented
token type. That is, the client tried to revoke an access token on a server
Expand All @@ -214,7 +214,7 @@ class UnsupportedTokenTypeError(Generic[UserType], OAuth2Error[UserType]):
error: ErrorType = "unsupported_token_type"


class AccessDeniedError(Generic[UserType], OAuth2Error[UserType]):
class AccessDeniedError(OAuth2Error):
"""
The resource owner or authorization server denied the request
"""
Expand Down
61 changes: 30 additions & 31 deletions aioauth/grant_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
----
"""

from typing import Generic, Optional
from typing import Optional

from .requests import Request
from .types import UserType
from .storage import BaseStorage
from .errors import (
InvalidClientError,
Expand All @@ -27,12 +26,12 @@
from .utils import enforce_list, enforce_str, generate_token


class GrantTypeBase(Generic[UserType]):
class GrantTypeBase:
"""Base grant type that all other grant types inherit from."""

def __init__(
self,
storage: BaseStorage[UserType],
storage: BaseStorage,
client_id: str,
client_secret: Optional[str],
):
Expand All @@ -42,7 +41,7 @@ def __init__(
self.scope: Optional[str] = None

async def create_token_response(
self, request: Request[UserType], client: Client[UserType]
self, request: Request, client: Client
) -> TokenResponse:
"""Creates token response to reply to client."""
if self.scope is None:
Expand All @@ -65,28 +64,28 @@ async def create_token_response(
token_type=token.token_type,
)

async def validate_request(self, request: Request[UserType]) -> Client[UserType]:
async def validate_request(self, request: Request) -> Client:
"""Validates the client request to ensure it is valid."""
client = await self.storage.get_client(
request=request, client_id=self.client_id, client_secret=self.client_secret
)

if not client:
raise InvalidClientError[UserType](
raise InvalidClientError(
request=request, description="Invalid client_id parameter value."
)

if not client.check_grant_type(request.post.grant_type):
raise UnauthorizedClientError[UserType](request=request)
raise UnauthorizedClientError(request=request)

if not client.check_scope(request.post.scope):
raise InvalidScopeError[UserType](request=request)
raise InvalidScopeError(request=request)

self.scope = request.post.scope
return client


class AuthorizationCodeGrantType(GrantTypeBase[UserType]):
class AuthorizationCodeGrantType(GrantTypeBase):
"""
The Authorization Code grant type is used by confidential and public
clients to exchange an authorization code for an access token. After
Expand All @@ -102,21 +101,21 @@ class AuthorizationCodeGrantType(GrantTypeBase[UserType]):
See `RFC 6749 section 1.3.1 <https://tools.ietf.org/html/rfc6749#section-1.3.1>`_.
"""

async def validate_request(self, request: Request[UserType]) -> Client[UserType]:
async def validate_request(self, request: Request) -> Client:
client = await super().validate_request(request)

if not request.post.redirect_uri:
raise InvalidRedirectURIError[UserType](
raise InvalidRedirectURIError(
request=request, description="Mismatching redirect URI."
)

if not client.check_redirect_uri(request.post.redirect_uri):
raise InvalidRedirectURIError[UserType](
raise InvalidRedirectURIError(
request=request, description="Invalid redirect URI."
)

if not request.post.code:
raise InvalidRequestError[UserType](
raise InvalidRequestError(
request=request, description="Missing code parameter."
)

Expand All @@ -125,31 +124,31 @@ async def validate_request(self, request: Request[UserType]) -> Client[UserType]
)

if not authorization_code:
raise InvalidGrantError[UserType](request=request)
raise InvalidGrantError(request=request)

if (
authorization_code.code_challenge
and authorization_code.code_challenge_method
):
if not request.post.code_verifier:
raise InvalidRequestError[UserType](
raise InvalidRequestError(
request=request, description="Code verifier required."
)

is_valid_code_challenge = authorization_code.check_code_challenge(
request.post.code_verifier
)
if not is_valid_code_challenge:
raise MismatchingStateError[UserType](request=request)
raise MismatchingStateError(request=request)

if authorization_code.is_expired:
raise InvalidGrantError[UserType](request=request)
raise InvalidGrantError(request=request)

self.scope = authorization_code.scope
return client

async def create_token_response(
self, request: Request[UserType], client: Client[UserType]
self, request: Request, client: Client
) -> TokenResponse:
token_response = await super().create_token_response(request, client)

Expand All @@ -165,7 +164,7 @@ async def create_token_response(
return token_response


class PasswordGrantType(GrantTypeBase[UserType]):
class PasswordGrantType(GrantTypeBase):
"""
The Password grant type is a way to exchange a user's credentials
for an access token. Because the client application has to collect
Expand All @@ -176,25 +175,25 @@ class PasswordGrantType(GrantTypeBase[UserType]):
disallows the password grant entirely.
"""

async def validate_request(self, request: Request[UserType]) -> Client[UserType]:
async def validate_request(self, request: Request) -> Client:
client = await super().validate_request(request)

if not request.post.username or not request.post.password:
raise InvalidRequestError[UserType](
raise InvalidRequestError(
request=request, description="Invalid credentials given."
)

user = await self.storage.get_user(request)

if user is None:
raise InvalidRequestError[UserType](
raise InvalidRequestError(
request=request, description="Invalid credentials given."
)

return client


class RefreshTokenGrantType(GrantTypeBase[UserType]):
class RefreshTokenGrantType(GrantTypeBase):
"""
The Refresh Token grant type is used by clients to exchange a
refresh token for an access token when the access token has expired.
Expand All @@ -204,7 +203,7 @@ class RefreshTokenGrantType(GrantTypeBase[UserType]):
"""

async def create_token_response(
self, request: Request[UserType], client: Client[UserType]
self, request: Request, client: Client
) -> TokenResponse:
"""Validate token request and create token response."""
old_token = await self.storage.get_token(
Expand All @@ -216,7 +215,7 @@ async def create_token_response(
)

if not old_token or old_token.revoked or old_token.refresh_token_expired:
raise InvalidGrantError[UserType](request=request)
raise InvalidGrantError(request=request)

# Revoke old token
await self.storage.revoke_token(
Expand Down Expand Up @@ -256,18 +255,18 @@ async def create_token_response(
token_type=token.token_type,
)

async def validate_request(self, request: Request[UserType]) -> Client[UserType]:
async def validate_request(self, request: Request) -> Client:
client = await super().validate_request(request)

if not request.post.refresh_token:
raise InvalidRequestError[UserType](
raise InvalidRequestError(
request=request, description="Missing refresh token parameter."
)

return client


class ClientCredentialsGrantType(GrantTypeBase[UserType]):
class ClientCredentialsGrantType(GrantTypeBase):
"""
The Client Credentials grant type is used by clients to obtain an
access token outside of the context of a user. This is typically
Expand All @@ -276,9 +275,9 @@ class ClientCredentialsGrantType(GrantTypeBase[UserType]):
See `RFC 6749 section 4.4 <https://tools.ietf.org/html/rfc6749#section-4.4>`_.
"""

async def validate_request(self, request: Request[UserType]) -> Client[UserType]:
async def validate_request(self, request: Request) -> Client:
# client_credentials grant requires a client_secret
if self.client_secret is None:
raise InvalidClientError[UserType](request)
raise InvalidClientError(request)

return await super().validate_request(request)
Loading

0 comments on commit 2a3bcbf

Please sign in to comment.