Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@

# This cannot be in the TYPE_CHECKING block since some providers import it globally.
# TODO: Move this inside once all providers drop Airflow 2.x support.
ResourceMethod = Literal["GET", "POST", "PUT", "DELETE", "MENU"]
# List of methods (or actions) a user can do against a resource
ResourceMethod = Literal["GET", "POST", "PUT", "DELETE"]
# Extends ``ResourceMethod`` to include "MENU". The method "MENU" is only supported with specific resources (menu items)
ExtendedResourceMethod = Literal["GET", "POST", "PUT", "DELETE", "MENU"]

log = logging.getLogger(__name__)
T = TypeVar("T", bound=BaseUser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@

if TYPE_CHECKING:
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod

try:
from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod
except ImportError:
from airflow.api_fastapi.auth.managers.base_auth_manager import (
ResourceMethod as ExtendedResourceMethod,
)
from airflow.providers.amazon.aws.auth_manager.user import AwsAuthManagerUser


Expand All @@ -48,7 +55,7 @@
class IsAuthorizedRequest(TypedDict, total=False):
"""Represent the parameters of ``is_authorized`` method in AVP facade."""

method: ResourceMethod
method: ExtendedResourceMethod
entity_type: AvpEntities
entity_id: str | None
context: dict | None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
from airflow import __version__ as airflow_version
from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX
from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager

try:
from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod
except ImportError:
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod as ExtendedResourceMethod

from airflow.api_fastapi.auth.managers.models.resource_details import (
AccessView,
BackfillDetails,
Expand Down Expand Up @@ -382,7 +388,7 @@ def is_authorized_variable(

def is_authorized_view(self, *, access_view: AccessView, user: User) -> bool:
# "Docs" are only links in the menu, there is no page associated
method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET"
method: ExtendedResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET"
return self._is_authorized(
method=method,
resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view],
Expand Down Expand Up @@ -523,7 +529,7 @@ def get_db_manager() -> str | None:
def _is_authorized(
self,
*,
method: ResourceMethod,
method: ExtendedResourceMethod,
resource_type: str,
user: User,
) -> bool:
Expand Down Expand Up @@ -594,7 +600,7 @@ def _is_authorized_dag_run(
return len(authorized_dags) > 0

@staticmethod
def _get_fab_action(method: ResourceMethod) -> str:
def _get_fab_action(method: ExtendedResourceMethod) -> str:
"""
Convert the method to a FAB action.

Expand Down
9 changes: 7 additions & 2 deletions providers/fab/src/airflow/providers/fab/www/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@
if TYPE_CHECKING:
from sqlalchemy.orm.session import Session

from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod
try:
from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod
except ImportError:
from airflow.api_fastapi.auth.managers.base_auth_manager import (
ResourceMethod as ExtendedResourceMethod,
)
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager

# Convert methods to FAB action name
_MAP_METHOD_NAME_TO_FAB_ACTION_NAME: dict[ResourceMethod, str] = {
_MAP_METHOD_NAME_TO_FAB_ACTION_NAME: dict[ExtendedResourceMethod, str] = {
"POST": ACTION_CAN_CREATE,
"GET": ACTION_CAN_READ,
"PUT": ACTION_CAN_EDIT,
Expand Down
2 changes: 1 addition & 1 deletion providers/fab/www-hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
52c1ebe16934a7ef8ad42e05a675a3c6e662476a2d47fea3c142ff1b27305e45
bba05295e6d4ef8f0bfe766b77cef4d90d62e86f3a2162de32d6e94979b236c7
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
from keycloak import KeycloakAdmin, KeycloakError

from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod

try:
from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod
except ImportError:
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod as ExtendedResourceMethod
from airflow.api_fastapi.common.types import MenuItem
from airflow.configuration import conf
from airflow.providers.keycloak.auth_manager.constants import (
Expand Down Expand Up @@ -109,6 +114,7 @@ def _get_client_uuid(args):

def _create_scopes(client: KeycloakAdmin, client_uuid: str):
scopes = [{"name": method} for method in get_args(ResourceMethod)]
scopes.append({"name": "MENU"})
for scope in scopes:
client.create_client_authz_scopes(client_id=client_uuid, payload=scope)

Expand Down Expand Up @@ -173,7 +179,7 @@ def _create_read_only_permission(client: KeycloakAdmin, client_uuid: str):

def _create_admin_permission(client: KeycloakAdmin, client_uuid: str):
all_scopes = client.get_client_authz_scopes(client_uuid)
scopes = [scope["id"] for scope in all_scopes if scope["name"] in get_args(ResourceMethod)]
scopes = [scope["id"] for scope in all_scopes if scope["name"] in get_args(ExtendedResourceMethod)]
payload = {
"name": "Admin",
"type": "scope",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@

from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX
from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager

try:
from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod
except ImportError:
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod as ExtendedResourceMethod

from airflow.api_fastapi.common.types import MenuItem
from airflow.cli.cli_config import CLICommand, GroupCommand
from airflow.configuration import conf
Expand Down Expand Up @@ -201,7 +207,9 @@ def filter_authorized_menu_items(
self, menu_items: list[MenuItem], *, user: KeycloakAuthManagerUser
) -> list[MenuItem]:
authorized_menus = self._is_batch_authorized(
permissions=[(cast("ResourceMethod", "MENU"), menu_item.value) for menu_item in menu_items],
permissions=[
(cast("ExtendedResourceMethod", "MENU"), menu_item.value) for menu_item in menu_items
],
user=user,
)
return [MenuItem(menu[1]) for menu in authorized_menus]
Expand Down Expand Up @@ -285,9 +293,9 @@ def _is_authorized(
def _is_batch_authorized(
self,
*,
permissions: list[tuple[ResourceMethod, str]],
permissions: list[tuple[ExtendedResourceMethod, str]],
user: KeycloakAuthManagerUser,
) -> set[tuple[ResourceMethod, str]]:
) -> set[tuple[ExtendedResourceMethod, str]]:
client_id = conf.get(CONF_SECTION_NAME, CONF_CLIENT_ID_KEY)
realm = conf.get(CONF_SECTION_NAME, CONF_REALM_KEY)
server_url = conf.get(CONF_SECTION_NAME, CONF_SERVER_URL_KEY)
Expand Down Expand Up @@ -326,7 +334,7 @@ def _get_payload(client_id: str, permission: str, attributes: dict[str, str] | N
return payload

@staticmethod
def _get_batch_payload(client_id: str, permissions: list[tuple[ResourceMethod, str]]):
def _get_batch_payload(client_id: str, permissions: list[tuple[ExtendedResourceMethod, str]]):
payload: dict[str, Any] = {
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"audience": client_id,
Expand Down