apache-airflow-providers-fab 1.5.3__py3-none-any.whl → 2.0.0__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.
Files changed (105) hide show
  1. airflow/providers/fab/LICENSE +0 -52
  2. airflow/providers/fab/__init__.py +3 -3
  3. airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +4 -5
  4. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +5 -5
  5. airflow/providers/fab/auth_manager/api/auth/backend/session.py +2 -2
  6. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +15 -15
  7. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +13 -14
  8. airflow/providers/fab/auth_manager/api_fastapi/__init__.py +16 -0
  9. airflow/providers/fab/auth_manager/api_fastapi/datamodels/__init__.py +16 -0
  10. airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +32 -0
  11. airflow/providers/fab/auth_manager/api_fastapi/openapi/__init__.py +16 -0
  12. airflow/providers/fab/auth_manager/api_fastapi/openapi/v1-generated.yaml +153 -0
  13. airflow/providers/fab/auth_manager/api_fastapi/routes/__init__.py +16 -0
  14. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +51 -0
  15. airflow/providers/fab/auth_manager/api_fastapi/services/__init__.py +16 -0
  16. airflow/providers/fab/auth_manager/api_fastapi/services/login.py +58 -0
  17. airflow/providers/fab/auth_manager/cli_commands/db_command.py +1 -3
  18. airflow/providers/fab/auth_manager/cli_commands/user_command.py +2 -2
  19. airflow/providers/fab/auth_manager/cli_commands/utils.py +12 -11
  20. airflow/providers/fab/auth_manager/fab_auth_manager.py +238 -126
  21. airflow/providers/fab/auth_manager/models/__init__.py +1 -1
  22. airflow/providers/fab/auth_manager/models/anonymous_user.py +1 -1
  23. airflow/providers/fab/auth_manager/models/db.py +22 -5
  24. airflow/providers/fab/auth_manager/openapi/v1.yaml +9 -0
  25. airflow/providers/fab/auth_manager/schemas/user_schema.py +1 -1
  26. airflow/providers/fab/auth_manager/security_manager/override.py +186 -655
  27. airflow/providers/fab/auth_manager/views/permissions.py +1 -1
  28. airflow/providers/fab/auth_manager/views/roles_list.py +1 -1
  29. airflow/providers/fab/auth_manager/views/user.py +1 -1
  30. airflow/providers/fab/auth_manager/views/user_edit.py +1 -1
  31. airflow/providers/fab/auth_manager/views/user_stats.py +1 -1
  32. airflow/providers/fab/get_provider_info.py +29 -34
  33. airflow/providers/fab/www/airflow_flask_app.py +31 -0
  34. airflow/providers/fab/www/api_connexion/__init__.py +17 -0
  35. airflow/providers/fab/www/api_connexion/exceptions.py +197 -0
  36. airflow/providers/fab/www/api_connexion/parameters.py +131 -0
  37. airflow/providers/fab/www/api_connexion/security.py +84 -0
  38. airflow/providers/fab/www/api_connexion/types.py +30 -0
  39. airflow/providers/fab/www/app.py +120 -0
  40. airflow/providers/fab/www/auth.py +350 -0
  41. airflow/providers/fab/www/constants.py +28 -0
  42. airflow/providers/fab/www/extensions/__init__.py +16 -0
  43. airflow/providers/fab/www/extensions/init_appbuilder.py +606 -0
  44. airflow/providers/fab/www/extensions/init_jinja_globals.py +82 -0
  45. airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
  46. airflow/providers/fab/www/extensions/init_security.py +61 -0
  47. airflow/providers/fab/www/extensions/init_session.py +64 -0
  48. airflow/providers/fab/www/extensions/init_views.py +177 -0
  49. airflow/providers/fab/www/package-lock.json +8939 -0
  50. airflow/providers/fab/www/package.json +77 -0
  51. airflow/providers/fab/www/security/__init__.py +17 -0
  52. airflow/providers/fab/www/security/permissions.py +126 -0
  53. airflow/providers/fab/www/security_appless.py +44 -0
  54. airflow/providers/fab/www/security_manager.py +122 -0
  55. airflow/providers/fab/www/session.py +41 -0
  56. airflow/providers/fab/www/static/css/bootstrap-theme.css +6215 -0
  57. airflow/providers/fab/www/static/css/flash.css +57 -0
  58. airflow/providers/fab/www/static/css/loading-dots.css +60 -0
  59. airflow/providers/fab/www/static/css/main.css +676 -0
  60. airflow/providers/fab/www/static/css/material-icons.css +84 -0
  61. airflow/providers/fab/www/static/dist/48f0ea180c40270a5b05.png +1 -0
  62. airflow/providers/fab/www/static/dist/649c0b07771e68fafdeb.png +1 -0
  63. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.css +33 -0
  64. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.js +1 -0
  65. airflow/providers/fab/www/static/dist/f7490d556a6c42e49ba4.png +1 -0
  66. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.css +18 -0
  67. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.js +1 -0
  68. airflow/providers/fab/www/static/dist/jquery-ui.min.css +5 -0
  69. airflow/providers/fab/www/static/dist/jquery-ui.min.js +2 -0
  70. airflow/providers/fab/www/static/dist/jquery-ui.min.js.LICENSE.txt +4 -0
  71. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.css +18 -0
  72. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.js +1 -0
  73. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.css +18 -0
  74. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js +2 -0
  75. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js.LICENSE.txt +18 -0
  76. airflow/providers/fab/www/static/dist/manifest.json +20 -0
  77. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +18 -0
  78. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.js +1 -0
  79. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js +2 -0
  80. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js.LICENSE.txt +11 -0
  81. airflow/providers/fab/www/static/dist/oss-licenses.json +20 -0
  82. airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
  83. airflow/providers/fab/www/static/js/main.js +324 -0
  84. airflow/providers/fab/www/static/sort_asc.png +0 -0
  85. airflow/providers/fab/www/static/sort_both.png +0 -0
  86. airflow/providers/fab/www/static/sort_desc.png +0 -0
  87. airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
  88. airflow/providers/fab/www/templates/airflow/error.html +35 -0
  89. airflow/providers/fab/www/templates/airflow/main.html +78 -0
  90. airflow/providers/fab/www/templates/airflow/traceback.html +53 -0
  91. airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
  92. airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
  93. airflow/providers/fab/www/templates/appbuilder/navbar.html +60 -0
  94. airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -0
  95. airflow/providers/fab/www/templates/appbuilder/navbar_right.html +64 -0
  96. airflow/providers/fab/www/utils.py +288 -0
  97. airflow/providers/fab/www/views.py +129 -0
  98. airflow/providers/fab/www/webpack.config.js +213 -0
  99. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/METADATA +29 -37
  100. apache_airflow_providers_fab-2.0.0.dist-info/RECORD +125 -0
  101. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/WHEEL +1 -1
  102. airflow/providers/fab/auth_manager/decorators/auth.py +0 -126
  103. apache_airflow_providers_fab-1.5.3.dist-info/RECORD +0 -51
  104. /airflow/providers/fab/{auth_manager/decorators → www}/__init__.py +0 -0
  105. {apache_airflow_providers_fab-1.5.3.dist-info → apache_airflow_providers_fab-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,120 @@
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 datetime import timedelta
21
+ from os.path import isabs
22
+
23
+ from flask import Flask
24
+ from flask_appbuilder import SQLA
25
+ from flask_wtf.csrf import CSRFProtect
26
+ from sqlalchemy.engine.url import make_url
27
+
28
+ from airflow import settings
29
+ from airflow.api_fastapi.app import get_auth_manager
30
+ from airflow.configuration import conf
31
+ from airflow.exceptions import AirflowConfigException
32
+ from airflow.logging_config import configure_logging
33
+ from airflow.providers.fab.www.extensions.init_appbuilder import init_appbuilder
34
+ from airflow.providers.fab.www.extensions.init_jinja_globals import init_jinja_globals
35
+ from airflow.providers.fab.www.extensions.init_manifest_files import configure_manifest_files
36
+ from airflow.providers.fab.www.extensions.init_security import init_api_auth, init_xframe_protection
37
+ from airflow.providers.fab.www.extensions.init_session import init_airflow_session_interface
38
+ from airflow.providers.fab.www.extensions.init_views import (
39
+ init_api_auth_provider,
40
+ init_api_error_handlers,
41
+ init_error_handlers,
42
+ init_plugins,
43
+ )
44
+ from airflow.providers.fab.www.utils import get_session_lifetime_config
45
+
46
+ app: Flask | None = None
47
+
48
+ # Initializes at the module level, so plugins can access it.
49
+ # See: /docs/plugins.rst
50
+ csrf = CSRFProtect()
51
+
52
+
53
+ def create_app(enable_plugins: bool):
54
+ """Create a new instance of Airflow WWW app."""
55
+ from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
56
+
57
+ flask_app = Flask(__name__)
58
+ flask_app.secret_key = conf.get("webserver", "SECRET_KEY")
59
+ flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database", "SQL_ALCHEMY_CONN")
60
+ flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
61
+ flask_app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=get_session_lifetime_config())
62
+
63
+ webserver_config = conf.get_mandatory_value("fab", "config_file")
64
+ # Enable customizations in webserver_config.py to be applied via Flask.current_app.
65
+ with flask_app.app_context():
66
+ flask_app.config.from_pyfile(webserver_config, silent=True)
67
+
68
+ url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
69
+ if url.drivername == "sqlite" and url.database and not isabs(url.database):
70
+ raise AirflowConfigException(
71
+ f"Cannot use relative path: `{conf.get('database', 'SQL_ALCHEMY_CONN')}` to connect to sqlite. "
72
+ "Please use absolute path such as `sqlite:////tmp/airflow.db`."
73
+ )
74
+
75
+ if "SQLALCHEMY_ENGINE_OPTIONS" not in flask_app.config:
76
+ flask_app.config["SQLALCHEMY_ENGINE_OPTIONS"] = settings.prepare_engine_args()
77
+
78
+ csrf.init_app(flask_app)
79
+
80
+ db = SQLA()
81
+ db.session = settings.Session
82
+ db.init_app(flask_app)
83
+
84
+ configure_logging()
85
+ configure_manifest_files(flask_app)
86
+ init_api_auth(flask_app)
87
+
88
+ with flask_app.app_context():
89
+ init_appbuilder(flask_app, enable_plugins=enable_plugins)
90
+ init_error_handlers(flask_app)
91
+ # In two scenarios a Flask application can be created:
92
+ # - To support Airflow 2 plugins relying on Flask (``enable_plugins`` is True)
93
+ # - To support FAB auth manager (``enable_plugins`` is False)
94
+ # There are some edge cases where ``enable_plugins`` is False but the auth manager configured is not
95
+ # FAB auth manager. One example is ``run_update_fastapi_api_spec``, it calls
96
+ # ``FabAuthManager().get_fastapi_app()`` to generate the openapi documentation regardless of the
97
+ # configured auth manager.
98
+ if enable_plugins:
99
+ init_plugins(flask_app)
100
+ elif isinstance(get_auth_manager(), FabAuthManager):
101
+ init_api_auth_provider(flask_app)
102
+ init_api_error_handlers(flask_app)
103
+ init_jinja_globals(flask_app, enable_plugins=enable_plugins)
104
+ init_xframe_protection(flask_app)
105
+ init_airflow_session_interface(flask_app)
106
+ return flask_app
107
+
108
+
109
+ def cached_app():
110
+ """Return cached instance of Airflow WWW app."""
111
+ global app
112
+ if not app:
113
+ app = create_app()
114
+ return app
115
+
116
+
117
+ def purge_cached_app():
118
+ """Remove the cached version of the app in global state."""
119
+ global app
120
+ app = None
@@ -0,0 +1,350 @@
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.
17
+ from __future__ import annotations
18
+
19
+ import functools
20
+ import logging
21
+ from collections.abc import Sequence
22
+ from functools import wraps
23
+ from typing import TYPE_CHECKING, Callable, TypeVar, cast
24
+
25
+ from flask import flash, redirect, render_template, request, url_for
26
+ from flask_appbuilder._compat import as_unicode
27
+ from flask_appbuilder.const import (
28
+ FLAMSG_ERR_SEC_ACCESS_DENIED,
29
+ LOGMSG_ERR_SEC_ACCESS_DENIED,
30
+ PERMISSION_PREFIX,
31
+ )
32
+
33
+ from airflow.api_fastapi.app import get_auth_manager
34
+ from airflow.api_fastapi.auth.managers.models.resource_details import (
35
+ AccessView,
36
+ ConnectionDetails,
37
+ DagAccessEntity,
38
+ DagDetails,
39
+ PoolDetails,
40
+ VariableDetails,
41
+ )
42
+ from airflow.configuration import conf
43
+ from airflow.providers.fab.www.utils import get_fab_auth_manager
44
+ from airflow.utils.net import get_hostname
45
+
46
+ if TYPE_CHECKING:
47
+ from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod
48
+ from airflow.api_fastapi.auth.managers.models.batch_apis import (
49
+ IsAuthorizedConnectionRequest,
50
+ IsAuthorizedDagRequest,
51
+ IsAuthorizedPoolRequest,
52
+ IsAuthorizedVariableRequest,
53
+ )
54
+ from airflow.models import DagRun, Pool, TaskInstance, Variable
55
+ from airflow.models.connection import Connection
56
+ from airflow.models.xcom import XComModel
57
+
58
+ T = TypeVar("T", bound=Callable)
59
+
60
+ log = logging.getLogger(__name__)
61
+
62
+
63
+ def get_access_denied_message():
64
+ return conf.get("webserver", "access_denied_message")
65
+
66
+
67
+ def has_access_with_pk(f):
68
+ """
69
+ Check permissions on views.
70
+
71
+ The implementation is very similar from
72
+ https://github.com/dpgaspar/Flask-AppBuilder/blob/c6fecdc551629e15467fde5d06b4437379d90592/flask_appbuilder/security/decorators.py#L134
73
+
74
+ The difference is that this decorator will pass the resource ID to check permissions. It allows
75
+ fined-grained access control using resource IDs.
76
+ """
77
+ if hasattr(f, "_permission_name"):
78
+ permission_str = f._permission_name
79
+ else:
80
+ permission_str = f.__name__
81
+
82
+ def wraps(self, *args, **kwargs):
83
+ permission_str = f"{PERMISSION_PREFIX}{f._permission_name}"
84
+ if self.method_permission_name:
85
+ _permission_name = self.method_permission_name.get(f.__name__)
86
+ if _permission_name:
87
+ permission_str = f"{PERMISSION_PREFIX}{_permission_name}"
88
+ if permission_str in self.base_permissions and self.appbuilder.sm.has_access(
89
+ action_name=permission_str,
90
+ resource_name=self.class_permission_name,
91
+ resource_pk=kwargs.get("pk"),
92
+ ):
93
+ return f(self, *args, **kwargs)
94
+ else:
95
+ log.warning(LOGMSG_ERR_SEC_ACCESS_DENIED, permission_str, self.__class__.__name__)
96
+ flash(as_unicode(FLAMSG_ERR_SEC_ACCESS_DENIED), "danger")
97
+ return redirect(get_auth_manager().get_url_login(next_url=request.url))
98
+
99
+ f._permission_name = permission_str
100
+ return functools.update_wrapper(wraps, f)
101
+
102
+
103
+ def _has_access_no_details(is_authorized_callback: Callable[[], bool]) -> Callable[[T], T]:
104
+ """
105
+ Check current user's permissions against required permissions.
106
+
107
+ This works only for resources with no details. This function is used in some ``has_access_`` functions
108
+ below.
109
+
110
+ :param is_authorized_callback: callback to execute to figure whether the user is authorized to access
111
+ the resource?
112
+ """
113
+
114
+ def has_access_decorator(func: T):
115
+ @wraps(func)
116
+ def decorated(*args, **kwargs):
117
+ return _has_access(
118
+ is_authorized=is_authorized_callback(),
119
+ func=func,
120
+ args=args,
121
+ kwargs=kwargs,
122
+ )
123
+
124
+ return cast("T", decorated)
125
+
126
+ return has_access_decorator
127
+
128
+
129
+ def _has_access(*, is_authorized: bool, func: Callable, args, kwargs):
130
+ """
131
+ Define the behavior whether the user is authorized to access the resource.
132
+
133
+ :param is_authorized: whether the user is authorized to access the resource
134
+ :param func: the function to call if the user is authorized
135
+ :param args: the arguments of ``func``
136
+ :param kwargs: the keyword arguments ``func``
137
+
138
+ :meta private:
139
+ """
140
+ if is_authorized:
141
+ return func(*args, **kwargs)
142
+ elif get_fab_auth_manager().is_logged_in() and not get_auth_manager().is_authorized_view(
143
+ access_view=AccessView.WEBSITE,
144
+ user=get_fab_auth_manager().get_user(),
145
+ ):
146
+ return (
147
+ render_template(
148
+ "airflow/no_roles_permissions.html",
149
+ hostname=get_hostname() if conf.getboolean("webserver", "EXPOSE_HOSTNAME") else "",
150
+ logout_url=get_fab_auth_manager().get_url_logout(),
151
+ ),
152
+ 403,
153
+ )
154
+ elif not get_fab_auth_manager().is_logged_in():
155
+ return redirect(get_auth_manager().get_url_login(next_url=request.url))
156
+ else:
157
+ access_denied = get_access_denied_message()
158
+ flash(access_denied, "danger")
159
+ return redirect(url_for("FabIndexView.index"))
160
+
161
+
162
+ def has_access_configuration(method: ResourceMethod) -> Callable[[T], T]:
163
+ return _has_access_no_details(
164
+ lambda: get_auth_manager().is_authorized_configuration(
165
+ method=method, user=get_fab_auth_manager().get_user()
166
+ )
167
+ )
168
+
169
+
170
+ def has_access_connection(method: ResourceMethod) -> Callable[[T], T]:
171
+ def has_access_decorator(func: T):
172
+ @wraps(func)
173
+ def decorated(*args, **kwargs):
174
+ connections: set[Connection] = set(args[1])
175
+ requests: Sequence[IsAuthorizedConnectionRequest] = [
176
+ {
177
+ "method": method,
178
+ "details": ConnectionDetails(conn_id=connection.conn_id),
179
+ }
180
+ for connection in connections
181
+ ]
182
+ is_authorized = get_auth_manager().batch_is_authorized_connection(
183
+ requests, user=get_auth_manager().get_user()
184
+ )
185
+ return _has_access(
186
+ is_authorized=is_authorized,
187
+ func=func,
188
+ args=args,
189
+ kwargs=kwargs,
190
+ )
191
+
192
+ return cast("T", decorated)
193
+
194
+ return has_access_decorator
195
+
196
+
197
+ def has_access_dag(method: ResourceMethod, access_entity: DagAccessEntity | None = None) -> Callable[[T], T]:
198
+ def has_access_decorator(func: T):
199
+ @wraps(func)
200
+ def decorated(*args, **kwargs):
201
+ dag_id_kwargs = kwargs.get("dag_id")
202
+ dag_id_args = request.args.get("dag_id")
203
+ dag_id_form = request.form.get("dag_id")
204
+ dag_id_json = request.json.get("dag_id") if request.is_json else None
205
+ all_dag_ids = [dag_id_kwargs, dag_id_args, dag_id_form, dag_id_json]
206
+ unique_dag_ids = set(dag_id for dag_id in all_dag_ids if dag_id is not None)
207
+
208
+ if len(unique_dag_ids) > 1:
209
+ log.warning(
210
+ "There are different dag_ids passed in the request: %s. Returning 403.", unique_dag_ids
211
+ )
212
+ log.warning(
213
+ "kwargs: %s, args: %s, form: %s, json: %s",
214
+ dag_id_kwargs,
215
+ dag_id_args,
216
+ dag_id_form,
217
+ dag_id_json,
218
+ )
219
+ return (
220
+ render_template(
221
+ "airflow/no_roles_permissions.html",
222
+ hostname=get_hostname() if conf.getboolean("webserver", "EXPOSE_HOSTNAME") else "",
223
+ logout_url=get_auth_manager().get_url_logout(),
224
+ ),
225
+ 403,
226
+ )
227
+ dag_id = unique_dag_ids.pop() if unique_dag_ids else None
228
+
229
+ is_authorized = get_auth_manager().is_authorized_dag(
230
+ method=method,
231
+ access_entity=access_entity,
232
+ details=None if not dag_id else DagDetails(id=dag_id),
233
+ user=get_auth_manager().get_user(),
234
+ )
235
+
236
+ return _has_access(
237
+ is_authorized=is_authorized,
238
+ func=func,
239
+ args=args,
240
+ kwargs=kwargs,
241
+ )
242
+
243
+ return cast("T", decorated)
244
+
245
+ return has_access_decorator
246
+
247
+
248
+ def has_access_dag_entities(method: ResourceMethod, access_entity: DagAccessEntity) -> Callable[[T], T]:
249
+ def has_access_decorator(func: T):
250
+ @wraps(func)
251
+ def decorated(*args, **kwargs):
252
+ items: set[XComModel | DagRun | TaskInstance] = set(args[1])
253
+ requests: Sequence[IsAuthorizedDagRequest] = [
254
+ {
255
+ "method": method,
256
+ "access_entity": access_entity,
257
+ "details": DagDetails(id=item.dag_id),
258
+ }
259
+ for item in items
260
+ if item is not None
261
+ ]
262
+ is_authorized = get_auth_manager().batch_is_authorized_dag(
263
+ requests, user=get_auth_manager().get_user()
264
+ )
265
+ return _has_access(
266
+ is_authorized=is_authorized,
267
+ func=func,
268
+ args=args,
269
+ kwargs=kwargs,
270
+ )
271
+
272
+ return cast("T", decorated)
273
+
274
+ return has_access_decorator
275
+
276
+
277
+ def has_access_asset(method: ResourceMethod) -> Callable[[T], T]:
278
+ """Check current user's permissions against required permissions for assets."""
279
+ return _has_access_no_details(
280
+ lambda: get_auth_manager().is_authorized_asset(method=method, user=get_fab_auth_manager().get_user())
281
+ )
282
+
283
+
284
+ def has_access_pool(method: ResourceMethod) -> Callable[[T], T]:
285
+ def has_access_decorator(func: T):
286
+ @wraps(func)
287
+ def decorated(*args, **kwargs):
288
+ pools: set[Pool] = set(args[1])
289
+ requests: Sequence[IsAuthorizedPoolRequest] = [
290
+ {
291
+ "method": method,
292
+ "details": PoolDetails(name=pool.pool),
293
+ }
294
+ for pool in pools
295
+ ]
296
+ is_authorized = get_auth_manager().batch_is_authorized_pool(
297
+ requests, user=get_auth_manager().get_user()
298
+ )
299
+ return _has_access(
300
+ is_authorized=is_authorized,
301
+ func=func,
302
+ args=args,
303
+ kwargs=kwargs,
304
+ )
305
+
306
+ return cast("T", decorated)
307
+
308
+ return has_access_decorator
309
+
310
+
311
+ def has_access_variable(method: ResourceMethod) -> Callable[[T], T]:
312
+ def has_access_decorator(func: T):
313
+ @wraps(func)
314
+ def decorated(*args, **kwargs):
315
+ if len(args) == 1:
316
+ # No items provided
317
+ is_authorized = get_auth_manager().is_authorized_variable(
318
+ method=method, user=get_auth_manager().get_user()
319
+ )
320
+ else:
321
+ variables: set[Variable] = set(args[1])
322
+ requests: Sequence[IsAuthorizedVariableRequest] = [
323
+ {
324
+ "method": method,
325
+ "details": VariableDetails(key=variable.key),
326
+ }
327
+ for variable in variables
328
+ ]
329
+ is_authorized = get_auth_manager().batch_is_authorized_variable(
330
+ requests, user=get_auth_manager().get_user()
331
+ )
332
+ return _has_access(
333
+ is_authorized=is_authorized,
334
+ func=func,
335
+ args=args,
336
+ kwargs=kwargs,
337
+ )
338
+
339
+ return cast("T", decorated)
340
+
341
+ return has_access_decorator
342
+
343
+
344
+ def has_access_view(access_view: AccessView = AccessView.WEBSITE) -> Callable[[T], T]:
345
+ """Check current user's permissions to access the website."""
346
+ return _has_access_no_details(
347
+ lambda: get_auth_manager().is_authorized_view(
348
+ access_view=access_view, user=get_fab_auth_manager().get_user()
349
+ )
350
+ )
@@ -0,0 +1,28 @@
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.
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from airflow.configuration import conf
22
+
23
+ WWW = Path(__file__).resolve().parent
24
+ # There is a difference with configuring Swagger in Connexion 2.x and Connexion 3.x
25
+ # Connexion 2: https://connexion.readthedocs.io/en/2.14.2/quickstart.html#the-swagger-ui-console
26
+ # Connexion 3: https://connexion.readthedocs.io/en/stable/swagger_ui.html#configuring-the-swagger-ui
27
+ SWAGGER_ENABLED = conf.getboolean("webserver", "enable_swagger_ui", fallback=True)
28
+ SWAGGER_BUNDLE = WWW.joinpath("static", "dist", "swagger-ui")
@@ -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.