apache-airflow-providers-fab 2.0.0b1__py3-none-any.whl → 2.0.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/fab/__init__.py +1 -1
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +3 -4
- airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +3 -3
- airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +8 -9
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +7 -8
- airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +1 -1
- airflow/providers/fab/auth_manager/api_fastapi/openapi/v1-generated.yaml +5 -4
- airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +1 -1
- airflow/providers/fab/auth_manager/api_fastapi/services/login.py +5 -5
- airflow/providers/fab/auth_manager/cli_commands/db_command.py +1 -1
- airflow/providers/fab/auth_manager/cli_commands/utils.py +1 -1
- airflow/providers/fab/auth_manager/fab_auth_manager.py +4 -0
- airflow/providers/fab/auth_manager/models/db.py +2 -2
- airflow/providers/fab/auth_manager/openapi/v1.yaml +9 -0
- airflow/providers/fab/auth_manager/security_manager/override.py +0 -39
- airflow/providers/fab/get_provider_info.py +7 -3
- airflow/providers/fab/www/api_connexion/parameters.py +1 -1
- airflow/providers/fab/www/api_connexion/security.py +3 -3
- airflow/providers/fab/www/app.py +1 -1
- airflow/providers/fab/www/auth.py +6 -6
- airflow/providers/fab/www/package-lock.json +1428 -1904
- airflow/providers/fab/www/package.json +20 -24
- airflow/providers/fab/www/static/dist/48f0ea180c40270a5b05.png +1 -0
- airflow/providers/fab/www/static/dist/649c0b07771e68fafdeb.png +1 -0
- airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.css +2 -2
- airflow/providers/fab/www/static/dist/f7490d556a6c42e49ba4.png +1 -0
- airflow/providers/fab/www/static/dist/jquery-ui.min.css +1 -1
- airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.css +18 -0
- airflow/providers/fab/www/static/dist/{main.ec1d38d994d72bb083cd.js → main.edb2d40dfbbc537916e3.js} +1 -1
- airflow/providers/fab/www/static/dist/manifest.json +7 -4
- airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +1 -1
- airflow/providers/fab/www/static/dist/{moment.4d28b37c229bdfc54575.js → moment.624b1f00ba723d39ce06.js} +2 -2
- airflow/providers/fab/www/static/dist/{moment.4d28b37c229bdfc54575.js.LICENSE.txt → moment.624b1f00ba723d39ce06.js.LICENSE.txt} +1 -1
- airflow/providers/fab/www/static/dist/oss-licenses.json +2 -11
- airflow/providers/fab/www/views.py +1 -1
- airflow/providers/fab/www/webpack.config.js +2 -2
- {apache_airflow_providers_fab-2.0.0b1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/METADATA +18 -10
- {apache_airflow_providers_fab-2.0.0b1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/RECORD +42 -39
- airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.css +0 -18
- /airflow/providers/fab/www/static/dist/{main.ec1d38d994d72bb083cd.js.LICENSE.txt → main.edb2d40dfbbc537916e3.js.LICENSE.txt} +0 -0
- {apache_airflow_providers_fab-2.0.0b1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-2.0.0b1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/entry_points.txt +0 -0
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
29
29
|
|
30
30
|
__all__ = ["__version__"]
|
31
31
|
|
32
|
-
__version__ = "2.0.
|
32
|
+
__version__ = "2.0.0"
|
33
33
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
35
35
|
"3.0.0.dev0"
|
@@ -26,9 +26,9 @@ from flask_appbuilder.const import AUTH_LDAP
|
|
26
26
|
from flask_login import login_user
|
27
27
|
|
28
28
|
from airflow.api_fastapi.app import get_auth_manager
|
29
|
-
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
30
29
|
|
31
30
|
if TYPE_CHECKING:
|
31
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
32
32
|
from airflow.providers.fab.auth_manager.models import User
|
33
33
|
|
34
34
|
CLIENT_AUTH: tuple[str, str] | Any | None = None
|
@@ -45,8 +45,7 @@ def auth_current_user() -> User | None:
|
|
45
45
|
auth = request.authorization
|
46
46
|
if auth is None or not auth.username or not auth.password:
|
47
47
|
return None
|
48
|
-
|
49
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
48
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
50
49
|
user = None
|
51
50
|
if security_manager.auth_type == AUTH_LDAP:
|
52
51
|
user = security_manager.auth_user_ldap(auth.username, auth.password)
|
@@ -67,4 +66,4 @@ def requires_authentication(function: T):
|
|
67
66
|
else:
|
68
67
|
return Response("Unauthorized", 401, {"WWW-Authenticate": "Basic"})
|
69
68
|
|
70
|
-
return cast(T, decorated)
|
69
|
+
return cast("T", decorated)
|
@@ -28,11 +28,11 @@ from requests_kerberos import HTTPKerberosAuth
|
|
28
28
|
|
29
29
|
from airflow.api_fastapi.app import get_auth_manager
|
30
30
|
from airflow.configuration import conf
|
31
|
-
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
32
31
|
from airflow.utils.net import getfqdn
|
33
32
|
|
34
33
|
if TYPE_CHECKING:
|
35
34
|
from airflow.api_fastapi.auth.managers.models.base_user import BaseUser
|
35
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
36
36
|
|
37
37
|
log = logging.getLogger(__name__)
|
38
38
|
|
@@ -115,7 +115,7 @@ T = TypeVar("T", bound=Callable)
|
|
115
115
|
|
116
116
|
|
117
117
|
def find_user(username=None, email=None):
|
118
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
118
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
119
119
|
return security_manager.find_user(username=username, email=email)
|
120
120
|
|
121
121
|
|
@@ -143,4 +143,4 @@ def requires_authentication(function: T, find_user: Callable[[str], BaseUser] |
|
|
143
143
|
return _forbidden()
|
144
144
|
return _unauthorized()
|
145
145
|
|
146
|
-
return cast(T, decorated)
|
146
|
+
return cast("T", decorated)
|
@@ -25,7 +25,6 @@ from marshmallow import ValidationError
|
|
25
25
|
from sqlalchemy import asc, desc, func, select
|
26
26
|
|
27
27
|
from airflow.api_fastapi.app import get_auth_manager
|
28
|
-
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
29
28
|
from airflow.providers.fab.auth_manager.models import Action, Role
|
30
29
|
from airflow.providers.fab.auth_manager.schemas.role_and_permission_schema import (
|
31
30
|
ActionCollection,
|
@@ -40,6 +39,7 @@ from airflow.providers.fab.www.api_connexion.security import requires_access_cus
|
|
40
39
|
from airflow.providers.fab.www.security import permissions
|
41
40
|
|
42
41
|
if TYPE_CHECKING:
|
42
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
43
43
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
44
44
|
from airflow.providers.fab.www.api_connexion.types import APIResponse, UpdateMask
|
45
45
|
|
@@ -60,7 +60,7 @@ def _check_action_and_resource(sm: FabAirflowSecurityManagerOverride, perms: lis
|
|
60
60
|
@requires_access_custom_view("GET", permissions.RESOURCE_ROLE)
|
61
61
|
def get_role(*, role_name: str) -> APIResponse:
|
62
62
|
"""Get role."""
|
63
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
63
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
64
64
|
role = security_manager.find_role(name=role_name)
|
65
65
|
if not role:
|
66
66
|
raise NotFound(title="Role not found", detail=f"Role with name {role_name!r} was not found")
|
@@ -71,7 +71,7 @@ def get_role(*, role_name: str) -> APIResponse:
|
|
71
71
|
@format_parameters({"limit": check_limit})
|
72
72
|
def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None) -> APIResponse:
|
73
73
|
"""Get roles."""
|
74
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
74
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
75
75
|
session = security_manager.get_session
|
76
76
|
total_entries = session.scalars(select(func.count(Role.id))).one()
|
77
77
|
direction = desc if order_by.startswith("-") else asc
|
@@ -81,8 +81,7 @@ def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None)
|
|
81
81
|
allowed_sort_attrs = ["role_id", "name"]
|
82
82
|
if order_by not in allowed_sort_attrs:
|
83
83
|
raise BadRequest(
|
84
|
-
detail=f"Ordering with '{order_by}' is disallowed or "
|
85
|
-
f"the attribute does not exist on the model"
|
84
|
+
detail=f"Ordering with '{order_by}' is disallowed or the attribute does not exist on the model"
|
86
85
|
)
|
87
86
|
|
88
87
|
query = select(Role)
|
@@ -99,7 +98,7 @@ def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None)
|
|
99
98
|
@format_parameters({"limit": check_limit})
|
100
99
|
def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
|
101
100
|
"""Get permissions."""
|
102
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
101
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
103
102
|
session = security_manager.get_session
|
104
103
|
total_entries = session.scalars(select(func.count(Action.id))).one()
|
105
104
|
query = select(Action)
|
@@ -110,7 +109,7 @@ def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
|
|
110
109
|
@requires_access_custom_view("DELETE", permissions.RESOURCE_ROLE)
|
111
110
|
def delete_role(*, role_name: str) -> APIResponse:
|
112
111
|
"""Delete a role."""
|
113
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
112
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
114
113
|
|
115
114
|
role = security_manager.find_role(name=role_name)
|
116
115
|
if not role:
|
@@ -122,7 +121,7 @@ def delete_role(*, role_name: str) -> APIResponse:
|
|
122
121
|
@requires_access_custom_view("PUT", permissions.RESOURCE_ROLE)
|
123
122
|
def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse:
|
124
123
|
"""Update a role."""
|
125
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
124
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
126
125
|
body = request.json
|
127
126
|
try:
|
128
127
|
data = role_schema.load(body)
|
@@ -155,7 +154,7 @@ def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse
|
|
155
154
|
@requires_access_custom_view("POST", permissions.RESOURCE_ROLE)
|
156
155
|
def post_role() -> APIResponse:
|
157
156
|
"""Create a new role."""
|
158
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
157
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
159
158
|
body = request.json
|
160
159
|
try:
|
161
160
|
data = role_schema.load(body)
|
@@ -26,7 +26,6 @@ from sqlalchemy import asc, desc, func, select
|
|
26
26
|
from werkzeug.security import generate_password_hash
|
27
27
|
|
28
28
|
from airflow.api_fastapi.app import get_auth_manager
|
29
|
-
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
30
29
|
from airflow.providers.fab.auth_manager.models import User
|
31
30
|
from airflow.providers.fab.auth_manager.schemas.user_schema import (
|
32
31
|
UserCollection,
|
@@ -40,6 +39,7 @@ from airflow.providers.fab.www.api_connexion.security import requires_access_cus
|
|
40
39
|
from airflow.providers.fab.www.security import permissions
|
41
40
|
|
42
41
|
if TYPE_CHECKING:
|
42
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
43
43
|
from airflow.providers.fab.auth_manager.models import Role
|
44
44
|
from airflow.providers.fab.www.api_connexion.types import APIResponse, UpdateMask
|
45
45
|
|
@@ -47,7 +47,7 @@ if TYPE_CHECKING:
|
|
47
47
|
@requires_access_custom_view("GET", permissions.RESOURCE_USER)
|
48
48
|
def get_user(*, username: str) -> APIResponse:
|
49
49
|
"""Get a user."""
|
50
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
50
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
51
51
|
user = security_manager.find_user(username=username)
|
52
52
|
if not user:
|
53
53
|
raise NotFound(title="User not found", detail=f"The User with username `{username}` was not found")
|
@@ -58,7 +58,7 @@ def get_user(*, username: str) -> APIResponse:
|
|
58
58
|
@format_parameters({"limit": check_limit})
|
59
59
|
def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) -> APIResponse:
|
60
60
|
"""Get users."""
|
61
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
61
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
62
62
|
session = security_manager.get_session
|
63
63
|
total_entries = session.execute(select(func.count(User.id))).scalar()
|
64
64
|
direction = desc if order_by.startswith("-") else asc
|
@@ -76,8 +76,7 @@ def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) ->
|
|
76
76
|
]
|
77
77
|
if order_by not in allowed_sort_attrs:
|
78
78
|
raise BadRequest(
|
79
|
-
detail=f"Ordering with '{order_by}' is disallowed or "
|
80
|
-
f"the attribute does not exist on the model"
|
79
|
+
detail=f"Ordering with '{order_by}' is disallowed or the attribute does not exist on the model"
|
81
80
|
)
|
82
81
|
|
83
82
|
query = select(User).order_by(direction(getattr(User, order_param))).offset(offset).limit(limit)
|
@@ -94,7 +93,7 @@ def post_user() -> APIResponse:
|
|
94
93
|
except ValidationError as e:
|
95
94
|
raise BadRequest(detail=str(e.messages))
|
96
95
|
|
97
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
96
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
98
97
|
username = data["username"]
|
99
98
|
email = data["email"]
|
100
99
|
|
@@ -137,7 +136,7 @@ def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
|
|
137
136
|
except ValidationError as e:
|
138
137
|
raise BadRequest(detail=str(e.messages))
|
139
138
|
|
140
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
139
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
141
140
|
|
142
141
|
user = security_manager.find_user(username=username)
|
143
142
|
if user is None:
|
@@ -201,7 +200,7 @@ def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
|
|
201
200
|
@requires_access_custom_view("DELETE", permissions.RESOURCE_USER)
|
202
201
|
def delete_user(*, username: str) -> APIResponse:
|
203
202
|
"""Delete a user."""
|
204
|
-
security_manager = cast(FabAuthManager, get_auth_manager()).security_manager
|
203
|
+
security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
|
205
204
|
|
206
205
|
user = security_manager.find_user(username=username)
|
207
206
|
if user is None:
|
@@ -89,7 +89,8 @@ components:
|
|
89
89
|
detail:
|
90
90
|
anyOf:
|
91
91
|
- type: string
|
92
|
-
-
|
92
|
+
- additionalProperties: true
|
93
|
+
type: object
|
93
94
|
title: Detail
|
94
95
|
type: object
|
95
96
|
required:
|
@@ -121,12 +122,12 @@ components:
|
|
121
122
|
description: API Token serializer for requests.
|
122
123
|
LoginResponse:
|
123
124
|
properties:
|
124
|
-
|
125
|
+
access_token:
|
125
126
|
type: string
|
126
|
-
title:
|
127
|
+
title: Access Token
|
127
128
|
type: object
|
128
129
|
required:
|
129
|
-
-
|
130
|
+
- access_token
|
130
131
|
title: LoginResponse
|
131
132
|
description: API Token serializer for responses.
|
132
133
|
ValidationError:
|
@@ -47,5 +47,5 @@ def create_token(body: LoginBody) -> LoginResponse:
|
|
47
47
|
def create_token_cli(body: LoginBody) -> LoginResponse:
|
48
48
|
"""Generate a new CLI API token."""
|
49
49
|
return FABAuthManagerLogin.create_token(
|
50
|
-
body=body,
|
50
|
+
body=body, expiration_time_in_seconds=conf.getint("api_auth", "jwt_cli_expiration_time")
|
51
51
|
)
|
@@ -24,9 +24,9 @@ from starlette.exceptions import HTTPException
|
|
24
24
|
from airflow.api_fastapi.app import get_auth_manager
|
25
25
|
from airflow.configuration import conf
|
26
26
|
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.login import LoginBody, LoginResponse
|
27
|
-
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
28
27
|
|
29
28
|
if TYPE_CHECKING:
|
29
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
30
30
|
from airflow.providers.fab.auth_manager.models import User
|
31
31
|
|
32
32
|
|
@@ -35,7 +35,7 @@ class FABAuthManagerLogin:
|
|
35
35
|
|
36
36
|
@classmethod
|
37
37
|
def create_token(
|
38
|
-
cls, body: LoginBody,
|
38
|
+
cls, body: LoginBody, expiration_time_in_seconds: int = conf.getint("api_auth", "jwt_expiration_time")
|
39
39
|
) -> LoginResponse:
|
40
40
|
"""Create a new token."""
|
41
41
|
if not body.username or not body.password:
|
@@ -43,15 +43,15 @@ class FABAuthManagerLogin:
|
|
43
43
|
status_code=status.HTTP_400_BAD_REQUEST, detail="Username and password must be provided"
|
44
44
|
)
|
45
45
|
|
46
|
-
auth_manager = cast(FabAuthManager, get_auth_manager())
|
46
|
+
auth_manager = cast("FabAuthManager", get_auth_manager())
|
47
47
|
user: User = auth_manager.security_manager.find_user(username=body.username)
|
48
48
|
if not user:
|
49
49
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username")
|
50
50
|
|
51
51
|
if auth_manager.security_manager.check_password(username=body.username, password=body.password):
|
52
52
|
return LoginResponse(
|
53
|
-
|
54
|
-
user=user, expiration_time_in_seconds=
|
53
|
+
access_token=auth_manager.generate_jwt(
|
54
|
+
user=user, expiration_time_in_seconds=expiration_time_in_seconds
|
55
55
|
)
|
56
56
|
)
|
57
57
|
else:
|
@@ -17,7 +17,7 @@
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
19
|
from airflow import settings
|
20
|
-
from airflow.cli.commands.
|
20
|
+
from airflow.cli.commands.db_command import run_db_downgrade_command, run_db_migrate_command
|
21
21
|
from airflow.providers.fab.auth_manager.models.db import _REVISION_HEADS_MAP, FABDBManager
|
22
22
|
from airflow.utils import cli as cli_utils
|
23
23
|
from airflow.utils.providers_configuration_loader import providers_configuration_loaded
|
@@ -59,7 +59,7 @@ def get_application_builder() -> Generator[AirflowAppBuilder, None, None]:
|
|
59
59
|
url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
|
60
60
|
if url.drivername == "sqlite" and url.database and not isabs(url.database):
|
61
61
|
raise AirflowConfigException(
|
62
|
-
f
|
62
|
+
f"Cannot use relative path: `{conf.get('database', 'SQL_ALCHEMY_CONN')}` to connect to sqlite. "
|
63
63
|
"Please use absolute path such as `sqlite:////tmp/airflow.db`."
|
64
64
|
)
|
65
65
|
flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
@@ -521,6 +521,10 @@ class FabAuthManager(BaseAuthManager[User]):
|
|
521
521
|
if self._is_authorized(method="MENU", resource_type=item["resource_type"], user=user)
|
522
522
|
]
|
523
523
|
|
524
|
+
@staticmethod
|
525
|
+
def get_db_manager() -> str | None:
|
526
|
+
return "airflow.providers.fab.auth_manager.models.db.FABDBManager"
|
527
|
+
|
524
528
|
def _is_authorized(
|
525
529
|
self,
|
526
530
|
*,
|
@@ -54,8 +54,8 @@ class FABDBManager(BaseDBManager):
|
|
54
54
|
alembic_file = (PACKAGE_DIR / "alembic.ini").as_posix()
|
55
55
|
supports_table_dropping = True
|
56
56
|
|
57
|
-
def
|
58
|
-
super().
|
57
|
+
def create_db_from_orm(self):
|
58
|
+
super().create_db_from_orm()
|
59
59
|
_get_flask_db(settings.SQL_ALCHEMY_CONN).create_all()
|
60
60
|
|
61
61
|
def upgradedb(self, to_revision=None, from_revision=None, show_sql_only=False):
|
@@ -687,12 +687,21 @@ components:
|
|
687
687
|
Basic:
|
688
688
|
type: http
|
689
689
|
scheme: basic
|
690
|
+
description: To authenticate FAB auth manager API requests, clients have the option to use basic
|
691
|
+
authentication. To learn more about FAB auth manager API authentication, please read
|
692
|
+
https://airflow.apache.org/docs/apache-airflow-providers-fab/stable/auth-manager/api-authentication.html#basic-authentication.
|
690
693
|
GoogleOpenId:
|
691
694
|
type: openIdConnect
|
692
695
|
openIdConnectUrl: https://accounts.google.com/.well-known/openid-configuration
|
696
|
+
description: To authenticate FAB auth manager API requests, clients have the option to use Google OpenID.
|
697
|
+
To learn more about Google OpenID authentication, please read
|
698
|
+
https://airflow.apache.org/docs/apache-airflow-providers-google/stable/api-auth-backend/google-openid.html.
|
693
699
|
Kerberos:
|
694
700
|
type: http
|
695
701
|
scheme: negotiate
|
702
|
+
description: To authenticate FAB auth manager API requests, clients have the option to use Kerberos
|
703
|
+
authentication. To learn more about FAB auth manager API authentication, please read
|
704
|
+
https://airflow.apache.org/docs/apache-airflow-providers-fab/stable/auth-manager/api-authentication.html#kerberos-authentication.
|
696
705
|
|
697
706
|
tags:
|
698
707
|
- name: Role
|
@@ -21,8 +21,6 @@ import copy
|
|
21
21
|
import datetime
|
22
22
|
import itertools
|
23
23
|
import logging
|
24
|
-
import os
|
25
|
-
import random
|
26
24
|
import uuid
|
27
25
|
from collections.abc import Collection, Iterable, Mapping
|
28
26
|
from typing import TYPE_CHECKING, Any
|
@@ -740,43 +738,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
740
738
|
"""Get the builtin roles."""
|
741
739
|
return self._builtin_roles
|
742
740
|
|
743
|
-
def create_admin_standalone(self) -> tuple[str | None, str | None]:
|
744
|
-
"""Create an Admin user with a random password so that users can access airflow."""
|
745
|
-
from airflow.configuration import AIRFLOW_HOME, make_group_other_inaccessible
|
746
|
-
|
747
|
-
user_name = "admin"
|
748
|
-
|
749
|
-
# We want a streamlined first-run experience, but we do not want to
|
750
|
-
# use a preset password as people will inevitably run this on a public
|
751
|
-
# server. Thus, we make a random password and store it in AIRFLOW_HOME,
|
752
|
-
# with the reasoning that if you can read that directory, you can see
|
753
|
-
# the database credentials anyway.
|
754
|
-
password_path = os.path.join(AIRFLOW_HOME, "standalone_admin_password.txt")
|
755
|
-
|
756
|
-
user_exists = self.find_user(user_name) is not None
|
757
|
-
we_know_password = os.path.isfile(password_path)
|
758
|
-
|
759
|
-
# If the user does not exist, make a random password and make it
|
760
|
-
if not user_exists:
|
761
|
-
print(f"FlaskAppBuilder Authentication Manager: Creating {user_name} user")
|
762
|
-
if (role := self.find_role("Admin")) is None:
|
763
|
-
raise AirflowException("Unable to find role 'Admin'")
|
764
|
-
# password does not contain visually similar characters: ijlIJL1oO0
|
765
|
-
password = "".join(random.choices("abcdefghkmnpqrstuvwxyzABCDEFGHKMNPQRSTUVWXYZ23456789", k=16))
|
766
|
-
with open(password_path, "w") as file:
|
767
|
-
file.write(password)
|
768
|
-
make_group_other_inaccessible(password_path)
|
769
|
-
self.add_user(user_name, "Admin", "User", "admin@example.com", role, password)
|
770
|
-
print(f"FlaskAppBuilder Authentication Manager: Created {user_name} user")
|
771
|
-
# If the user does exist, and we know its password, read the password
|
772
|
-
elif user_exists and we_know_password:
|
773
|
-
with open(password_path) as file:
|
774
|
-
password = file.read().strip()
|
775
|
-
# Otherwise we don't know the password
|
776
|
-
else:
|
777
|
-
password = None
|
778
|
-
return user_name, password
|
779
|
-
|
780
741
|
def _init_config(self):
|
781
742
|
"""
|
782
743
|
Initialize config.
|
@@ -29,7 +29,7 @@ def get_provider_info():
|
|
29
29
|
"state": "not-ready",
|
30
30
|
"source-date-epoch": 1741121873,
|
31
31
|
"versions": [
|
32
|
-
"2.0.
|
32
|
+
"2.0.0",
|
33
33
|
"1.5.2",
|
34
34
|
"1.5.1",
|
35
35
|
"1.5.0",
|
@@ -86,12 +86,16 @@ def get_provider_info():
|
|
86
86
|
"dependencies": [
|
87
87
|
"apache-airflow>=3.0.0.dev0",
|
88
88
|
"apache-airflow-providers-common-compat>=1.2.1",
|
89
|
-
"
|
89
|
+
"blinker>=1.6.2",
|
90
|
+
"flask>=2.2.1,<2.3",
|
90
91
|
"flask-appbuilder==4.5.3",
|
91
92
|
"flask-login>=0.6.2",
|
93
|
+
"flask-session>=0.4.0,<0.6",
|
94
|
+
"flask-wtf>=1.1.0",
|
92
95
|
"connexion[flask]>=2.14.2,<3.0",
|
93
96
|
"jmespath>=0.7.0",
|
97
|
+
"werkzeug>=2.2,<4",
|
94
98
|
],
|
95
99
|
"optional-dependencies": {"kerberos": ["kerberos>=1.3.0"]},
|
96
|
-
"devel-dependencies": ["kerberos>=1.3.0"],
|
100
|
+
"devel-dependencies": ["kerberos>=1.3.0", "requests_kerberos>=0.14.0"],
|
97
101
|
}
|
@@ -104,7 +104,7 @@ def format_parameters(params_formatters: dict[str, Callable[[Any], Any]]) -> Cal
|
|
104
104
|
kwargs[key] = formatter(kwargs[key])
|
105
105
|
return func(*args, **kwargs)
|
106
106
|
|
107
|
-
return cast(T, wrapped_function)
|
107
|
+
return cast("T", wrapped_function)
|
108
108
|
|
109
109
|
return format_parameters_decorator
|
110
110
|
|
@@ -22,18 +22,18 @@ from typing import TYPE_CHECKING, Callable, TypeVar, cast
|
|
22
22
|
from flask import Response, current_app
|
23
23
|
|
24
24
|
from airflow.api_fastapi.app import get_auth_manager
|
25
|
-
from airflow.providers.fab.www.airflow_flask_app import AirflowApp
|
26
25
|
from airflow.providers.fab.www.api_connexion.exceptions import PermissionDenied, Unauthenticated
|
27
26
|
|
28
27
|
if TYPE_CHECKING:
|
29
28
|
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod
|
29
|
+
from airflow.providers.fab.www.airflow_flask_app import AirflowApp
|
30
30
|
|
31
31
|
T = TypeVar("T", bound=Callable)
|
32
32
|
|
33
33
|
|
34
34
|
def check_authentication() -> None:
|
35
35
|
"""Check that the request has valid authorization information."""
|
36
|
-
for auth in cast(AirflowApp, current_app).api_auth:
|
36
|
+
for auth in cast("AirflowApp", current_app).api_auth:
|
37
37
|
response = auth.requires_authentication(Response)()
|
38
38
|
if response.status_code == 200:
|
39
39
|
return
|
@@ -79,6 +79,6 @@ def requires_access_custom_view(
|
|
79
79
|
kwargs=kwargs,
|
80
80
|
)
|
81
81
|
|
82
|
-
return cast(T, decorated)
|
82
|
+
return cast("T", decorated)
|
83
83
|
|
84
84
|
return requires_access_decorator
|
airflow/providers/fab/www/app.py
CHANGED
@@ -60,7 +60,7 @@ def create_app(enable_plugins: bool):
|
|
60
60
|
url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
|
61
61
|
if url.drivername == "sqlite" and url.database and not isabs(url.database):
|
62
62
|
raise AirflowConfigException(
|
63
|
-
f
|
63
|
+
f"Cannot use relative path: `{conf.get('database', 'SQL_ALCHEMY_CONN')}` to connect to sqlite. "
|
64
64
|
"Please use absolute path such as `sqlite:////tmp/airflow.db`."
|
65
65
|
)
|
66
66
|
|
@@ -121,7 +121,7 @@ def _has_access_no_details(is_authorized_callback: Callable[[], bool]) -> Callab
|
|
121
121
|
kwargs=kwargs,
|
122
122
|
)
|
123
123
|
|
124
|
-
return cast(T, decorated)
|
124
|
+
return cast("T", decorated)
|
125
125
|
|
126
126
|
return has_access_decorator
|
127
127
|
|
@@ -189,7 +189,7 @@ def has_access_connection(method: ResourceMethod) -> Callable[[T], T]:
|
|
189
189
|
kwargs=kwargs,
|
190
190
|
)
|
191
191
|
|
192
|
-
return cast(T, decorated)
|
192
|
+
return cast("T", decorated)
|
193
193
|
|
194
194
|
return has_access_decorator
|
195
195
|
|
@@ -240,7 +240,7 @@ def has_access_dag(method: ResourceMethod, access_entity: DagAccessEntity | None
|
|
240
240
|
kwargs=kwargs,
|
241
241
|
)
|
242
242
|
|
243
|
-
return cast(T, decorated)
|
243
|
+
return cast("T", decorated)
|
244
244
|
|
245
245
|
return has_access_decorator
|
246
246
|
|
@@ -269,7 +269,7 @@ def has_access_dag_entities(method: ResourceMethod, access_entity: DagAccessEnti
|
|
269
269
|
kwargs=kwargs,
|
270
270
|
)
|
271
271
|
|
272
|
-
return cast(T, decorated)
|
272
|
+
return cast("T", decorated)
|
273
273
|
|
274
274
|
return has_access_decorator
|
275
275
|
|
@@ -303,7 +303,7 @@ def has_access_pool(method: ResourceMethod) -> Callable[[T], T]:
|
|
303
303
|
kwargs=kwargs,
|
304
304
|
)
|
305
305
|
|
306
|
-
return cast(T, decorated)
|
306
|
+
return cast("T", decorated)
|
307
307
|
|
308
308
|
return has_access_decorator
|
309
309
|
|
@@ -336,7 +336,7 @@ def has_access_variable(method: ResourceMethod) -> Callable[[T], T]:
|
|
336
336
|
kwargs=kwargs,
|
337
337
|
)
|
338
338
|
|
339
|
-
return cast(T, decorated)
|
339
|
+
return cast("T", decorated)
|
340
340
|
|
341
341
|
return has_access_decorator
|
342
342
|
|