apache-airflow-providers-fab 2.0.0rc1__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/LICENSE +0 -52
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +3 -4
- airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +4 -4
- airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +14 -14
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +12 -13
- airflow/providers/fab/auth_manager/api_fastapi/__init__.py +16 -0
- airflow/providers/fab/auth_manager/api_fastapi/datamodels/__init__.py +16 -0
- airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +32 -0
- airflow/providers/fab/auth_manager/api_fastapi/openapi/__init__.py +16 -0
- airflow/providers/fab/auth_manager/api_fastapi/openapi/v1-generated.yaml +153 -0
- airflow/providers/fab/auth_manager/api_fastapi/routes/__init__.py +16 -0
- airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +51 -0
- airflow/providers/fab/auth_manager/api_fastapi/services/__init__.py +16 -0
- airflow/providers/fab/auth_manager/api_fastapi/services/login.py +58 -0
- airflow/providers/fab/auth_manager/cli_commands/db_command.py +2 -4
- airflow/providers/fab/auth_manager/cli_commands/user_command.py +2 -2
- airflow/providers/fab/auth_manager/cli_commands/utils.py +17 -4
- airflow/providers/fab/auth_manager/fab_auth_manager.py +222 -119
- airflow/providers/fab/auth_manager/models/__init__.py +1 -1
- airflow/providers/fab/auth_manager/models/anonymous_user.py +1 -1
- airflow/providers/fab/auth_manager/models/db.py +22 -5
- airflow/providers/fab/auth_manager/openapi/v1.yaml +9 -0
- airflow/providers/fab/auth_manager/schemas/user_schema.py +1 -1
- airflow/providers/fab/auth_manager/security_manager/override.py +89 -561
- airflow/providers/fab/auth_manager/views/permissions.py +1 -1
- airflow/providers/fab/auth_manager/views/roles_list.py +1 -1
- airflow/providers/fab/auth_manager/views/user.py +1 -1
- airflow/providers/fab/auth_manager/views/user_edit.py +1 -1
- airflow/providers/fab/auth_manager/views/user_stats.py +1 -1
- airflow/providers/fab/get_provider_info.py +26 -15
- airflow/providers/fab/www/airflow_flask_app.py +31 -0
- airflow/providers/fab/www/api_connexion/exceptions.py +197 -0
- airflow/providers/fab/www/api_connexion/parameters.py +131 -0
- airflow/providers/fab/www/api_connexion/security.py +84 -0
- airflow/providers/fab/www/api_connexion/types.py +30 -0
- airflow/providers/fab/www/app.py +34 -9
- airflow/providers/fab/www/auth.py +350 -0
- airflow/providers/fab/www/constants.py +28 -0
- airflow/providers/fab/www/extensions/init_appbuilder.py +54 -9
- airflow/providers/fab/www/extensions/init_jinja_globals.py +5 -3
- airflow/providers/fab/www/extensions/init_security.py +19 -0
- airflow/providers/fab/www/extensions/init_session.py +64 -0
- airflow/providers/fab/www/extensions/init_views.py +111 -1
- airflow/providers/fab/www/package-lock.json +4967 -16517
- airflow/providers/fab/www/package.json +25 -104
- airflow/providers/fab/www/security/__init__.py +17 -0
- airflow/providers/fab/www/security/permissions.py +126 -0
- airflow/providers/fab/www/security_appless.py +44 -0
- airflow/providers/fab/www/security_manager.py +122 -0
- airflow/providers/fab/www/session.py +41 -0
- airflow/providers/fab/www/static/css/flash.css +57 -0
- 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 +33 -0
- airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.js +1 -0
- airflow/providers/fab/www/static/dist/f7490d556a6c42e49ba4.png +1 -0
- airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.css +18 -0
- airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.js +1 -0
- airflow/providers/fab/www/static/dist/jquery-ui.min.css +5 -0
- airflow/providers/fab/www/static/dist/jquery-ui.min.js +2 -0
- airflow/providers/fab/www/static/dist/jquery-ui.min.js.LICENSE.txt +4 -0
- airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.css +18 -0
- airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.js +1 -0
- airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.css +18 -0
- airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js +2 -0
- airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js.LICENSE.txt +18 -0
- airflow/providers/fab/www/static/dist/manifest.json +20 -0
- airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +18 -0
- airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.js +1 -0
- airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js +2 -0
- airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js.LICENSE.txt +11 -0
- airflow/providers/fab/www/static/dist/oss-licenses.json +20 -0
- airflow/providers/fab/www/templates/airflow/main.html +10 -11
- airflow/providers/fab/www/templates/airflow/traceback.html +1 -5
- airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
- airflow/providers/fab/www/templates/appbuilder/navbar.html +7 -0
- airflow/providers/fab/www/templates/appbuilder/navbar_right.html +64 -0
- airflow/providers/fab/www/utils.py +272 -0
- airflow/providers/fab/www/views.py +129 -0
- airflow/providers/fab/www/webpack.config.js +5 -40
- {apache_airflow_providers_fab-2.0.0rc1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/METADATA +24 -34
- apache_airflow_providers_fab-2.0.0rc2.dist-info/RECORD +125 -0
- {apache_airflow_providers_fab-2.0.0rc1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/WHEEL +1 -1
- airflow/providers/fab/auth_manager/decorators/auth.py +0 -127
- apache_airflow_providers_fab-2.0.0rc1.dist-info/RECORD +0 -78
- /airflow/providers/fab/{auth_manager/decorators → www/api_connexion}/__init__.py +0 -0
- {apache_airflow_providers_fab-2.0.0rc1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,64 @@
|
|
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 flask import session as builtin_flask_session
|
20
|
+
|
21
|
+
from airflow.configuration import conf
|
22
|
+
from airflow.exceptions import AirflowConfigException
|
23
|
+
from airflow.providers.fab.www.session import (
|
24
|
+
AirflowDatabaseSessionInterface,
|
25
|
+
AirflowSecureCookieSessionInterface,
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
def init_airflow_session_interface(app):
|
30
|
+
"""Set airflow session interface."""
|
31
|
+
config = app.config.copy()
|
32
|
+
selected_backend = conf.get("webserver", "SESSION_BACKEND")
|
33
|
+
# A bit of a misnomer - normally cookies expire whenever the browser is closed
|
34
|
+
# or when they hit their expiry datetime, whichever comes first. "Permanent"
|
35
|
+
# cookies only expire when they hit their expiry datetime, and can outlive
|
36
|
+
# the browser being closed.
|
37
|
+
permanent_cookie = config.get("SESSION_PERMANENT", True)
|
38
|
+
|
39
|
+
if selected_backend == "securecookie":
|
40
|
+
app.session_interface = AirflowSecureCookieSessionInterface()
|
41
|
+
if permanent_cookie:
|
42
|
+
|
43
|
+
def make_session_permanent():
|
44
|
+
builtin_flask_session.permanent = True
|
45
|
+
|
46
|
+
app.before_request(make_session_permanent)
|
47
|
+
elif selected_backend == "database":
|
48
|
+
app.session_interface = AirflowDatabaseSessionInterface(
|
49
|
+
app=app,
|
50
|
+
db=None,
|
51
|
+
permanent=permanent_cookie,
|
52
|
+
# Typically these would be configurable with Flask-Session,
|
53
|
+
# but we will set them explicitly instead as they don't make
|
54
|
+
# sense to have configurable in Airflow's use case
|
55
|
+
table="session",
|
56
|
+
key_prefix="",
|
57
|
+
use_signer=True,
|
58
|
+
)
|
59
|
+
else:
|
60
|
+
raise AirflowConfigException(
|
61
|
+
"Unrecognized session backend specified in "
|
62
|
+
f"web_server_session_backend: '{selected_backend}'. Please set "
|
63
|
+
"this to either 'database' or 'securecookie'."
|
64
|
+
)
|
@@ -17,14 +17,86 @@
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
19
|
import logging
|
20
|
+
from functools import cached_property
|
20
21
|
from typing import TYPE_CHECKING
|
21
22
|
|
23
|
+
from connexion import Resolver
|
24
|
+
from connexion.decorators.validation import RequestBodyValidator
|
25
|
+
from connexion.exceptions import BadRequestProblem, ProblemException
|
26
|
+
from flask import request
|
27
|
+
|
28
|
+
from airflow.api_fastapi.app import get_auth_manager
|
29
|
+
from airflow.providers.fab.www.api_connexion.exceptions import common_error_handler
|
30
|
+
|
22
31
|
if TYPE_CHECKING:
|
23
32
|
from flask import Flask
|
24
33
|
|
25
34
|
log = logging.getLogger(__name__)
|
26
35
|
|
27
36
|
|
37
|
+
def init_appbuilder_views(app):
|
38
|
+
"""Initialize Web UI views."""
|
39
|
+
from airflow.models import import_all_models
|
40
|
+
from airflow.providers.fab.www import views
|
41
|
+
|
42
|
+
import_all_models()
|
43
|
+
|
44
|
+
appbuilder = app.appbuilder
|
45
|
+
|
46
|
+
# Remove the session from scoped_session registry to avoid
|
47
|
+
# reusing a session with a disconnected connection
|
48
|
+
appbuilder.session.remove()
|
49
|
+
appbuilder.add_view_no_menu(views.FabIndexView())
|
50
|
+
|
51
|
+
|
52
|
+
class _LazyResolution:
|
53
|
+
"""
|
54
|
+
OpenAPI endpoint that lazily resolves the function on first use.
|
55
|
+
|
56
|
+
This is a stand-in replacement for ``connexion.Resolution`` that implements
|
57
|
+
its public attributes ``function`` and ``operation_id``, but the function
|
58
|
+
is only resolved when it is first accessed.
|
59
|
+
"""
|
60
|
+
|
61
|
+
def __init__(self, resolve_func, operation_id):
|
62
|
+
self._resolve_func = resolve_func
|
63
|
+
self.operation_id = operation_id
|
64
|
+
|
65
|
+
@cached_property
|
66
|
+
def function(self):
|
67
|
+
return self._resolve_func(self.operation_id)
|
68
|
+
|
69
|
+
|
70
|
+
class _LazyResolver(Resolver):
|
71
|
+
"""
|
72
|
+
OpenAPI endpoint resolver that loads lazily on first use.
|
73
|
+
|
74
|
+
This re-implements ``connexion.Resolver.resolve()`` to not eagerly resolve
|
75
|
+
the endpoint function (and thus avoid importing it in the process), but only
|
76
|
+
return a placeholder that will be actually resolved when the contained
|
77
|
+
function is accessed.
|
78
|
+
"""
|
79
|
+
|
80
|
+
def resolve(self, operation):
|
81
|
+
operation_id = self.resolve_operation_id(operation)
|
82
|
+
return _LazyResolution(self.resolve_function_from_operation_id, operation_id)
|
83
|
+
|
84
|
+
|
85
|
+
class _CustomErrorRequestBodyValidator(RequestBodyValidator):
|
86
|
+
"""
|
87
|
+
Custom request body validator that overrides error messages.
|
88
|
+
|
89
|
+
By default, Connextion emits a very generic *None is not of type 'object'*
|
90
|
+
error when receiving an empty request body (with the view specifying the
|
91
|
+
body as non-nullable). We overrides it to provide a more useful message.
|
92
|
+
"""
|
93
|
+
|
94
|
+
def validate_schema(self, data, url):
|
95
|
+
if not self.is_null_value_valid and data is None:
|
96
|
+
raise BadRequestProblem(detail="Request body must not be empty")
|
97
|
+
return super().validate_schema(data, url)
|
98
|
+
|
99
|
+
|
28
100
|
def init_plugins(app):
|
29
101
|
"""Integrate Flask and FAB with plugins."""
|
30
102
|
from airflow import plugins_manager
|
@@ -59,9 +131,47 @@ def init_plugins(app):
|
|
59
131
|
app.register_blueprint(blue_print["blueprint"])
|
60
132
|
|
61
133
|
|
134
|
+
base_paths: list[str] = [] # contains the list of base paths that have api endpoints
|
135
|
+
|
136
|
+
|
137
|
+
def init_api_error_handlers(app: Flask) -> None:
|
138
|
+
"""Add error handlers for 404 and 405 errors for existing API paths."""
|
139
|
+
from airflow.providers.fab.www import views
|
140
|
+
|
141
|
+
@app.errorhandler(404)
|
142
|
+
def _handle_api_not_found(ex):
|
143
|
+
if any([request.path.startswith(p) for p in base_paths]):
|
144
|
+
# 404 errors are never handled on the blueprint level
|
145
|
+
# unless raised from a view func so actual 404 errors,
|
146
|
+
# i.e. "no route for it" defined, need to be handled
|
147
|
+
# here on the application level
|
148
|
+
return common_error_handler(ex)
|
149
|
+
else:
|
150
|
+
return views.not_found(ex)
|
151
|
+
|
152
|
+
@app.errorhandler(405)
|
153
|
+
def _handle_method_not_allowed(ex):
|
154
|
+
if any([request.path.startswith(p) for p in base_paths]):
|
155
|
+
return common_error_handler(ex)
|
156
|
+
else:
|
157
|
+
return views.method_not_allowed(ex)
|
158
|
+
|
159
|
+
app.register_error_handler(ProblemException, common_error_handler)
|
160
|
+
|
161
|
+
|
62
162
|
def init_error_handlers(app: Flask):
|
63
163
|
"""Add custom errors handlers."""
|
64
|
-
from airflow.www import views
|
164
|
+
from airflow.providers.fab.www import views
|
65
165
|
|
66
166
|
app.register_error_handler(500, views.show_traceback)
|
67
167
|
app.register_error_handler(404, views.not_found)
|
168
|
+
|
169
|
+
|
170
|
+
def init_api_auth_provider(app):
|
171
|
+
"""Initialize the API offered by the auth manager."""
|
172
|
+
auth_mgr = get_auth_manager()
|
173
|
+
blueprint = auth_mgr.get_api_endpoints()
|
174
|
+
if blueprint:
|
175
|
+
base_paths.append(blueprint.url_prefix)
|
176
|
+
app.register_blueprint(blueprint)
|
177
|
+
app.extensions["csrf"].exempt(blueprint)
|