This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 829d10aa82 Use `requires_access_custom_view` for user and roles API
endpoints (#35207)
829d10aa82 is described below
commit 829d10aa827ce3ffab9529c63b1ba86c119b3ad1
Author: Vincent <[email protected]>
AuthorDate: Fri Nov 3 12:53:49 2023 -0400
Use `requires_access_custom_view` for user and roles API endpoints (#35207)
---
airflow/api_connexion/security.py | 21 +++++++++++++++++++++
.../api_endpoints/role_and_permission_endpoint.py | 14 +++++++-------
.../managers/fab/api_endpoints/user_endpoint.py | 12 ++++++------
3 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/airflow/api_connexion/security.py
b/airflow/api_connexion/security.py
index 956e7a2300..9084a6df42 100644
--- a/airflow/api_connexion/security.py
+++ b/airflow/api_connexion/security.py
@@ -246,5 +246,26 @@ def requires_access_view(access_view: AccessView) ->
Callable[[T], T]:
return requires_access_decorator
+def requires_access_custom_view(
+ fab_action_name: str,
+ fab_resource_name: str,
+) -> Callable[[T], T]:
+ def requires_access_decorator(func: T):
+ @wraps(func)
+ def decorated(*args, **kwargs):
+ return _requires_access(
+ is_authorized_callback=lambda:
get_auth_manager().is_authorized_custom_view(
+ fab_action_name=fab_action_name,
fab_resource_name=fab_resource_name
+ ),
+ func=func,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ return cast(T, decorated)
+
+ return requires_access_decorator
+
+
def get_readable_dags() -> list[str]:
return get_airflow_app().appbuilder.sm.get_accessible_dag_ids(g.user)
diff --git
a/airflow/auth/managers/fab/api_endpoints/role_and_permission_endpoint.py
b/airflow/auth/managers/fab/api_endpoints/role_and_permission_endpoint.py
index 4b1a310c86..ac0589385f 100644
--- a/airflow/auth/managers/fab/api_endpoints/role_and_permission_endpoint.py
+++ b/airflow/auth/managers/fab/api_endpoints/role_and_permission_endpoint.py
@@ -24,7 +24,6 @@ from flask import request
from marshmallow import ValidationError
from sqlalchemy import asc, desc, func, select
-from airflow.api_connexion import security
from airflow.api_connexion.exceptions import AlreadyExists, BadRequest,
NotFound
from airflow.api_connexion.parameters import check_limit, format_parameters
from airflow.api_connexion.schemas.role_and_permission_schema import (
@@ -34,6 +33,7 @@ from airflow.api_connexion.schemas.role_and_permission_schema
import (
role_collection_schema,
role_schema,
)
+from airflow.api_connexion.security import requires_access_custom_view
from airflow.auth.managers.fab.models import Action, Role
from airflow.security import permissions
from airflow.utils.airflow_flask_app import get_airflow_app
@@ -56,7 +56,7 @@ def _check_action_and_resource(sm: AirflowSecurityManagerV2,
perms: list[tuple[s
raise BadRequest(detail=f"The specified resource: {resource!r} was
not found")
[email protected]_access([(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ROLE)])
+@requires_access_custom_view(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ROLE)
def get_role(*, role_name: str) -> APIResponse:
"""Get role."""
ab_security_manager = get_airflow_app().appbuilder.sm
@@ -66,7 +66,7 @@ def get_role(*, role_name: str) -> APIResponse:
return role_schema.dump(role)
[email protected]_access([(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ROLE)])
+@requires_access_custom_view(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ROLE)
@format_parameters({"limit": check_limit})
def get_roles(*, order_by: str = "name", limit: int, offset: int | None =
None) -> APIResponse:
"""Get roles."""
@@ -94,7 +94,7 @@ def get_roles(*, order_by: str = "name", limit: int, offset:
int | None = None)
return role_collection_schema.dump(RoleCollection(roles=roles,
total_entries=total_entries))
[email protected]_access([(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ACTION)])
+@requires_access_custom_view(permissions.ACTION_CAN_READ,
permissions.RESOURCE_ACTION)
@format_parameters({"limit": check_limit})
def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
"""Get permissions."""
@@ -105,7 +105,7 @@ def get_permissions(*, limit: int, offset: int | None =
None) -> APIResponse:
return action_collection_schema.dump(ActionCollection(actions=actions,
total_entries=total_entries))
[email protected]_access([(permissions.ACTION_CAN_DELETE,
permissions.RESOURCE_ROLE)])
+@requires_access_custom_view(permissions.ACTION_CAN_DELETE,
permissions.RESOURCE_ROLE)
def delete_role(*, role_name: str) -> APIResponse:
"""Delete a role."""
ab_security_manager = get_airflow_app().appbuilder.sm
@@ -116,7 +116,7 @@ def delete_role(*, role_name: str) -> APIResponse:
return NoContent, HTTPStatus.NO_CONTENT
[email protected]_access([(permissions.ACTION_CAN_EDIT,
permissions.RESOURCE_ROLE)])
+@requires_access_custom_view(permissions.ACTION_CAN_EDIT,
permissions.RESOURCE_ROLE)
def patch_role(*, role_name: str, update_mask: UpdateMask = None) ->
APIResponse:
"""Update a role."""
appbuilder = get_airflow_app().appbuilder
@@ -150,7 +150,7 @@ def patch_role(*, role_name: str, update_mask: UpdateMask =
None) -> APIResponse
return role_schema.dump(role)
[email protected]_access([(permissions.ACTION_CAN_CREATE,
permissions.RESOURCE_ROLE)])
+@requires_access_custom_view(permissions.ACTION_CAN_CREATE,
permissions.RESOURCE_ROLE)
def post_role() -> APIResponse:
"""Create a new role."""
appbuilder = get_airflow_app().appbuilder
diff --git a/airflow/auth/managers/fab/api_endpoints/user_endpoint.py
b/airflow/auth/managers/fab/api_endpoints/user_endpoint.py
index 3a2e9f2fb3..ab04421ab8 100644
--- a/airflow/auth/managers/fab/api_endpoints/user_endpoint.py
+++ b/airflow/auth/managers/fab/api_endpoints/user_endpoint.py
@@ -25,7 +25,6 @@ from marshmallow import ValidationError
from sqlalchemy import asc, desc, func, select
from werkzeug.security import generate_password_hash
-from airflow.api_connexion import security
from airflow.api_connexion.exceptions import AlreadyExists, BadRequest,
NotFound, Unknown
from airflow.api_connexion.parameters import check_limit, format_parameters
from airflow.api_connexion.schemas.user_schema import (
@@ -34,6 +33,7 @@ from airflow.api_connexion.schemas.user_schema import (
user_collection_schema,
user_schema,
)
+from airflow.api_connexion.security import requires_access_custom_view
from airflow.auth.managers.fab.models import User
from airflow.security import permissions
from airflow.utils.airflow_flask_app import get_airflow_app
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
from airflow.auth.managers.fab.models import Role
[email protected]_access([(permissions.ACTION_CAN_READ,
permissions.RESOURCE_USER)])
+@requires_access_custom_view(permissions.ACTION_CAN_READ,
permissions.RESOURCE_USER)
def get_user(*, username: str) -> APIResponse:
"""Get a user."""
ab_security_manager = get_airflow_app().appbuilder.sm
@@ -53,7 +53,7 @@ def get_user(*, username: str) -> APIResponse:
return user_collection_item_schema.dump(user)
[email protected]_access([(permissions.ACTION_CAN_READ,
permissions.RESOURCE_USER)])
+@requires_access_custom_view(permissions.ACTION_CAN_READ,
permissions.RESOURCE_USER)
@format_parameters({"limit": check_limit})
def get_users(*, limit: int, order_by: str = "id", offset: str | None = None)
-> APIResponse:
"""Get users."""
@@ -85,7 +85,7 @@ def get_users(*, limit: int, order_by: str = "id", offset:
str | None = None) ->
return user_collection_schema.dump(UserCollection(users=users,
total_entries=total_entries))
[email protected]_access([(permissions.ACTION_CAN_CREATE,
permissions.RESOURCE_USER)])
+@requires_access_custom_view(permissions.ACTION_CAN_CREATE,
permissions.RESOURCE_USER)
def post_user() -> APIResponse:
"""Create a new user."""
try:
@@ -128,7 +128,7 @@ def post_user() -> APIResponse:
return user_schema.dump(user)
[email protected]_access([(permissions.ACTION_CAN_EDIT,
permissions.RESOURCE_USER)])
+@requires_access_custom_view(permissions.ACTION_CAN_EDIT,
permissions.RESOURCE_USER)
def patch_user(*, username: str, update_mask: UpdateMask = None) ->
APIResponse:
"""Update a user."""
try:
@@ -197,7 +197,7 @@ def patch_user(*, username: str, update_mask: UpdateMask =
None) -> APIResponse:
return user_schema.dump(user)
[email protected]_access([(permissions.ACTION_CAN_DELETE,
permissions.RESOURCE_USER)])
+@requires_access_custom_view(permissions.ACTION_CAN_DELETE,
permissions.RESOURCE_USER)
def delete_user(*, username: str) -> APIResponse:
"""Delete a user."""
security_manager = get_airflow_app().appbuilder.sm