updates
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
|
||||
from annotated_doc import Doc
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.openapi.models import OAuth2 as OAuth2Model
|
||||
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
||||
@@ -7,10 +8,10 @@ from fastapi.param_functions import Form
|
||||
from fastapi.security.base import SecurityBase
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from starlette.requests import Request
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
# TODO: import from typing when deprecating Python 3.9
|
||||
from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
class OAuth2PasswordRequestForm:
|
||||
@@ -52,9 +53,9 @@ class OAuth2PasswordRequestForm:
|
||||
```
|
||||
|
||||
Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
|
||||
You could have custom internal logic to separate it by colon caracters (`:`) or
|
||||
You could have custom internal logic to separate it by colon characters (`:`) or
|
||||
similar, and get the two parts `items` and `read`. Many applications do that to
|
||||
group and organize permisions, you could do it as well in your application, just
|
||||
group and organize permissions, you could do it as well in your application, just
|
||||
know that that it is application specific, it's not part of the specification.
|
||||
"""
|
||||
|
||||
@@ -63,7 +64,7 @@ class OAuth2PasswordRequestForm:
|
||||
*,
|
||||
grant_type: Annotated[
|
||||
Union[str, None],
|
||||
Form(pattern="password"),
|
||||
Form(pattern="^password$"),
|
||||
Doc(
|
||||
"""
|
||||
The OAuth2 spec says it is required and MUST be the fixed string
|
||||
@@ -85,11 +86,11 @@ class OAuth2PasswordRequestForm:
|
||||
],
|
||||
password: Annotated[
|
||||
str,
|
||||
Form(),
|
||||
Form(json_schema_extra={"format": "password"}),
|
||||
Doc(
|
||||
"""
|
||||
`password` string. The OAuth2 spec requires the exact field name
|
||||
`password".
|
||||
`password`.
|
||||
"""
|
||||
),
|
||||
],
|
||||
@@ -130,7 +131,7 @@ class OAuth2PasswordRequestForm:
|
||||
] = None,
|
||||
client_secret: Annotated[
|
||||
Union[str, None],
|
||||
Form(),
|
||||
Form(json_schema_extra={"format": "password"}),
|
||||
Doc(
|
||||
"""
|
||||
If there's a `client_password` (and a `client_id`), they can be sent
|
||||
@@ -194,9 +195,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
||||
```
|
||||
|
||||
Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
|
||||
You could have custom internal logic to separate it by colon caracters (`:`) or
|
||||
You could have custom internal logic to separate it by colon characters (`:`) or
|
||||
similar, and get the two parts `items` and `read`. Many applications do that to
|
||||
group and organize permisions, you could do it as well in your application, just
|
||||
group and organize permissions, you could do it as well in your application, just
|
||||
know that that it is application specific, it's not part of the specification.
|
||||
|
||||
|
||||
@@ -217,7 +218,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
||||
self,
|
||||
grant_type: Annotated[
|
||||
str,
|
||||
Form(pattern="password"),
|
||||
Form(pattern="^password$"),
|
||||
Doc(
|
||||
"""
|
||||
The OAuth2 spec says it is required and MUST be the fixed string
|
||||
@@ -243,7 +244,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
||||
Doc(
|
||||
"""
|
||||
`password` string. The OAuth2 spec requires the exact field name
|
||||
`password".
|
||||
`password`.
|
||||
"""
|
||||
),
|
||||
],
|
||||
@@ -353,7 +354,7 @@ class OAuth2(SecurityBase):
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
By default, if no HTTP Auhtorization header is provided, required for
|
||||
By default, if no HTTP Authorization header is provided, required for
|
||||
OAuth2 authentication, it will automatically cancel the request and
|
||||
send the client an error.
|
||||
|
||||
@@ -376,13 +377,33 @@ class OAuth2(SecurityBase):
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
def make_not_authenticated_error(self) -> HTTPException:
|
||||
"""
|
||||
The OAuth 2 specification doesn't define the challenge that should be used,
|
||||
because a `Bearer` token is not really the only option to authenticate.
|
||||
|
||||
But declaring any other authentication challenge would be application-specific
|
||||
as it's not defined in the specification.
|
||||
|
||||
For practical reasons, this method uses the `Bearer` challenge by default, as
|
||||
it's probably the most common one.
|
||||
|
||||
If you are implementing an OAuth2 authentication scheme other than the provided
|
||||
ones in FastAPI (based on bearer tokens), you might want to override this.
|
||||
|
||||
Ref: https://datatracker.ietf.org/doc/html/rfc6749
|
||||
"""
|
||||
return HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
authorization = request.headers.get("Authorization")
|
||||
if not authorization:
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
raise self.make_not_authenticated_error()
|
||||
else:
|
||||
return None
|
||||
return authorization
|
||||
@@ -441,7 +462,7 @@ class OAuth2PasswordBearer(OAuth2):
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
By default, if no HTTP Auhtorization header is provided, required for
|
||||
By default, if no HTTP Authorization header is provided, required for
|
||||
OAuth2 authentication, it will automatically cancel the request and
|
||||
send the client an error.
|
||||
|
||||
@@ -457,11 +478,26 @@ class OAuth2PasswordBearer(OAuth2):
|
||||
"""
|
||||
),
|
||||
] = True,
|
||||
refreshUrl: Annotated[
|
||||
Optional[str],
|
||||
Doc(
|
||||
"""
|
||||
The URL to refresh the token and obtain a new one.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
):
|
||||
if not scopes:
|
||||
scopes = {}
|
||||
flows = OAuthFlowsModel(
|
||||
password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes})
|
||||
password=cast(
|
||||
Any,
|
||||
{
|
||||
"tokenUrl": tokenUrl,
|
||||
"refreshUrl": refreshUrl,
|
||||
"scopes": scopes,
|
||||
},
|
||||
)
|
||||
)
|
||||
super().__init__(
|
||||
flows=flows,
|
||||
@@ -475,11 +511,7 @@ class OAuth2PasswordBearer(OAuth2):
|
||||
scheme, param = get_authorization_scheme_param(authorization)
|
||||
if not authorization or scheme.lower() != "bearer":
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
raise self.make_not_authenticated_error()
|
||||
else:
|
||||
return None
|
||||
return param
|
||||
@@ -543,7 +575,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
||||
bool,
|
||||
Doc(
|
||||
"""
|
||||
By default, if no HTTP Auhtorization header is provided, required for
|
||||
By default, if no HTTP Authorization header is provided, required for
|
||||
OAuth2 authentication, it will automatically cancel the request and
|
||||
send the client an error.
|
||||
|
||||
@@ -585,11 +617,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
||||
scheme, param = get_authorization_scheme_param(authorization)
|
||||
if not authorization or scheme.lower() != "bearer":
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
raise self.make_not_authenticated_error()
|
||||
else:
|
||||
return None # pragma: nocover
|
||||
return param
|
||||
|
||||
Reference in New Issue
Block a user