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,61 @@
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
+ import json
21
+ import os
22
+
23
+ from flask import url_for
24
+
25
+
26
+ def configure_manifest_files(app):
27
+ """
28
+ Load the manifest file and register the `url_for_asset_` template tag.
29
+
30
+ :param app:
31
+ """
32
+ manifest = {}
33
+
34
+ def parse_manifest_json():
35
+ try:
36
+ manifest_file = os.path.join(os.path.dirname(__file__), os.pardir, "static/dist/manifest.json")
37
+ with open(manifest_file) as file:
38
+ manifest.update(json.load(file))
39
+
40
+ for source, target in manifest.copy().items():
41
+ manifest[source] = os.path.join("dist", target)
42
+ except Exception:
43
+ print("Please make sure to build the frontend in static/ directory and restart the server")
44
+
45
+ def get_asset_url(filename):
46
+ if app.debug:
47
+ parse_manifest_json()
48
+ return url_for("static", filename=manifest.get(filename, filename))
49
+
50
+ parse_manifest_json()
51
+
52
+ @app.context_processor
53
+ def get_url_for_asset():
54
+ """
55
+ Template tag to return the asset URL.
56
+
57
+ WebPack renders the assets after minification and modification under the
58
+ static/dist folder. This template tag reads the asset name in
59
+ ``manifest.json`` and returns the appropriate file.
60
+ """
61
+ return {"url_for_asset": get_asset_url}
@@ -0,0 +1,61 @@
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 logging
20
+ from importlib import import_module
21
+
22
+ from airflow.configuration import conf
23
+ from airflow.exceptions import AirflowException
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ def init_xframe_protection(app):
29
+ """
30
+ Add X-Frame-Options header.
31
+
32
+ Use it to avoid click-jacking attacks, by ensuring that their content is not embedded into other sites.
33
+
34
+ See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
35
+ """
36
+ x_frame_enabled = conf.getboolean("webserver", "X_FRAME_ENABLED", fallback=True)
37
+ if x_frame_enabled:
38
+ return
39
+
40
+ def apply_caching(response):
41
+ response.headers["X-Frame-Options"] = "DENY"
42
+ return response
43
+
44
+ app.after_request(apply_caching)
45
+
46
+
47
+ def init_api_auth(app):
48
+ """Load authentication backends."""
49
+ auth_backends = conf.get(
50
+ "fab", "auth_backends", fallback="airflow.providers.fab.auth_manager.api.auth.backend.session"
51
+ )
52
+
53
+ app.api_auth = []
54
+ try:
55
+ for backend in auth_backends.split(","):
56
+ auth = import_module(backend.strip())
57
+ auth.init_app(app)
58
+ app.api_auth.append(auth)
59
+ except ImportError as err:
60
+ log.critical("Cannot import %s for API authentication due to: %s", backend, err)
61
+ raise AirflowException(err)
@@ -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("fab", "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"[fab] session_backend: '{selected_backend}'. Please set "
63
+ "this to either 'database' or 'securecookie'."
64
+ )
@@ -0,0 +1,177 @@
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 logging
20
+ from functools import cached_property
21
+ from typing import TYPE_CHECKING
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
+
31
+ if TYPE_CHECKING:
32
+ from flask import Flask
33
+
34
+ log = logging.getLogger(__name__)
35
+
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
+
100
+ def init_plugins(app):
101
+ """Integrate Flask and FAB with plugins."""
102
+ from airflow import plugins_manager
103
+
104
+ plugins_manager.initialize_flask_plugins()
105
+
106
+ appbuilder = app.appbuilder
107
+
108
+ for view in plugins_manager.flask_appbuilder_views:
109
+ name = view.get("name")
110
+ if name:
111
+ filtered_view_kwargs = {k: v for k, v in view.items() if k not in ["view"]}
112
+ log.debug("Adding view %s with menu", name)
113
+ baseview = view.get("view")
114
+ if baseview:
115
+ appbuilder.add_view(baseview, **filtered_view_kwargs)
116
+ else:
117
+ log.error("'view' key is missing for the named view: %s", name)
118
+ else:
119
+ # if 'name' key is missing, intent is to add view without menu
120
+ log.debug("Adding view %s without menu", str(type(view["view"])))
121
+ appbuilder.add_view_no_menu(view["view"])
122
+
123
+ for menu_link in sorted(
124
+ plugins_manager.flask_appbuilder_menu_links, key=lambda x: (x.get("category", ""), x["name"])
125
+ ):
126
+ log.debug("Adding menu link %s to %s", menu_link["name"], menu_link["href"])
127
+ appbuilder.add_link(**menu_link)
128
+
129
+ for blue_print in plugins_manager.flask_blueprints:
130
+ log.debug("Adding blueprint %s:%s", blue_print["name"], blue_print["blueprint"].import_name)
131
+ app.register_blueprint(blue_print["blueprint"])
132
+
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
+
162
+ def init_error_handlers(app: Flask):
163
+ """Add custom errors handlers."""
164
+ from airflow.providers.fab.www import views
165
+
166
+ app.register_error_handler(500, views.show_traceback)
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)