mirror of https://github.com/tiangolo/fastapi.git
Add `oauth_scopes`, deprecate `scopes`
This commit is contained in:
parent
81e4a639af
commit
73fd05ccf0
|
|
@ -106,7 +106,17 @@ Now we declare that the *path operation* for `/users/me/items/` requires the sco
|
|||
|
||||
For this, we import and use `Security` from `fastapi`.
|
||||
|
||||
You can use `Security` to declare dependencies (just like `Depends`), but `Security` also receives a parameter `scopes` with a list of scopes (strings).
|
||||
You can use `Security` to declare dependencies (just like `Depends`), but `Security` also receives a parameter `oauth_scopes` with a list of scopes (strings).
|
||||
|
||||
/// note
|
||||
|
||||
Before version 0.121.4, the name of this parameter was `scopes`.
|
||||
|
||||
Since FastAPI 0.121.4, the `scopes` parameter has been deprecated in favor of `oauth_scopes`
|
||||
to avoid confusing it with the `scope` parameter, which is used to specify when the exit code
|
||||
of dependencies with `yield` should run.
|
||||
|
||||
///
|
||||
|
||||
In this case, we pass a dependency function `get_current_active_user` to `Security` (the same way we would do with `Depends`).
|
||||
|
||||
|
|
@ -124,7 +134,7 @@ We are doing it here to demonstrate how **FastAPI** handles scopes declared at d
|
|||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,173] *}
|
||||
|
||||
/// info | Technical Details
|
||||
|
||||
|
|
@ -213,13 +223,13 @@ Here's how the hierarchy of dependencies and scopes looks like:
|
|||
* This `security_scopes` parameter has a property `scopes` with a `list` containing all these scopes declared above, so:
|
||||
* `security_scopes.scopes` will contain `["me", "items"]` for the *path operation* `read_own_items`.
|
||||
* `security_scopes.scopes` will contain `["me"]` for the *path operation* `read_users_me`, because it is declared in the dependency `get_current_active_user`.
|
||||
* `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `scopes`, and its dependency, `get_current_user`, doesn't declare any `scopes` either.
|
||||
* `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `oauth_scopes`, and its dependency, `get_current_user`, doesn't declare any `oauth_scopes` either.
|
||||
|
||||
/// tip
|
||||
|
||||
The important and "magic" thing here is that `get_current_user` will have a different list of `scopes` to check for each *path operation*.
|
||||
|
||||
All depending on the `scopes` declared in each *path operation* and each dependency in the dependency tree for that specific *path operation*.
|
||||
All depending on the `oauth_scopes` declared in each *path operation* and each dependency in the dependency tree for that specific *path operation*.
|
||||
|
||||
///
|
||||
|
||||
|
|
@ -271,4 +281,4 @@ But in the end, they are implementing the same OAuth2 standard.
|
|||
|
||||
## `Security` in decorator `dependencies` { #security-in-decorator-dependencies }
|
||||
|
||||
The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter (as explained in [Dependencies in path operation decorators](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), you could also use `Security` with `scopes` there.
|
||||
The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter (as explained in [Dependencies in path operation decorators](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), you could also use `Security` with `oauth_scopes` there.
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Security(get_current_user, scopes=["me"]),
|
||||
current_user: User = Security(get_current_user, oauth_scopes=["me"]),
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -167,7 +167,7 @@ async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: User = Security(get_current_active_user, scopes=["items"]),
|
||||
current_user: User = Security(get_current_active_user, oauth_scopes=["items"]),
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[User, Security(get_current_user, scopes=["me"])],
|
||||
current_user: Annotated[User, Security(get_current_user, oauth_scopes=["me"])],
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -170,7 +170,9 @@ async def read_users_me(
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])],
|
||||
current_user: Annotated[
|
||||
User, Security(get_current_active_user, oauth_scopes=["items"])
|
||||
],
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[User, Security(get_current_user, scopes=["me"])],
|
||||
current_user: Annotated[User, Security(get_current_user, oauth_scopes=["me"])],
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -169,7 +169,9 @@ async def read_users_me(
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])],
|
||||
current_user: Annotated[
|
||||
User, Security(get_current_active_user, oauth_scopes=["items"])
|
||||
],
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[User, Security(get_current_user, scopes=["me"])],
|
||||
current_user: Annotated[User, Security(get_current_user, oauth_scopes=["me"])],
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -169,7 +169,9 @@ async def read_users_me(
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])],
|
||||
current_user: Annotated[
|
||||
User, Security(get_current_active_user, oauth_scopes=["items"])
|
||||
],
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Security(get_current_user, scopes=["me"]),
|
||||
current_user: User = Security(get_current_user, oauth_scopes=["me"]),
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -166,7 +166,7 @@ async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: User = Security(get_current_active_user, scopes=["items"]),
|
||||
current_user: User = Security(get_current_active_user, oauth_scopes=["items"]),
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ async def get_current_user(
|
|||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Security(get_current_user, scopes=["me"]),
|
||||
current_user: User = Security(get_current_user, oauth_scopes=["me"]),
|
||||
):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
|
@ -167,7 +167,7 @@ async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: User = Security(get_current_active_user, scopes=["items"]),
|
||||
current_user: User = Security(get_current_active_user, oauth_scopes=["items"]),
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De
|
|||
"A parameter-less dependency must have a callable dependency"
|
||||
)
|
||||
use_security_scopes: List[str] = []
|
||||
if isinstance(depends, params.Security) and depends.scopes:
|
||||
use_security_scopes.extend(depends.scopes)
|
||||
if isinstance(depends, params.Security) and depends.oauth_scopes:
|
||||
use_security_scopes.extend(depends.oauth_scopes)
|
||||
return get_dependant(
|
||||
path=path,
|
||||
call=depends.dependency,
|
||||
|
|
@ -276,8 +276,8 @@ def get_dependant(
|
|||
)
|
||||
use_security_scopes = security_scopes or []
|
||||
if isinstance(param_details.depends, params.Security):
|
||||
if param_details.depends.scopes:
|
||||
use_security_scopes.extend(param_details.depends.scopes)
|
||||
if param_details.depends.oauth_scopes:
|
||||
use_security_scopes.extend(param_details.depends.oauth_scopes)
|
||||
sub_dependant = get_dependant(
|
||||
path=path,
|
||||
call=param_details.depends.dependency,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
|
|
@ -2322,6 +2323,29 @@ def Security( # noqa: N802
|
|||
),
|
||||
] = None,
|
||||
*,
|
||||
oauth_scopes: Annotated[
|
||||
Optional[
|
||||
Union[
|
||||
List[str],
|
||||
Tuple[str, ...],
|
||||
Set[str],
|
||||
FrozenSet[str],
|
||||
]
|
||||
],
|
||||
Doc(
|
||||
"""
|
||||
OAuth2 scopes required for the *path operation* that uses this Security
|
||||
dependency.
|
||||
|
||||
The term "scope" comes from the OAuth2 specification, it seems to be
|
||||
intentionally vague and interpretable. It normally refers to permissions,
|
||||
in cases to roles.
|
||||
|
||||
These scopes are integrated with OpenAPI (and the API docs at `/docs`).
|
||||
So they are visible in the OpenAPI specification.
|
||||
"""
|
||||
),
|
||||
] = None,
|
||||
scopes: Annotated[
|
||||
Optional[
|
||||
Union[
|
||||
|
|
@ -2342,6 +2366,26 @@ def Security( # noqa: N802
|
|||
|
||||
These scopes are integrated with OpenAPI (and the API docs at `/docs`).
|
||||
So they are visible in the OpenAPI specification.
|
||||
|
||||
This parameter is deprecated in favor of `oauth_scopes`.
|
||||
"""
|
||||
),
|
||||
deprecated(
|
||||
"""
|
||||
In order to avoid confusion with `scope` parameter, the `scopes` parameter
|
||||
is deprecated in favor of `oauth_scopes`.
|
||||
|
||||
To specify dependency scope for dependencies with `yield` use `scope` parameter:
|
||||
|
||||
```python
|
||||
Security(dependency_fn, scope="function")
|
||||
```
|
||||
|
||||
To specify OAuth2 scopes use `oauth_scopes` parameter:
|
||||
|
||||
```python
|
||||
Security(dependency_fn, oauth_scopes=["items", "users"])
|
||||
```
|
||||
)
|
||||
"""
|
||||
),
|
||||
|
|
@ -2391,23 +2435,28 @@ def Security( # noqa: N802
|
|||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(
|
||||
current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
|
||||
current_user: Annotated[
|
||||
User, Security(get_current_active_user, oauth_scopes=["items"])
|
||||
]
|
||||
):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
```
|
||||
"""
|
||||
|
||||
if isinstance(scopes, str):
|
||||
if scopes in ("function", "request"):
|
||||
raise FastAPIError(
|
||||
"Invalid value for `scopes` parameter in Security(). "
|
||||
"You probably meant to use the `scope` parameter instead of `scopes`. "
|
||||
"Expected a sequence of strings (e.g., ['admin', 'user']), but received a single string."
|
||||
)
|
||||
raise FastAPIError(
|
||||
"Invalid value for `scopes` parameter in Security(). "
|
||||
"Expected a sequence of strings (e.g., ['admin', 'user']), but received a single string. "
|
||||
"Wrap it in a list: scopes=['your_scope'] instead of scopes='your_scope'."
|
||||
if scopes is not None:
|
||||
warnings.warn(
|
||||
(
|
||||
"The 'scopes' parameter in Security() is deprecated in favor of "
|
||||
"'oauth_scopes' in order to avoid confusion with 'scope' parameter."
|
||||
),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)
|
||||
oauth_scopes = oauth_scopes or scopes
|
||||
|
||||
return params.Security(
|
||||
dependency=dependency,
|
||||
oauth_scopes=oauth_scopes,
|
||||
use_cache=use_cache,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -781,7 +781,7 @@ class Depends:
|
|||
|
||||
@dataclass
|
||||
class Security(Depends):
|
||||
scopes: Optional[
|
||||
oauth_scopes: Optional[
|
||||
Union[
|
||||
List[str],
|
||||
Tuple[str, ...],
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ async def get_sub_counter_no_cache(
|
|||
@app.get("/scope-counter")
|
||||
async def get_scope_counter(
|
||||
count: int = Security(dep_counter),
|
||||
scope_count_1: int = Security(dep_counter, scopes=["scope"]),
|
||||
scope_count_2: int = Security(dep_counter, scopes=["scope"]),
|
||||
scope_count_1: int = Security(dep_counter, oauth_scopes=["scope"]),
|
||||
scope_count_2: int = Security(dep_counter, oauth_scopes=["scope"]),
|
||||
):
|
||||
return {
|
||||
"counter": count,
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ def process_auth(
|
|||
|
||||
@app.get("/get-credentials")
|
||||
def get_credentials(
|
||||
credentials: Annotated[dict, Security(process_auth, scopes=["a", "b"])],
|
||||
credentials: Annotated[dict, Security(process_auth, oauth_scopes=["a", "b"])],
|
||||
):
|
||||
return credentials
|
||||
|
||||
|
||||
@app.get(
|
||||
"/parameterless-with-scopes",
|
||||
dependencies=[Security(process_auth, scopes=["a", "b"])],
|
||||
dependencies=[Security(process_auth, oauth_scopes=["a", "b"])],
|
||||
)
|
||||
def get_parameterless_with_scopes():
|
||||
return {"status": "ok"}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def get_data_override():
|
|||
|
||||
@app.get("/user")
|
||||
def read_user(
|
||||
user_data: Tuple[str, List[str]] = Security(get_user, scopes=["foo", "bar"]),
|
||||
user_data: Tuple[str, List[str]] = Security(get_user, oauth_scopes=["foo", "bar"]),
|
||||
data: List[int] = Depends(get_data),
|
||||
):
|
||||
return {"user": user_data[0], "scopes": user_data[1], "data": data}
|
||||
|
|
|
|||
Loading…
Reference in New Issue