apache-airflow-providers-fab 1.5.3rc1__py3-none-any.whl → 2.0.0rc1__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 +3 -3
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +1 -1
- airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +1 -1
- airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +1 -1
- airflow/providers/fab/auth_manager/cli_commands/db_command.py +1 -1
- airflow/providers/fab/auth_manager/cli_commands/utils.py +4 -16
- airflow/providers/fab/auth_manager/decorators/auth.py +3 -2
- airflow/providers/fab/auth_manager/fab_auth_manager.py +26 -20
- airflow/providers/fab/auth_manager/security_manager/override.py +7 -135
- airflow/providers/fab/get_provider_info.py +4 -5
- airflow/providers/fab/www/__init__.py +17 -0
- airflow/providers/fab/www/app.py +87 -0
- airflow/providers/fab/www/extensions/__init__.py +16 -0
- airflow/providers/fab/www/extensions/init_appbuilder.py +557 -0
- airflow/providers/fab/www/extensions/init_jinja_globals.py +80 -0
- airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
- airflow/providers/fab/www/extensions/init_security.py +42 -0
- airflow/providers/fab/www/extensions/init_views.py +67 -0
- airflow/providers/fab/www/package-lock.json +21201 -0
- airflow/providers/fab/www/package.json +156 -0
- airflow/providers/fab/www/static/css/bootstrap-theme.css +6215 -0
- airflow/providers/fab/www/static/css/loading-dots.css +60 -0
- airflow/providers/fab/www/static/css/main.css +676 -0
- airflow/providers/fab/www/static/css/material-icons.css +84 -0
- airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
- airflow/providers/fab/www/static/js/main.js +324 -0
- airflow/providers/fab/www/static/sort_asc.png +0 -0
- airflow/providers/fab/www/static/sort_both.png +0 -0
- airflow/providers/fab/www/static/sort_desc.png +0 -0
- airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
- airflow/providers/fab/www/templates/airflow/error.html +35 -0
- airflow/providers/fab/www/templates/airflow/main.html +79 -0
- airflow/providers/fab/www/templates/airflow/traceback.html +57 -0
- airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
- airflow/providers/fab/www/templates/appbuilder/navbar.html +53 -0
- airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -0
- airflow/providers/fab/www/webpack.config.js +248 -0
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/METADATA +10 -10
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/RECORD +43 -16
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0rc1.dist-info}/entry_points.txt +0 -0
@@ -29,11 +29,11 @@ from airflow import __version__ as airflow_version
|
|
29
29
|
|
30
30
|
__all__ = ["__version__"]
|
31
31
|
|
32
|
-
__version__ = "
|
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"
|
36
36
|
):
|
37
37
|
raise RuntimeError(
|
38
|
-
f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow
|
38
|
+
f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow 3.0.0.dev0+"
|
39
39
|
)
|
@@ -25,8 +25,8 @@ from flask import Response, current_app, request
|
|
25
25
|
from flask_appbuilder.const import AUTH_LDAP
|
26
26
|
from flask_login import login_user
|
27
27
|
|
28
|
+
from airflow.api_fastapi.app import get_auth_manager
|
28
29
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
29
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
30
30
|
|
31
31
|
if TYPE_CHECKING:
|
32
32
|
from airflow.providers.fab.auth_manager.models import User
|
@@ -26,10 +26,10 @@ import kerberos
|
|
26
26
|
from flask import Response, current_app, g, make_response, request
|
27
27
|
from requests_kerberos import HTTPKerberosAuth
|
28
28
|
|
29
|
+
from airflow.api_fastapi.app import get_auth_manager
|
29
30
|
from airflow.configuration import conf
|
30
31
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
31
32
|
from airflow.utils.net import getfqdn
|
32
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
33
33
|
|
34
34
|
if TYPE_CHECKING:
|
35
35
|
from airflow.auth.managers.models.base_user import BaseUser
|
@@ -23,7 +23,7 @@ from typing import Any, Callable, TypeVar, cast
|
|
23
23
|
|
24
24
|
from flask import Response
|
25
25
|
|
26
|
-
from airflow.
|
26
|
+
from airflow.api_fastapi.app import get_auth_manager
|
27
27
|
|
28
28
|
CLIENT_AUTH: tuple[str, str] | Any | None = None
|
29
29
|
|
@@ -27,6 +27,7 @@ from sqlalchemy import asc, desc, func, select
|
|
27
27
|
from airflow.api_connexion.exceptions import AlreadyExists, BadRequest, NotFound
|
28
28
|
from airflow.api_connexion.parameters import check_limit, format_parameters
|
29
29
|
from airflow.api_connexion.security import requires_access_custom_view
|
30
|
+
from airflow.api_fastapi.app import get_auth_manager
|
30
31
|
from airflow.providers.fab.auth_manager.models import Action, Role
|
31
32
|
from airflow.providers.fab.auth_manager.schemas.role_and_permission_schema import (
|
32
33
|
ActionCollection,
|
@@ -37,7 +38,6 @@ from airflow.providers.fab.auth_manager.schemas.role_and_permission_schema impor
|
|
37
38
|
)
|
38
39
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
39
40
|
from airflow.security import permissions
|
40
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
41
41
|
|
42
42
|
if TYPE_CHECKING:
|
43
43
|
from airflow.api_connexion.types import APIResponse, UpdateMask
|
@@ -28,6 +28,7 @@ from werkzeug.security import generate_password_hash
|
|
28
28
|
from airflow.api_connexion.exceptions import AlreadyExists, BadRequest, NotFound, Unknown
|
29
29
|
from airflow.api_connexion.parameters import check_limit, format_parameters
|
30
30
|
from airflow.api_connexion.security import requires_access_custom_view
|
31
|
+
from airflow.api_fastapi.app import get_auth_manager
|
31
32
|
from airflow.providers.fab.auth_manager.models import User
|
32
33
|
from airflow.providers.fab.auth_manager.schemas.user_schema import (
|
33
34
|
UserCollection,
|
@@ -37,7 +38,6 @@ from airflow.providers.fab.auth_manager.schemas.user_schema import (
|
|
37
38
|
)
|
38
39
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
39
40
|
from airflow.security import permissions
|
40
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
41
41
|
|
42
42
|
if TYPE_CHECKING:
|
43
43
|
from airflow.api_connexion.types import APIResponse, UpdateMask
|
@@ -17,7 +17,7 @@
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
19
|
from airflow import settings
|
20
|
-
from airflow.cli.commands.db_command import run_db_downgrade_command, run_db_migrate_command
|
20
|
+
from airflow.cli.commands.local_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
|
@@ -18,31 +18,27 @@
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
20
|
import os
|
21
|
+
from collections.abc import Generator
|
21
22
|
from contextlib import contextmanager
|
22
|
-
from functools import
|
23
|
-
from
|
24
|
-
from typing import TYPE_CHECKING, Generator
|
23
|
+
from functools import cache
|
24
|
+
from typing import TYPE_CHECKING
|
25
25
|
|
26
26
|
from flask import Flask
|
27
27
|
|
28
28
|
import airflow
|
29
29
|
from airflow.configuration import conf
|
30
|
-
from airflow.exceptions import AirflowConfigException
|
31
|
-
from airflow.www.app import make_url
|
32
30
|
from airflow.www.extensions.init_appbuilder import init_appbuilder
|
33
|
-
from airflow.www.extensions.init_session import init_airflow_session_interface
|
34
31
|
from airflow.www.extensions.init_views import init_plugins
|
35
32
|
|
36
33
|
if TYPE_CHECKING:
|
37
34
|
from airflow.www.extensions.init_appbuilder import AirflowAppBuilder
|
38
35
|
|
39
36
|
|
40
|
-
@
|
37
|
+
@cache
|
41
38
|
def _return_appbuilder(app: Flask) -> AirflowAppBuilder:
|
42
39
|
"""Return an appbuilder instance for the given app."""
|
43
40
|
init_appbuilder(app)
|
44
41
|
init_plugins(app)
|
45
|
-
init_airflow_session_interface(app)
|
46
42
|
return app.appbuilder # type: ignore[attr-defined]
|
47
43
|
|
48
44
|
|
@@ -54,12 +50,4 @@ def get_application_builder() -> Generator[AirflowAppBuilder, None, None]:
|
|
54
50
|
with flask_app.app_context():
|
55
51
|
# Enable customizations in webserver_config.py to be applied via Flask.current_app.
|
56
52
|
flask_app.config.from_pyfile(webserver_config, silent=True)
|
57
|
-
flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
|
58
|
-
url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
|
59
|
-
if url.drivername == "sqlite" and url.database and not isabs(url.database):
|
60
|
-
raise AirflowConfigException(
|
61
|
-
f'Cannot use relative path: `{conf.get("database", "SQL_ALCHEMY_CONN")}` to connect to sqlite. '
|
62
|
-
"Please use absolute path such as `sqlite:////tmp/airflow.db`."
|
63
|
-
)
|
64
|
-
flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
65
53
|
yield _return_appbuilder(flask_app)
|
@@ -18,19 +18,20 @@
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
20
|
import logging
|
21
|
+
from collections.abc import Sequence
|
21
22
|
from functools import wraps
|
22
|
-
from typing import Callable,
|
23
|
+
from typing import Callable, TypeVar, cast
|
23
24
|
|
24
25
|
from flask import current_app, render_template, request
|
25
26
|
|
26
27
|
from airflow.api_connexion.exceptions import PermissionDenied
|
27
28
|
from airflow.api_connexion.security import check_authentication
|
29
|
+
from airflow.api_fastapi.app import get_auth_manager
|
28
30
|
from airflow.configuration import conf
|
29
31
|
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
30
32
|
from airflow.utils.airflow_flask_app import AirflowApp
|
31
33
|
from airflow.utils.net import get_hostname
|
32
34
|
from airflow.www.auth import _has_access
|
33
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
34
35
|
|
35
36
|
T = TypeVar("T", bound=Callable)
|
36
37
|
|
@@ -18,10 +18,10 @@
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
20
|
import argparse
|
21
|
-
import
|
21
|
+
from collections.abc import Container
|
22
22
|
from functools import cached_property
|
23
23
|
from pathlib import Path
|
24
|
-
from typing import TYPE_CHECKING,
|
24
|
+
from typing import TYPE_CHECKING, Any
|
25
25
|
|
26
26
|
import packaging.version
|
27
27
|
from connexion import FlaskApi
|
@@ -47,7 +47,7 @@ from airflow.cli.cli_config import (
|
|
47
47
|
GroupCommand,
|
48
48
|
)
|
49
49
|
from airflow.configuration import conf
|
50
|
-
from airflow.exceptions import AirflowConfigException, AirflowException
|
50
|
+
from airflow.exceptions import AirflowConfigException, AirflowException
|
51
51
|
from airflow.models import DagModel
|
52
52
|
from airflow.providers.fab.auth_manager.cli_commands.definition import (
|
53
53
|
DB_COMMANDS,
|
@@ -82,7 +82,7 @@ from airflow.security.permissions import (
|
|
82
82
|
RESOURCE_WEBSITE,
|
83
83
|
RESOURCE_XCOM,
|
84
84
|
)
|
85
|
-
from airflow.utils.session import NEW_SESSION, provide_session
|
85
|
+
from airflow.utils.session import NEW_SESSION, create_session, provide_session
|
86
86
|
from airflow.utils.yaml import safe_load
|
87
87
|
from airflow.version import version
|
88
88
|
from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
|
@@ -131,13 +131,18 @@ _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE = {
|
|
131
131
|
}
|
132
132
|
|
133
133
|
|
134
|
-
class FabAuthManager(BaseAuthManager):
|
134
|
+
class FabAuthManager(BaseAuthManager[User]):
|
135
135
|
"""
|
136
136
|
Flask-AppBuilder auth manager.
|
137
137
|
|
138
138
|
This auth manager is responsible for providing a backward compatible user management experience to users.
|
139
139
|
"""
|
140
140
|
|
141
|
+
def init(self) -> None:
|
142
|
+
"""Run operations when Airflow is initializing."""
|
143
|
+
if self.appbuilder:
|
144
|
+
self._sync_appbuilder_roles()
|
145
|
+
|
141
146
|
@staticmethod
|
142
147
|
def get_cli_commands() -> list[CLICommand]:
|
143
148
|
"""Vends CLI commands to be included in Airflow CLI."""
|
@@ -199,9 +204,12 @@ class FabAuthManager(BaseAuthManager):
|
|
199
204
|
|
200
205
|
return current_user
|
201
206
|
|
202
|
-
def
|
203
|
-
|
204
|
-
|
207
|
+
def deserialize_user(self, token: dict[str, Any]) -> User:
|
208
|
+
with create_session() as session:
|
209
|
+
return session.get(User, token["id"])
|
210
|
+
|
211
|
+
def serialize_user(self, user: User) -> dict[str, Any]:
|
212
|
+
return {"id": user.id}
|
205
213
|
|
206
214
|
def is_logged_in(self) -> bool:
|
207
215
|
"""Return whether the user is logged in."""
|
@@ -209,8 +217,10 @@ class FabAuthManager(BaseAuthManager):
|
|
209
217
|
if Version(Version(version).base_version) < Version("3.0.0"):
|
210
218
|
return not user.is_anonymous and user.is_active
|
211
219
|
else:
|
212
|
-
return
|
213
|
-
|
220
|
+
return (
|
221
|
+
self.appbuilder
|
222
|
+
and self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
|
223
|
+
or (not user.is_anonymous and user.is_active)
|
214
224
|
)
|
215
225
|
|
216
226
|
def is_authorized_configuration(
|
@@ -283,16 +293,6 @@ class FabAuthManager(BaseAuthManager):
|
|
283
293
|
) -> bool:
|
284
294
|
return self._is_authorized(method=method, resource_type=RESOURCE_ASSET, user=user)
|
285
295
|
|
286
|
-
def is_authorized_dataset(
|
287
|
-
self, *, method: ResourceMethod, details: AssetDetails | None = None, user: BaseUser | None = None
|
288
|
-
) -> bool:
|
289
|
-
warnings.warn(
|
290
|
-
"is_authorized_dataset will be renamed as is_authorized_asset in Airflow 3 and will be removed when the minimum Airflow version is set to 3.0 for the fab provider",
|
291
|
-
AirflowProviderDeprecationWarning,
|
292
|
-
stacklevel=2,
|
293
|
-
)
|
294
|
-
return self.is_authorized_asset(method=method, user=user)
|
295
|
-
|
296
296
|
def is_authorized_pool(
|
297
297
|
self, *, method: ResourceMethod, details: PoolDetails | None = None, user: BaseUser | None = None
|
298
298
|
) -> bool:
|
@@ -376,6 +376,9 @@ class FabAuthManager(BaseAuthManager):
|
|
376
376
|
FabAirflowSecurityManagerOverride,
|
377
377
|
)
|
378
378
|
|
379
|
+
if not self.appbuilder:
|
380
|
+
raise AirflowException("AppBuilder is not initialized.")
|
381
|
+
|
379
382
|
sm_from_config = self.appbuilder.get_app.config.get("SECURITY_MANAGER_CLASS")
|
380
383
|
if sm_from_config:
|
381
384
|
if not issubclass(sm_from_config, FabAirflowSecurityManagerOverride):
|
@@ -547,6 +550,9 @@ class FabAuthManager(BaseAuthManager):
|
|
547
550
|
|
548
551
|
:meta private:
|
549
552
|
"""
|
553
|
+
if not self.appbuilder:
|
554
|
+
raise AirflowException("AppBuilder is not initialized.")
|
555
|
+
|
550
556
|
if "." in dag_id and hasattr(DagModel, "root_dag_id"):
|
551
557
|
return self.appbuilder.get_session.scalar(
|
552
558
|
select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id).limit(1)
|
@@ -24,13 +24,12 @@ import logging
|
|
24
24
|
import os
|
25
25
|
import random
|
26
26
|
import uuid
|
27
|
-
import
|
28
|
-
from typing import TYPE_CHECKING, Any, Callable
|
27
|
+
from collections.abc import Collection, Iterable, Mapping, Sequence
|
28
|
+
from typing import TYPE_CHECKING, Any, Callable
|
29
29
|
|
30
30
|
import jwt
|
31
31
|
import packaging.version
|
32
32
|
import re2
|
33
|
-
from deprecated import deprecated
|
34
33
|
from flask import flash, g, has_request_context, session
|
35
34
|
from flask_appbuilder import const
|
36
35
|
from flask_appbuilder.const import (
|
@@ -70,13 +69,13 @@ from itsdangerous import want_bytes
|
|
70
69
|
from markupsafe import Markup
|
71
70
|
from sqlalchemy import and_, func, inspect, literal, or_, select
|
72
71
|
from sqlalchemy.exc import MultipleResultsFound
|
73
|
-
from sqlalchemy.orm import
|
72
|
+
from sqlalchemy.orm import joinedload
|
74
73
|
from werkzeug.security import check_password_hash, generate_password_hash
|
75
74
|
|
76
75
|
from airflow import __version__ as airflow_version
|
77
|
-
from airflow.
|
76
|
+
from airflow.api_fastapi.app import get_auth_manager
|
78
77
|
from airflow.configuration import conf
|
79
|
-
from airflow.exceptions import AirflowException
|
78
|
+
from airflow.exceptions import AirflowException
|
80
79
|
from airflow.models import DagBag, DagModel
|
81
80
|
from airflow.providers.fab.auth_manager.models import (
|
82
81
|
Action,
|
@@ -109,13 +108,10 @@ from airflow.providers.fab.auth_manager.views.user_edit import (
|
|
109
108
|
)
|
110
109
|
from airflow.providers.fab.auth_manager.views.user_stats import CustomUserStatsChartView
|
111
110
|
from airflow.security import permissions
|
112
|
-
from airflow.utils.session import NEW_SESSION, provide_session
|
113
|
-
from airflow.www.extensions.init_auth_manager import get_auth_manager
|
114
111
|
from airflow.www.security_manager import AirflowSecurityManagerV2
|
115
112
|
from airflow.www.session import AirflowDatabaseSessionInterface
|
116
113
|
|
117
114
|
if TYPE_CHECKING:
|
118
|
-
from airflow.auth.managers.base_auth_manager import ResourceMethod
|
119
115
|
from airflow.security.permissions import RESOURCE_ASSET
|
120
116
|
else:
|
121
117
|
from airflow.providers.common.compat.security.permissions import RESOURCE_ASSET
|
@@ -576,7 +572,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
576
572
|
session_details = interface.serializer.loads(want_bytes(s.data))
|
577
573
|
if session_details.get("_user_id") == user.id:
|
578
574
|
session.delete(s)
|
579
|
-
session.commit()
|
580
575
|
else:
|
581
576
|
self._cli_safe_flash(
|
582
577
|
"Since you are using `securecookie` session backend mechanism, we cannot prevent "
|
@@ -774,14 +769,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
774
769
|
"""Get the admin role."""
|
775
770
|
return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
|
776
771
|
|
777
|
-
@property
|
778
|
-
@deprecated(
|
779
|
-
reason="The 'oauth_whitelists' property is deprecated. Please use 'oauth_allow_list' instead.",
|
780
|
-
category=AirflowProviderDeprecationWarning,
|
781
|
-
)
|
782
|
-
def oauth_whitelists(self):
|
783
|
-
return self.oauth_allow_list
|
784
|
-
|
785
772
|
def create_builtin_roles(self):
|
786
773
|
"""Return FAB builtin roles."""
|
787
774
|
return self.appbuilder.get_app.config.get("FAB_ROLES", {})
|
@@ -847,24 +834,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
847
834
|
app.config.setdefault("AUTH_ROLES_SYNC_AT_LOGIN", False)
|
848
835
|
app.config.setdefault("AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS", False)
|
849
836
|
|
850
|
-
from packaging.version import Version
|
851
|
-
from werkzeug import __version__ as werkzeug_version
|
852
|
-
|
853
|
-
parsed_werkzeug_version = Version(werkzeug_version)
|
854
|
-
if parsed_werkzeug_version < Version("3.0.0"):
|
855
|
-
app.config.setdefault(
|
856
|
-
"AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
|
857
|
-
"pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"
|
858
|
-
"c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c",
|
859
|
-
)
|
860
|
-
else:
|
861
|
-
app.config.setdefault(
|
862
|
-
"AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
|
863
|
-
"scrypt:32768:8:1$wiDa0ruWlIPhp9LM$6e409d093e62ad54df2af895d0e125b05ff6cf6414"
|
864
|
-
"8350189ffc4bcc71286edf1b8ad94a442c00f890224bf2b32153d0750c89ee9"
|
865
|
-
"401e62f9dcee5399065e4e5",
|
866
|
-
)
|
867
|
-
|
868
837
|
# LDAP Config
|
869
838
|
if self.auth_type == AUTH_LDAP:
|
870
839
|
if "AUTH_LDAP_SERVER" not in app.config:
|
@@ -904,15 +873,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
904
873
|
:meta private:
|
905
874
|
"""
|
906
875
|
app = self.appbuilder.get_app
|
907
|
-
if self.auth_type == AUTH_OID:
|
908
|
-
from flask_openid import OpenID
|
909
|
-
|
910
|
-
log.warning(
|
911
|
-
"AUTH_OID is deprecated and will be removed in version 5. "
|
912
|
-
"Migrate to other authentication methods."
|
913
|
-
)
|
914
|
-
self.oid = OpenID(app)
|
915
|
-
|
916
876
|
if self.auth_type == AUTH_OAUTH:
|
917
877
|
from authlib.integrations.flask_client import OAuth
|
918
878
|
|
@@ -985,70 +945,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
985
945
|
log.exception(const.LOGMSG_ERR_SEC_CREATE_DB)
|
986
946
|
exit(1)
|
987
947
|
|
988
|
-
def get_readable_dags(self, user) -> Iterable[DagModel]:
|
989
|
-
"""Get the DAGs readable by authenticated user."""
|
990
|
-
warnings.warn(
|
991
|
-
"`get_readable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
|
992
|
-
"instead.",
|
993
|
-
RemovedInAirflow3Warning,
|
994
|
-
stacklevel=2,
|
995
|
-
)
|
996
|
-
with warnings.catch_warnings():
|
997
|
-
warnings.simplefilter("ignore", RemovedInAirflow3Warning)
|
998
|
-
return self.get_accessible_dags([permissions.ACTION_CAN_READ], user)
|
999
|
-
|
1000
|
-
def get_editable_dags(self, user) -> Iterable[DagModel]:
|
1001
|
-
"""Get the DAGs editable by authenticated user."""
|
1002
|
-
warnings.warn(
|
1003
|
-
"`get_editable_dags` has been deprecated. Please use `get_auth_manager().get_permitted_dag_ids` "
|
1004
|
-
"instead.",
|
1005
|
-
RemovedInAirflow3Warning,
|
1006
|
-
stacklevel=2,
|
1007
|
-
)
|
1008
|
-
with warnings.catch_warnings():
|
1009
|
-
warnings.simplefilter("ignore", RemovedInAirflow3Warning)
|
1010
|
-
return self.get_accessible_dags([permissions.ACTION_CAN_EDIT], user)
|
1011
|
-
|
1012
|
-
@provide_session
|
1013
|
-
def get_accessible_dags(
|
1014
|
-
self,
|
1015
|
-
user_actions: Container[str] | None,
|
1016
|
-
user,
|
1017
|
-
session: Session = NEW_SESSION,
|
1018
|
-
) -> Iterable[DagModel]:
|
1019
|
-
warnings.warn(
|
1020
|
-
"`get_accessible_dags` has been deprecated. Please use "
|
1021
|
-
"`get_auth_manager().get_permitted_dag_ids` instead.",
|
1022
|
-
RemovedInAirflow3Warning,
|
1023
|
-
stacklevel=3,
|
1024
|
-
)
|
1025
|
-
|
1026
|
-
dag_ids = self.get_accessible_dag_ids(user, user_actions, session)
|
1027
|
-
return session.scalars(select(DagModel).where(DagModel.dag_id.in_(dag_ids)))
|
1028
|
-
|
1029
|
-
@provide_session
|
1030
|
-
def get_accessible_dag_ids(
|
1031
|
-
self,
|
1032
|
-
user,
|
1033
|
-
user_actions: Container[str] | None = None,
|
1034
|
-
session: Session = NEW_SESSION,
|
1035
|
-
) -> set[str]:
|
1036
|
-
warnings.warn(
|
1037
|
-
"`get_accessible_dag_ids` has been deprecated. Please use "
|
1038
|
-
"`get_auth_manager().get_permitted_dag_ids` instead.",
|
1039
|
-
RemovedInAirflow3Warning,
|
1040
|
-
stacklevel=3,
|
1041
|
-
)
|
1042
|
-
if not user_actions:
|
1043
|
-
user_actions = [permissions.ACTION_CAN_EDIT, permissions.ACTION_CAN_READ]
|
1044
|
-
method_from_fab_action_map = get_method_from_fab_action_map()
|
1045
|
-
user_methods: Container[ResourceMethod] = [
|
1046
|
-
method_from_fab_action_map[action]
|
1047
|
-
for action in method_from_fab_action_map
|
1048
|
-
if action in user_actions
|
1049
|
-
]
|
1050
|
-
return get_auth_manager().get_permitted_dag_ids(user=user, methods=user_methods, session=session)
|
1051
|
-
|
1052
948
|
@staticmethod
|
1053
949
|
def get_readable_dag_ids(user=None) -> set[str]:
|
1054
950
|
"""Get the DAG IDs readable by authenticated user."""
|
@@ -1107,17 +1003,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1107
1003
|
if dag.access_control is not None:
|
1108
1004
|
self.sync_perm_for_dag(root_dag_id, dag.access_control)
|
1109
1005
|
|
1110
|
-
def prefixed_dag_id(self, dag_id: str) -> str:
|
1111
|
-
"""Return the permission name for a DAG id."""
|
1112
|
-
warnings.warn(
|
1113
|
-
"`prefixed_dag_id` has been deprecated. "
|
1114
|
-
"Please use `airflow.security.permissions.resource_name` instead.",
|
1115
|
-
RemovedInAirflow3Warning,
|
1116
|
-
stacklevel=2,
|
1117
|
-
)
|
1118
|
-
root_dag_id = self._get_root_dag_id(dag_id)
|
1119
|
-
return self._resource_name(root_dag_id, permissions.RESOURCE_DAG)
|
1120
|
-
|
1121
1006
|
def is_dag_resource(self, resource_name: str) -> bool:
|
1122
1007
|
"""Determine if a resource belongs to a DAG or all DAGs."""
|
1123
1008
|
if resource_name == permissions.RESOURCE_DAG:
|
@@ -1451,20 +1336,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1451
1336
|
def perms_include_action(self, perms, action_name):
|
1452
1337
|
return any(perm.action and perm.action.name == action_name for perm in perms)
|
1453
1338
|
|
1454
|
-
def init_role(self, role_name, perms) -> None:
|
1455
|
-
"""
|
1456
|
-
Initialize the role with actions and related resources.
|
1457
|
-
|
1458
|
-
:param role_name:
|
1459
|
-
:param perms:
|
1460
|
-
"""
|
1461
|
-
warnings.warn(
|
1462
|
-
"`init_role` has been deprecated. Please use `bulk_sync_roles` instead.",
|
1463
|
-
RemovedInAirflow3Warning,
|
1464
|
-
stacklevel=2,
|
1465
|
-
)
|
1466
|
-
self.bulk_sync_roles([{"role": role_name, "perms": perms}])
|
1467
|
-
|
1468
1339
|
def bulk_sync_roles(self, roles: Iterable[dict[str, Any]]) -> None:
|
1469
1340
|
"""Sync the provided roles and permissions."""
|
1470
1341
|
existing_roles = self._get_all_roles_with_permissions()
|
@@ -2226,7 +2097,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
2226
2097
|
if user is None or (not user.is_active):
|
2227
2098
|
# Balance failure and success
|
2228
2099
|
check_password_hash(
|
2229
|
-
|
2100
|
+
"pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"
|
2101
|
+
"c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c",
|
2230
2102
|
"password",
|
2231
2103
|
)
|
2232
2104
|
log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
|
@@ -28,10 +28,9 @@ def get_provider_info():
|
|
28
28
|
"name": "Fab",
|
29
29
|
"description": "`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__\n",
|
30
30
|
"state": "ready",
|
31
|
-
"source-date-epoch":
|
31
|
+
"source-date-epoch": 1731570160,
|
32
32
|
"versions": [
|
33
|
-
"
|
34
|
-
"1.5.2",
|
33
|
+
"2.0.0",
|
35
34
|
"1.5.1",
|
36
35
|
"1.5.0",
|
37
36
|
"1.4.1",
|
@@ -49,10 +48,10 @@ def get_provider_info():
|
|
49
48
|
"1.0.0",
|
50
49
|
],
|
51
50
|
"dependencies": [
|
52
|
-
"apache-airflow>=
|
51
|
+
"apache-airflow>=3.0.0.dev0",
|
53
52
|
"apache-airflow-providers-common-compat>=1.2.1",
|
54
53
|
"flask>=2.2,<2.3",
|
55
|
-
"flask-appbuilder==4.5.
|
54
|
+
"flask-appbuilder==4.5.2",
|
56
55
|
"flask-login>=0.6.2",
|
57
56
|
"google-re2>=1.0",
|
58
57
|
"jmespath>=0.7.0",
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
4
|
+
# distributed with this work for additional information
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance
|
8
|
+
# with the License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
13
|
+
# software distributed under the License is distributed on an
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
15
|
+
# KIND, either express or implied. See the License for the
|
16
|
+
# specific language governing permissions and limitations
|
17
|
+
# under the License.
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
4
|
+
# distributed with this work for additional information
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance
|
8
|
+
# with the License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
13
|
+
# software distributed under the License is distributed on an
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
15
|
+
# KIND, either express or implied. See the License for the
|
16
|
+
# specific language governing permissions and limitations
|
17
|
+
# under the License.
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
from os.path import isabs
|
21
|
+
|
22
|
+
from flask import Flask
|
23
|
+
from flask_appbuilder import SQLA
|
24
|
+
from flask_wtf.csrf import CSRFProtect
|
25
|
+
from sqlalchemy.engine.url import make_url
|
26
|
+
|
27
|
+
from airflow import settings
|
28
|
+
from airflow.configuration import conf
|
29
|
+
from airflow.exceptions import AirflowConfigException
|
30
|
+
from airflow.logging_config import configure_logging
|
31
|
+
from airflow.providers.fab.www.extensions.init_appbuilder import init_appbuilder
|
32
|
+
from airflow.providers.fab.www.extensions.init_jinja_globals import init_jinja_globals
|
33
|
+
from airflow.providers.fab.www.extensions.init_manifest_files import configure_manifest_files
|
34
|
+
from airflow.providers.fab.www.extensions.init_security import init_xframe_protection
|
35
|
+
from airflow.providers.fab.www.extensions.init_views import init_error_handlers, init_plugins
|
36
|
+
|
37
|
+
app: Flask | None = None
|
38
|
+
|
39
|
+
# Initializes at the module level, so plugins can access it.
|
40
|
+
# See: /docs/plugins.rst
|
41
|
+
csrf = CSRFProtect()
|
42
|
+
|
43
|
+
|
44
|
+
def create_app(config=None, testing=False):
|
45
|
+
"""Create a new instance of Airflow WWW app."""
|
46
|
+
flask_app = Flask(__name__)
|
47
|
+
flask_app.secret_key = conf.get("webserver", "SECRET_KEY")
|
48
|
+
flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
|
49
|
+
|
50
|
+
url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
|
51
|
+
if url.drivername == "sqlite" and url.database and not isabs(url.database):
|
52
|
+
raise AirflowConfigException(
|
53
|
+
f'Cannot use relative path: `{conf.get("database", "SQL_ALCHEMY_CONN")}` to connect to sqlite. '
|
54
|
+
"Please use absolute path such as `sqlite:////tmp/airflow.db`."
|
55
|
+
)
|
56
|
+
|
57
|
+
if "SQLALCHEMY_ENGINE_OPTIONS" not in flask_app.config:
|
58
|
+
flask_app.config["SQLALCHEMY_ENGINE_OPTIONS"] = settings.prepare_engine_args()
|
59
|
+
|
60
|
+
db = SQLA()
|
61
|
+
db.session = settings.Session
|
62
|
+
db.init_app(flask_app)
|
63
|
+
|
64
|
+
configure_logging()
|
65
|
+
configure_manifest_files(flask_app)
|
66
|
+
|
67
|
+
with flask_app.app_context():
|
68
|
+
init_appbuilder(flask_app)
|
69
|
+
init_plugins(flask_app)
|
70
|
+
init_error_handlers(flask_app)
|
71
|
+
init_jinja_globals(flask_app)
|
72
|
+
init_xframe_protection(flask_app)
|
73
|
+
return flask_app
|
74
|
+
|
75
|
+
|
76
|
+
def cached_app(config=None, testing=False):
|
77
|
+
"""Return cached instance of Airflow WWW app."""
|
78
|
+
global app
|
79
|
+
if not app:
|
80
|
+
app = create_app(config=config, testing=testing)
|
81
|
+
return app
|
82
|
+
|
83
|
+
|
84
|
+
def purge_cached_app():
|
85
|
+
"""Remove the cached version of the app in global state."""
|
86
|
+
global app
|
87
|
+
app = None
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|