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.
Files changed (88) hide show
  1. airflow/providers/fab/LICENSE +0 -52
  2. airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +3 -4
  3. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +4 -4
  4. airflow/providers/fab/auth_manager/api/auth/backend/session.py +1 -1
  5. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +14 -14
  6. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +12 -13
  7. airflow/providers/fab/auth_manager/api_fastapi/__init__.py +16 -0
  8. airflow/providers/fab/auth_manager/api_fastapi/datamodels/__init__.py +16 -0
  9. airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +32 -0
  10. airflow/providers/fab/auth_manager/api_fastapi/openapi/__init__.py +16 -0
  11. airflow/providers/fab/auth_manager/api_fastapi/openapi/v1-generated.yaml +153 -0
  12. airflow/providers/fab/auth_manager/api_fastapi/routes/__init__.py +16 -0
  13. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +51 -0
  14. airflow/providers/fab/auth_manager/api_fastapi/services/__init__.py +16 -0
  15. airflow/providers/fab/auth_manager/api_fastapi/services/login.py +58 -0
  16. airflow/providers/fab/auth_manager/cli_commands/db_command.py +2 -4
  17. airflow/providers/fab/auth_manager/cli_commands/user_command.py +2 -2
  18. airflow/providers/fab/auth_manager/cli_commands/utils.py +17 -4
  19. airflow/providers/fab/auth_manager/fab_auth_manager.py +222 -119
  20. airflow/providers/fab/auth_manager/models/__init__.py +1 -1
  21. airflow/providers/fab/auth_manager/models/anonymous_user.py +1 -1
  22. airflow/providers/fab/auth_manager/models/db.py +22 -5
  23. airflow/providers/fab/auth_manager/openapi/v1.yaml +9 -0
  24. airflow/providers/fab/auth_manager/schemas/user_schema.py +1 -1
  25. airflow/providers/fab/auth_manager/security_manager/override.py +89 -561
  26. airflow/providers/fab/auth_manager/views/permissions.py +1 -1
  27. airflow/providers/fab/auth_manager/views/roles_list.py +1 -1
  28. airflow/providers/fab/auth_manager/views/user.py +1 -1
  29. airflow/providers/fab/auth_manager/views/user_edit.py +1 -1
  30. airflow/providers/fab/auth_manager/views/user_stats.py +1 -1
  31. airflow/providers/fab/get_provider_info.py +26 -15
  32. airflow/providers/fab/www/airflow_flask_app.py +31 -0
  33. airflow/providers/fab/www/api_connexion/exceptions.py +197 -0
  34. airflow/providers/fab/www/api_connexion/parameters.py +131 -0
  35. airflow/providers/fab/www/api_connexion/security.py +84 -0
  36. airflow/providers/fab/www/api_connexion/types.py +30 -0
  37. airflow/providers/fab/www/app.py +34 -9
  38. airflow/providers/fab/www/auth.py +350 -0
  39. airflow/providers/fab/www/constants.py +28 -0
  40. airflow/providers/fab/www/extensions/init_appbuilder.py +54 -9
  41. airflow/providers/fab/www/extensions/init_jinja_globals.py +5 -3
  42. airflow/providers/fab/www/extensions/init_security.py +19 -0
  43. airflow/providers/fab/www/extensions/init_session.py +64 -0
  44. airflow/providers/fab/www/extensions/init_views.py +111 -1
  45. airflow/providers/fab/www/package-lock.json +4967 -16517
  46. airflow/providers/fab/www/package.json +25 -104
  47. airflow/providers/fab/www/security/__init__.py +17 -0
  48. airflow/providers/fab/www/security/permissions.py +126 -0
  49. airflow/providers/fab/www/security_appless.py +44 -0
  50. airflow/providers/fab/www/security_manager.py +122 -0
  51. airflow/providers/fab/www/session.py +41 -0
  52. airflow/providers/fab/www/static/css/flash.css +57 -0
  53. airflow/providers/fab/www/static/dist/48f0ea180c40270a5b05.png +1 -0
  54. airflow/providers/fab/www/static/dist/649c0b07771e68fafdeb.png +1 -0
  55. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.css +33 -0
  56. airflow/providers/fab/www/static/dist/airflowDefaultTheme.feec4a4075c2f3d6ae01.js +1 -0
  57. airflow/providers/fab/www/static/dist/f7490d556a6c42e49ba4.png +1 -0
  58. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.css +18 -0
  59. airflow/providers/fab/www/static/dist/flash.137b30cff85b5588e661.js +1 -0
  60. airflow/providers/fab/www/static/dist/jquery-ui.min.css +5 -0
  61. airflow/providers/fab/www/static/dist/jquery-ui.min.js +2 -0
  62. airflow/providers/fab/www/static/dist/jquery-ui.min.js.LICENSE.txt +4 -0
  63. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.css +18 -0
  64. airflow/providers/fab/www/static/dist/loadingDots.48ab7d5b04e66f2686b0.js +1 -0
  65. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.css +18 -0
  66. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js +2 -0
  67. airflow/providers/fab/www/static/dist/main.edb2d40dfbbc537916e3.js.LICENSE.txt +18 -0
  68. airflow/providers/fab/www/static/dist/manifest.json +20 -0
  69. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.css +18 -0
  70. airflow/providers/fab/www/static/dist/materialIcons.57390fa60d8f61175334.js +1 -0
  71. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js +2 -0
  72. airflow/providers/fab/www/static/dist/moment.624b1f00ba723d39ce06.js.LICENSE.txt +11 -0
  73. airflow/providers/fab/www/static/dist/oss-licenses.json +20 -0
  74. airflow/providers/fab/www/templates/airflow/main.html +10 -11
  75. airflow/providers/fab/www/templates/airflow/traceback.html +1 -5
  76. airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
  77. airflow/providers/fab/www/templates/appbuilder/navbar.html +7 -0
  78. airflow/providers/fab/www/templates/appbuilder/navbar_right.html +64 -0
  79. airflow/providers/fab/www/utils.py +272 -0
  80. airflow/providers/fab/www/views.py +129 -0
  81. airflow/providers/fab/www/webpack.config.js +5 -40
  82. {apache_airflow_providers_fab-2.0.0rc1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/METADATA +24 -34
  83. apache_airflow_providers_fab-2.0.0rc2.dist-info/RECORD +125 -0
  84. {apache_airflow_providers_fab-2.0.0rc1.dist-info → apache_airflow_providers_fab-2.0.0rc2.dist-info}/WHEEL +1 -1
  85. airflow/providers/fab/auth_manager/decorators/auth.py +0 -127
  86. apache_airflow_providers_fab-2.0.0rc1.dist-info/RECORD +0 -78
  87. /airflow/providers/fab/{auth_manager/decorators → www/api_connexion}/__init__.py +0 -0
  88. {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)