Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Let auth managers provide their own API endpoints #34349

Merged
merged 25 commits into from
Oct 17, 2023
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1472fa0
move user & role API endpoints to fab folder
vandonr-amz Sep 12, 2023
a3632f9
add a method to init API from auth provider
vandonr-amz Sep 13, 2023
7cfe915
add user & role APIs in fab auth manager
vandonr-amz Sep 13, 2023
43f3e5d
mark existing api endpoints as deprecated
vandonr-amz Sep 13, 2023
6f835d8
Merge remote-tracking branch 'apache/main' into vandonr/fab
vandonr-amz Sep 13, 2023
3c0490d
move tests
vandonr-amz Sep 13, 2023
f67eac3
test fixes
vandonr-amz Sep 13, 2023
32e67aa
apply suggestion
vandonr-amz Sep 13, 2023
8a8c738
docstring change
vandonr-amz Sep 13, 2023
8c104aa
more test fixing
vandonr-amz Sep 13, 2023
5ee06cf
change endpoint path to auth/fab
vandonr-amz Sep 22, 2023
7183e9b
Merge remote-tracking branch 'apache/main' into vandonr/fab
vandonr-amz Sep 22, 2023
51f8038
add intermediate layer on moved endpoints to check provider
vandonr-amz Sep 22, 2023
ec46600
a bit more detail in http error
vandonr-amz Sep 22, 2023
8566e29
static check fix
vandonr-amz Sep 25, 2023
73d014b
Merge remote-tracking branch 'apache/main' into vandonr/fab
vandonr-amz Sep 25, 2023
9301bb5
Merge branch 'main' into vandonr/fab
vandonr-amz Sep 25, 2023
95d57a2
Merge branch 'main' into vandonr/fab
vincbeck Sep 26, 2023
df4c63f
Add file `airflow/auth/managers/fab/openapi/v1.yaml` to MANIFEST.in
vincbeck Sep 26, 2023
1ad6988
Merge branch 'main' into vandonr/fab
vincbeck Oct 4, 2023
94ae9a2
Fix comment
vincbeck Oct 6, 2023
b89f301
Fix airflow/auth/managers/fab/openapi/v1.yaml
vincbeck Oct 6, 2023
6e6eb9c
Merge branch 'main' into vandonr/fab
vincbeck Oct 16, 2023
cc17098
Replace `get_api_blueprint` by `get_api_endpoints`
vincbeck Oct 16, 2023
d9ca5bf
Merge branch 'main' into vandonr/fab
vincbeck Oct 17, 2023
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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ exclude airflow/www/yarn.lock
exclude airflow/www/*.sh
include airflow/alembic.ini
include airflow/api_connexion/openapi/v1.yaml
include airflow/auth/managers/fab/openapi/v1.yaml
include airflow/git_version
include airflow/provider_info.schema.json
include airflow/customized_form_field_behaviours.schema.json
126 changes: 126 additions & 0 deletions airflow/api_connexion/endpoints/forward_to_fab_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations

import warnings
from typing import TYPE_CHECKING

from airflow.api_connexion.exceptions import BadRequest
from airflow.auth.managers.fab.api_endpoints import role_and_permission_endpoint, user_endpoint
from airflow.www.extensions.init_auth_manager import get_auth_manager

if TYPE_CHECKING:
from typing import Callable

from airflow.api_connexion.types import APIResponse


def _require_fab(func: Callable) -> Callable:
"""
Raise an HTTP error 400 if the auth manager is not FAB.

Intended to decorate endpoints that have been migrated from Airflow API to FAB API.
"""

def inner(*args, **kwargs):
from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager

auth_mgr = get_auth_manager()
if not isinstance(auth_mgr, FabAuthManager):
raise BadRequest(
detail="This endpoint is only available when using the default auth manager FabAuthManager."
)
else:
warnings.warn(
"This API endpoint is deprecated. "
"Please use the API under /auth/fab/v1 instead for this operation.",
DeprecationWarning,
)
return func(*args, **kwargs)

return inner


### role


@_require_fab
def get_role(**kwargs) -> APIResponse:
"""Get role."""
return role_and_permission_endpoint.get_role(**kwargs)


@_require_fab
def get_roles(**kwargs) -> APIResponse:
"""Get roles."""
return role_and_permission_endpoint.get_roles(**kwargs)


@_require_fab
def delete_role(**kwargs) -> APIResponse:
"""Delete a role."""
return role_and_permission_endpoint.delete_role(**kwargs)


@_require_fab
def patch_role(**kwargs) -> APIResponse:
"""Update a role."""
return role_and_permission_endpoint.patch_role(**kwargs)


@_require_fab
def post_role(**kwargs) -> APIResponse:
"""Create a new role."""
return role_and_permission_endpoint.post_role(**kwargs)


### permissions
@_require_fab
def get_permissions(**kwargs) -> APIResponse:
"""Get permissions."""
return role_and_permission_endpoint.get_permissions(**kwargs)


### user
@_require_fab
def get_user(**kwargs) -> APIResponse:
"""Get a user."""
return user_endpoint.get_user(**kwargs)


@_require_fab
def get_users(**kwargs) -> APIResponse:
"""Get users."""
return user_endpoint.get_users(**kwargs)


@_require_fab
def post_user(**kwargs) -> APIResponse:
"""Create a new user."""
return user_endpoint.post_user(**kwargs)


@_require_fab
def patch_user(**kwargs) -> APIResponse:
"""Update a user."""
return user_endpoint.patch_user(**kwargs)


@_require_fab
def delete_user(**kwargs) -> APIResponse:
"""Delete a user."""
return user_endpoint.delete_user(**kwargs)
33 changes: 22 additions & 11 deletions airflow/api_connexion/openapi/v1.yaml
Original file line number Diff line number Diff line change
@@ -2127,12 +2127,13 @@ paths:

/roles:
get:
deprecated: true
summary: List roles
description: |
Get a list of roles.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: get_roles
tags: [Role]
parameters:
@@ -2152,12 +2153,13 @@ paths:
$ref: '#/components/responses/PermissionDenied'

post:
deprecated: true
summary: Create a role
description: |
Create a new role.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: post_role
tags: [Role]
requestBody:
@@ -2185,12 +2187,13 @@ paths:
- $ref: '#/components/parameters/RoleName'

get:
deprecated: true
summary: Get a role
description: |
Get a role.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: get_role
tags: [Role]
responses:
@@ -2208,12 +2211,13 @@ paths:
$ref: '#/components/responses/NotFound'

patch:
deprecated: true
summary: Update a role
description: |
Update a role.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: patch_role
tags: [Role]
parameters:
@@ -2242,12 +2246,13 @@ paths:
$ref: '#/components/responses/NotFound'

delete:
deprecated: true
summary: Delete a role
description: |
Delete a role.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: delete_role
tags: [Role]
responses:
@@ -2264,12 +2269,13 @@ paths:

/permissions:
get:
deprecated: true
summary: List permissions
description: |
Get a list of permissions.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.role_and_permission_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: get_permissions
tags: [Permission]
parameters:
@@ -2289,12 +2295,13 @@ paths:

/users:
get:
deprecated: true
summary: List users
description: |
Get a list of users.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.user_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: get_users
tags: [User]
parameters:
@@ -2314,12 +2321,13 @@ paths:
$ref: '#/components/responses/PermissionDenied'

post:
deprecated: true
summary: Create a user
description: |
Create a new user with unique username and email.

*New in version 2.2.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.user_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: post_user
tags: [User]
requestBody:
@@ -2348,12 +2356,13 @@ paths:
parameters:
- $ref: '#/components/parameters/Username'
get:
deprecated: true
summary: Get a user
description: |
Get a user with a specific username.

*New in version 2.1.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.user_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: get_user
tags: [User]
responses:
@@ -2371,12 +2380,13 @@ paths:
$ref: '#/components/responses/NotFound'

patch:
deprecated: true
summary: Update a user
description: |
Update fields for a user.

*New in version 2.2.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.user_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: patch_user
tags: [User]
parameters:
@@ -2404,12 +2414,13 @@ paths:
$ref: '#/components/responses/NotFound'

delete:
deprecated: true
summary: Delete a user
description: |
Delete a user with a specific username.

*New in version 2.2.0*
x-openapi-router-controller: airflow.api_connexion.endpoints.user_endpoint
x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint
operationId: delete_user
tags: [User]
responses:
5 changes: 5 additions & 0 deletions airflow/auth/managers/base_auth_manager.py
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
from airflow.utils.session import NEW_SESSION, provide_session

if TYPE_CHECKING:
from connexion import FlaskApi
from flask import Flask
from sqlalchemy.orm import Session

@@ -66,6 +67,10 @@ def get_cli_commands() -> list[CLICommand]:
"""
return []

def get_api_endpoints(self) -> None | FlaskApi:
"""Return API endpoint(s) definition for the auth manager."""
return None

@abstractmethod
def get_user_name(self) -> str:
"""Return the username associated to the user in session."""
16 changes: 16 additions & 0 deletions airflow/auth/managers/fab/api_endpoints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
22 changes: 21 additions & 1 deletion airflow/auth/managers/fab/fab_auth_manager.py
Original file line number Diff line number Diff line change
@@ -18,8 +18,10 @@
from __future__ import annotations

import warnings
from pathlib import Path
from typing import TYPE_CHECKING, Container

from connexion import FlaskApi
from flask import url_for
from sqlalchemy import select
from sqlalchemy.orm import Session, joinedload
@@ -43,6 +45,7 @@
from airflow.cli.cli_config import (
GroupCommand,
)
from airflow.configuration import conf
from airflow.exceptions import AirflowException
from airflow.models import DagModel
from airflow.security import permissions
@@ -75,9 +78,10 @@
RESOURCE_XCOM,
)
from airflow.utils.session import NEW_SESSION, provide_session
from airflow.utils.yaml import safe_load
from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver

if TYPE_CHECKING:

from airflow.auth.managers.models.base_user import BaseUser
from airflow.cli.cli_config import (
CLICommand,
@@ -133,6 +137,22 @@ def get_cli_commands() -> list[CLICommand]:
SYNC_PERM_COMMAND, # not in a command group
]

def get_api_endpoints(self) -> None | FlaskApi:
folder = Path(__file__).parents[0].resolve() # this is airflow/auth/managers/fab/
with folder.joinpath("openapi", "v1.yaml").open() as f:
specification = safe_load(f)
return FlaskApi(
specification=specification,
resolver=_LazyResolver(),
base_path="/auth/fab/v1",
options={
"swagger_ui": conf.getboolean("webserver", "enable_swagger_ui", fallback=True),
},
strict_validation=True,
validate_responses=True,
validator_map={"body": _CustomErrorRequestBodyValidator},
)

def get_user_display_name(self) -> str:
"""Return the user's display name associated to the user in session."""
user = self.get_user()
Loading