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

Reply via email to