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,20 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"name": "moment",
|
4
|
+
"version": "2.30.1",
|
5
|
+
"author": "Iskren Ivov Chernev <iskren.chernev@gmail.com> (https://github.com/ichernev)",
|
6
|
+
"repository": "https://github.com/moment/moment.git",
|
7
|
+
"source": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
8
|
+
"license": "MIT",
|
9
|
+
"licenseText": "Copyright (c) JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"name": "moment-timezone",
|
13
|
+
"version": "0.5.48",
|
14
|
+
"author": "Tim Wood <washwithcare@gmail.com> (http://timwoodcreates.com/)",
|
15
|
+
"repository": "https://github.com/moment/moment-timezone.git",
|
16
|
+
"source": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
17
|
+
"license": "MIT",
|
18
|
+
"licenseText": "The MIT License (MIT)\n\nCopyright (c) JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
19
|
+
}
|
20
|
+
]
|
@@ -21,7 +21,7 @@
|
|
21
21
|
{% from 'airflow/_messages.html' import show_message %}
|
22
22
|
|
23
23
|
{% block page_title -%}
|
24
|
-
Airflow
|
24
|
+
Airflow
|
25
25
|
{% endblock %}
|
26
26
|
|
27
27
|
{% block head_css %}
|
@@ -53,12 +53,15 @@
|
|
53
53
|
{% endblock %}
|
54
54
|
|
55
55
|
{% block messages %}
|
56
|
-
{%
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
{% include 'appbuilder/flash.html' %}
|
57
|
+
{% if show_plugin_message %}
|
58
|
+
{% call show_message(category='warning', dismissible=false) %}
|
59
|
+
<p>
|
60
|
+
You have a plugin that is using a FAB view or Flask Blueprint, which was used for the Airflow 2 UI, and is now
|
61
|
+
deprecated. Please update your plugin to be compatible with the Airflow 3 UI.
|
62
|
+
</p>
|
63
|
+
{% endcall %}
|
64
|
+
{% endif %}
|
62
65
|
{% endblock %}
|
63
66
|
|
64
67
|
{% block tail_js %}
|
@@ -66,10 +69,6 @@
|
|
66
69
|
<script>
|
67
70
|
// below variables are used in main.js
|
68
71
|
// keep as var, changing to const or let breaks other code
|
69
|
-
var Airflow = {
|
70
|
-
serverTimezone: '{{ server_timezone }}',
|
71
|
-
defaultUITimezone: '{{ default_ui_timezone }}',
|
72
|
-
};
|
73
72
|
var hostName = '{{ hostname }}';
|
74
73
|
$('time[title]').tooltip();
|
75
74
|
</script>
|
@@ -45,12 +45,8 @@ Something bad has happened. For security reasons detailed information about the
|
|
45
45
|
* only after you tried it all, and have difficulty with diagnosing and fixing the problem yourself,
|
46
46
|
get the logs with errors, describe results of your investigation so far, and consider creating a
|
47
47
|
<b><a href="https://github.com/apache/airflow/issues/new/choose">bug report</a></b> including this information.
|
48
|
-
|
49
|
-
Python version: {{ python_version }}
|
50
|
-
Airflow version: {{ airflow_version }}
|
51
|
-
Node: {{ hostname }}
|
52
48
|
-------------------------------------------------------------------------------
|
53
|
-
|
49
|
+
</pre>
|
54
50
|
</div>
|
55
51
|
</div>
|
56
52
|
</body>
|
@@ -0,0 +1,34 @@
|
|
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
|
+
#}
|
19
|
+
|
20
|
+
{#-
|
21
|
+
Adapted from: https://github.com/dpgaspar/Flask-AppBuilder/blob/master/flask_appbuilder/templates/appbuilder/flash.html
|
22
|
+
-#}
|
23
|
+
<link rel="stylesheet" type="text/css" href="{{ url_for_asset('flash.css') }}">
|
24
|
+
|
25
|
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
26
|
+
{% if messages %}
|
27
|
+
{% for category, m in messages %}
|
28
|
+
{% if not (request.path == auth_manager.get_url_login() and 'access is denied' in m.lower()) %}
|
29
|
+
{# Don't show 'Access is Denied' alert if user is logged out and on the login page. #}
|
30
|
+
{{ show_message(m, category) }}
|
31
|
+
{% endif %}
|
32
|
+
{% endfor %}
|
33
|
+
{% endif %}
|
34
|
+
{% endwith %}
|
@@ -18,6 +18,7 @@
|
|
18
18
|
#}
|
19
19
|
|
20
20
|
{% set menu = appbuilder.menu %}
|
21
|
+
{% set languages = appbuilder.languages %}
|
21
22
|
|
22
23
|
<div class="navbar navbar-fixed-top" role="navigation" style="background-color: {{ navbar_color }};">
|
23
24
|
<div class="container">
|
@@ -46,7 +47,13 @@
|
|
46
47
|
</div>
|
47
48
|
<div class="navbar-collapse collapse">
|
48
49
|
<ul class="nav navbar-nav">
|
50
|
+
{%- if disable_nav_bar is not defined or not disable_nav_bar -%}
|
49
51
|
{% include 'appbuilder/navbar_menu.html' %}
|
52
|
+
{%- endif -%}
|
53
|
+
</ul>
|
54
|
+
<ul class="nav navbar-nav navbar-right">
|
55
|
+
<li class="active">
|
56
|
+
{% include 'appbuilder/navbar_right.html' %}
|
50
57
|
</ul>
|
51
58
|
</div>
|
52
59
|
</div>
|
@@ -0,0 +1,64 @@
|
|
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
|
+
#}
|
19
|
+
|
20
|
+
{% macro locale_menu(languages) %}
|
21
|
+
{% set locale = session['locale'] %}
|
22
|
+
{% if not locale %}
|
23
|
+
{% set locale = 'en' %}
|
24
|
+
{% endif %}
|
25
|
+
<li class="dropdown">
|
26
|
+
<a class="dropdown-toggle" href="javascript:void(0)">
|
27
|
+
<div class="f16"><i class="flag {{languages[locale].get('flag')}}"></i><b class="caret"></b></div>
|
28
|
+
</a>
|
29
|
+
{% if languages.keys()|length > 1 %}
|
30
|
+
<ul class="dropdown-menu">
|
31
|
+
<li class="dropdown">
|
32
|
+
{% for lang in languages %}
|
33
|
+
{% if lang != locale %}
|
34
|
+
<a href="{{appbuilder.get_url_for_locale(lang)}}">
|
35
|
+
<div class="f16"><i class="flag {{languages[lang].get('flag')}}"></i> - {{languages[lang].get('name')}}
|
36
|
+
</div></a>
|
37
|
+
{% endif %}
|
38
|
+
{% endfor %}
|
39
|
+
</li>
|
40
|
+
</ul>
|
41
|
+
{% endif %}
|
42
|
+
</li>
|
43
|
+
{% endmacro %}
|
44
|
+
|
45
|
+
{# clock and timezone menu #}
|
46
|
+
<li class="dropdown" id="timezone-dropdown">
|
47
|
+
<a class="dropdown-toggle" style="display:none" href="#">
|
48
|
+
<time id="clock" class="js-tooltip"></time>
|
49
|
+
<b class="caret"></b>
|
50
|
+
</a>
|
51
|
+
<ul class="dropdown-menu" id="timezone-menu">
|
52
|
+
<li id="timezone-utc"><a data-timezone="UTC" href="#">UTC</a></li>
|
53
|
+
<li id="timezone-server" style="display: none;"><a data-timezone="{{ server_timezone }}" href="#">{{ server_timezone }}</a></li>
|
54
|
+
<li id="timezone-local"><a href="#">Local</a></li>
|
55
|
+
<li id="timezone-manual" style="display: none"><a data-timezone="" href="#"></a></li>
|
56
|
+
<li role="separator" class="divider"></li>
|
57
|
+
<li>
|
58
|
+
<form>
|
59
|
+
<label for="timezone-other">Other</label>
|
60
|
+
<input id="timezone-other" placeholder="Select Timezone name" autocomplete="off" tabindex="-1">
|
61
|
+
</form>
|
62
|
+
</li>
|
63
|
+
</ul>
|
64
|
+
</li>
|
@@ -0,0 +1,272 @@
|
|
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 typing import TYPE_CHECKING, Any
|
21
|
+
|
22
|
+
from flask_appbuilder.models.filters import BaseFilter
|
23
|
+
from flask_appbuilder.models.sqla import filters as fab_sqlafilters
|
24
|
+
from flask_appbuilder.models.sqla.filters import get_field_setup_query, set_value_to_type
|
25
|
+
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
26
|
+
from flask_babel import lazy_gettext
|
27
|
+
from sqlalchemy import types
|
28
|
+
from sqlalchemy.ext.associationproxy import AssociationProxy
|
29
|
+
|
30
|
+
from airflow.api_fastapi.app import get_auth_manager
|
31
|
+
from airflow.providers.fab.www.security.permissions import (
|
32
|
+
ACTION_CAN_ACCESS_MENU,
|
33
|
+
ACTION_CAN_CREATE,
|
34
|
+
ACTION_CAN_DELETE,
|
35
|
+
ACTION_CAN_EDIT,
|
36
|
+
ACTION_CAN_READ,
|
37
|
+
)
|
38
|
+
from airflow.utils import timezone
|
39
|
+
|
40
|
+
if TYPE_CHECKING:
|
41
|
+
from sqlalchemy.orm.session import Session
|
42
|
+
|
43
|
+
from airflow.api_fastapi.auth.managers.base_auth_manager import ResourceMethod
|
44
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
45
|
+
|
46
|
+
# Convert methods to FAB action name
|
47
|
+
_MAP_METHOD_NAME_TO_FAB_ACTION_NAME: dict[ResourceMethod, str] = {
|
48
|
+
"POST": ACTION_CAN_CREATE,
|
49
|
+
"GET": ACTION_CAN_READ,
|
50
|
+
"PUT": ACTION_CAN_EDIT,
|
51
|
+
"DELETE": ACTION_CAN_DELETE,
|
52
|
+
"MENU": ACTION_CAN_ACCESS_MENU,
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
def get_fab_auth_manager() -> FabAuthManager:
|
57
|
+
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
58
|
+
|
59
|
+
auth_manager = get_auth_manager()
|
60
|
+
if not isinstance(auth_manager, FabAuthManager):
|
61
|
+
raise RuntimeError(
|
62
|
+
"This functionality is only available with if FabAuthManager is configured as auth manager in the environment."
|
63
|
+
)
|
64
|
+
return auth_manager
|
65
|
+
|
66
|
+
|
67
|
+
def get_fab_action_from_method_map():
|
68
|
+
"""Return the map associating a method to a FAB action."""
|
69
|
+
return _MAP_METHOD_NAME_TO_FAB_ACTION_NAME
|
70
|
+
|
71
|
+
|
72
|
+
def get_method_from_fab_action_map():
|
73
|
+
"""Return the map associating a FAB action to a method."""
|
74
|
+
return {
|
75
|
+
**{v: k for k, v in _MAP_METHOD_NAME_TO_FAB_ACTION_NAME.items()},
|
76
|
+
}
|
77
|
+
|
78
|
+
|
79
|
+
class UtcAwareFilterMixin:
|
80
|
+
"""Mixin for filter for UTC time."""
|
81
|
+
|
82
|
+
def apply(self, query, value):
|
83
|
+
"""Apply the filter."""
|
84
|
+
if isinstance(value, str) and not value.strip():
|
85
|
+
value = None
|
86
|
+
else:
|
87
|
+
value = timezone.parse(value, timezone=timezone.utc)
|
88
|
+
|
89
|
+
return super().apply(query, value)
|
90
|
+
|
91
|
+
|
92
|
+
class FilterIsNull(BaseFilter):
|
93
|
+
"""Is null filter."""
|
94
|
+
|
95
|
+
name = lazy_gettext("Is Null")
|
96
|
+
arg_name = "emp"
|
97
|
+
|
98
|
+
def apply(self, query, value):
|
99
|
+
query, field = get_field_setup_query(query, self.model, self.column_name)
|
100
|
+
value = set_value_to_type(self.datamodel, self.column_name, None)
|
101
|
+
return query.filter(field == value)
|
102
|
+
|
103
|
+
|
104
|
+
class FilterIsNotNull(BaseFilter):
|
105
|
+
"""Is not null filter."""
|
106
|
+
|
107
|
+
name = lazy_gettext("Is not Null")
|
108
|
+
arg_name = "nemp"
|
109
|
+
|
110
|
+
def apply(self, query, value):
|
111
|
+
query, field = get_field_setup_query(query, self.model, self.column_name)
|
112
|
+
value = set_value_to_type(self.datamodel, self.column_name, None)
|
113
|
+
return query.filter(field != value)
|
114
|
+
|
115
|
+
|
116
|
+
class FilterGreaterOrEqual(BaseFilter):
|
117
|
+
"""Greater than or Equal filter."""
|
118
|
+
|
119
|
+
name = lazy_gettext("Greater than or Equal")
|
120
|
+
arg_name = "gte"
|
121
|
+
|
122
|
+
def apply(self, query, value):
|
123
|
+
query, field = get_field_setup_query(query, self.model, self.column_name)
|
124
|
+
value = set_value_to_type(self.datamodel, self.column_name, value)
|
125
|
+
|
126
|
+
if value is None:
|
127
|
+
return query
|
128
|
+
|
129
|
+
return query.filter(field >= value)
|
130
|
+
|
131
|
+
|
132
|
+
class FilterSmallerOrEqual(BaseFilter):
|
133
|
+
"""Smaller than or Equal filter."""
|
134
|
+
|
135
|
+
name = lazy_gettext("Smaller than or Equal")
|
136
|
+
arg_name = "lte"
|
137
|
+
|
138
|
+
def apply(self, query, value):
|
139
|
+
query, field = get_field_setup_query(query, self.model, self.column_name)
|
140
|
+
value = set_value_to_type(self.datamodel, self.column_name, value)
|
141
|
+
|
142
|
+
if value is None:
|
143
|
+
return query
|
144
|
+
|
145
|
+
return query.filter(field <= value)
|
146
|
+
|
147
|
+
|
148
|
+
class UtcAwareFilterSmallerOrEqual(UtcAwareFilterMixin, FilterSmallerOrEqual):
|
149
|
+
"""Smaller than or Equal filter for UTC time."""
|
150
|
+
|
151
|
+
|
152
|
+
class UtcAwareFilterGreaterOrEqual(UtcAwareFilterMixin, FilterGreaterOrEqual):
|
153
|
+
"""Greater than or Equal filter for UTC time."""
|
154
|
+
|
155
|
+
|
156
|
+
class UtcAwareFilterEqual(UtcAwareFilterMixin, fab_sqlafilters.FilterEqual):
|
157
|
+
"""Equality filter for UTC time."""
|
158
|
+
|
159
|
+
|
160
|
+
class UtcAwareFilterGreater(UtcAwareFilterMixin, fab_sqlafilters.FilterGreater):
|
161
|
+
"""Greater Than filter for UTC time."""
|
162
|
+
|
163
|
+
|
164
|
+
class UtcAwareFilterSmaller(UtcAwareFilterMixin, fab_sqlafilters.FilterSmaller):
|
165
|
+
"""Smaller Than filter for UTC time."""
|
166
|
+
|
167
|
+
|
168
|
+
class UtcAwareFilterNotEqual(UtcAwareFilterMixin, fab_sqlafilters.FilterNotEqual):
|
169
|
+
"""Not Equal To filter for UTC time."""
|
170
|
+
|
171
|
+
|
172
|
+
class AirflowFilterConverter(fab_sqlafilters.SQLAFilterConverter):
|
173
|
+
"""Retrieve conversion tables for Airflow-specific filters."""
|
174
|
+
|
175
|
+
conversion_table = (
|
176
|
+
(
|
177
|
+
"is_utcdatetime",
|
178
|
+
[
|
179
|
+
UtcAwareFilterEqual,
|
180
|
+
UtcAwareFilterGreater,
|
181
|
+
UtcAwareFilterSmaller,
|
182
|
+
UtcAwareFilterNotEqual,
|
183
|
+
UtcAwareFilterSmallerOrEqual,
|
184
|
+
UtcAwareFilterGreaterOrEqual,
|
185
|
+
],
|
186
|
+
),
|
187
|
+
# FAB will try to create filters for extendedjson fields even though we
|
188
|
+
# exclude them from all UI, so we add this here to make it ignore them.
|
189
|
+
("is_extendedjson", []),
|
190
|
+
("is_json", []),
|
191
|
+
*fab_sqlafilters.SQLAFilterConverter.conversion_table,
|
192
|
+
)
|
193
|
+
|
194
|
+
def __init__(self, datamodel):
|
195
|
+
super().__init__(datamodel)
|
196
|
+
|
197
|
+
for _, filters in self.conversion_table:
|
198
|
+
if FilterIsNull not in filters:
|
199
|
+
filters.append(FilterIsNull)
|
200
|
+
if FilterIsNotNull not in filters:
|
201
|
+
filters.append(FilterIsNotNull)
|
202
|
+
|
203
|
+
|
204
|
+
class CustomSQLAInterface(SQLAInterface):
|
205
|
+
"""
|
206
|
+
FAB does not know how to handle columns with leading underscores because they are not supported by WTForm.
|
207
|
+
|
208
|
+
This hack will remove the leading '_' from the key to lookup the column names.
|
209
|
+
"""
|
210
|
+
|
211
|
+
def __init__(self, obj, session: Session | None = None):
|
212
|
+
super().__init__(obj, session=session)
|
213
|
+
|
214
|
+
def clean_column_names():
|
215
|
+
if self.list_properties:
|
216
|
+
self.list_properties = {k.lstrip("_"): v for k, v in self.list_properties.items()}
|
217
|
+
if self.list_columns:
|
218
|
+
self.list_columns = {k.lstrip("_"): v for k, v in self.list_columns.items()}
|
219
|
+
|
220
|
+
clean_column_names()
|
221
|
+
# Support for AssociationProxy in search and list columns
|
222
|
+
for obj_attr, desc in self.obj.__mapper__.all_orm_descriptors.items():
|
223
|
+
if isinstance(desc, AssociationProxy):
|
224
|
+
proxy_instance = getattr(self.obj, obj_attr)
|
225
|
+
if hasattr(proxy_instance.remote_attr.prop, "columns"):
|
226
|
+
self.list_columns[obj_attr] = proxy_instance.remote_attr.prop.columns[0]
|
227
|
+
self.list_properties[obj_attr] = proxy_instance.remote_attr.prop
|
228
|
+
|
229
|
+
def is_utcdatetime(self, col_name):
|
230
|
+
"""Check if the datetime is a UTC one."""
|
231
|
+
from airflow.utils.sqlalchemy import UtcDateTime
|
232
|
+
|
233
|
+
if col_name in self.list_columns:
|
234
|
+
obj = self.list_columns[col_name].type
|
235
|
+
return (
|
236
|
+
isinstance(obj, UtcDateTime)
|
237
|
+
or isinstance(obj, types.TypeDecorator)
|
238
|
+
and isinstance(obj.impl, UtcDateTime)
|
239
|
+
)
|
240
|
+
return False
|
241
|
+
|
242
|
+
def is_extendedjson(self, col_name):
|
243
|
+
"""Check if it is a special extended JSON type."""
|
244
|
+
from airflow.utils.sqlalchemy import ExtendedJSON
|
245
|
+
|
246
|
+
if col_name in self.list_columns:
|
247
|
+
obj = self.list_columns[col_name].type
|
248
|
+
return (
|
249
|
+
isinstance(obj, ExtendedJSON)
|
250
|
+
or isinstance(obj, types.TypeDecorator)
|
251
|
+
and isinstance(obj.impl, ExtendedJSON)
|
252
|
+
)
|
253
|
+
return False
|
254
|
+
|
255
|
+
def is_json(self, col_name):
|
256
|
+
"""Check if it is a JSON type."""
|
257
|
+
from sqlalchemy import JSON
|
258
|
+
|
259
|
+
if col_name in self.list_columns:
|
260
|
+
obj = self.list_columns[col_name].type
|
261
|
+
return (
|
262
|
+
isinstance(obj, JSON) or isinstance(obj, types.TypeDecorator) and isinstance(obj.impl, JSON)
|
263
|
+
)
|
264
|
+
return False
|
265
|
+
|
266
|
+
def get_col_default(self, col_name: str) -> Any:
|
267
|
+
if col_name not in self.list_columns:
|
268
|
+
# Handle AssociationProxy etc, or anything that isn't a "real" column
|
269
|
+
return None
|
270
|
+
return super().get_col_default(col_name)
|
271
|
+
|
272
|
+
filter_converter_class = AirflowFilterConverter
|
@@ -0,0 +1,129 @@
|
|
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 urllib.parse import unquote, urljoin, urlsplit
|
21
|
+
|
22
|
+
from flask import (
|
23
|
+
g,
|
24
|
+
make_response,
|
25
|
+
redirect,
|
26
|
+
render_template,
|
27
|
+
request,
|
28
|
+
url_for,
|
29
|
+
)
|
30
|
+
from flask_appbuilder import IndexView, expose
|
31
|
+
|
32
|
+
from airflow.api_fastapi.app import get_auth_manager
|
33
|
+
from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN
|
34
|
+
from airflow.configuration import conf
|
35
|
+
|
36
|
+
# Following the release of https://github.com/python/cpython/issues/102153 in Python 3.9.17 on
|
37
|
+
# June 6, 2023, we are adding extra sanitization of the urls passed to get_safe_url method to make it works
|
38
|
+
# the same way regardless if the user uses latest Python patchlevel versions or not. This also follows
|
39
|
+
# a recommended solution by the Python core team.
|
40
|
+
#
|
41
|
+
# From: https://github.com/python/cpython/commit/d28bafa2d3e424b6fdcfd7ae7cde8e71d7177369
|
42
|
+
#
|
43
|
+
# We recommend that users of these APIs where the values may be used anywhere
|
44
|
+
# with security implications code defensively. Do some verification within your
|
45
|
+
# code before trusting a returned component part. Does that ``scheme`` make
|
46
|
+
# sense? Is that a sensible ``path``? Is there anything strange about that
|
47
|
+
# ``hostname``? etc.
|
48
|
+
#
|
49
|
+
# C0 control and space to be stripped per WHATWG spec.
|
50
|
+
# == "".join([chr(i) for i in range(0, 0x20 + 1)])
|
51
|
+
_WHATWG_C0_CONTROL_OR_SPACE = (
|
52
|
+
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c"
|
53
|
+
"\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f "
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
class FabIndexView(IndexView):
|
58
|
+
"""
|
59
|
+
A simple view that inherits from FAB index view.
|
60
|
+
|
61
|
+
The only goal of this view is to redirect the user to the Airflow 3 UI index page if the user is
|
62
|
+
authenticated. It is impossible to redirect the user directly to the Airflow 3 UI index page before
|
63
|
+
redirecting them to this page because FAB itself defines the logic redirection and does not allow external
|
64
|
+
redirect.
|
65
|
+
"""
|
66
|
+
|
67
|
+
@expose("/")
|
68
|
+
def index(self):
|
69
|
+
if g.user is not None and g.user.is_authenticated:
|
70
|
+
token = get_auth_manager().generate_jwt(g.user)
|
71
|
+
response = make_response(redirect(f"{conf.get('api', 'base_url')}", code=302))
|
72
|
+
|
73
|
+
secure = conf.has_option("api", "ssl_cert")
|
74
|
+
response.set_cookie(COOKIE_NAME_JWT_TOKEN, token, secure=secure)
|
75
|
+
|
76
|
+
return response
|
77
|
+
else:
|
78
|
+
return redirect(conf.get("api", "base_url"), code=302)
|
79
|
+
|
80
|
+
|
81
|
+
def show_traceback(error):
|
82
|
+
"""Show Traceback for a given error."""
|
83
|
+
return render_template("airflow/traceback.html"), 500
|
84
|
+
|
85
|
+
|
86
|
+
def not_found(error):
|
87
|
+
"""Show Not Found on screen for any error in the Webserver."""
|
88
|
+
return (
|
89
|
+
render_template(
|
90
|
+
"airflow/error.html",
|
91
|
+
hostname="",
|
92
|
+
status_code=404,
|
93
|
+
error_message="Page cannot be found.",
|
94
|
+
),
|
95
|
+
404,
|
96
|
+
)
|
97
|
+
|
98
|
+
|
99
|
+
def get_safe_url(url):
|
100
|
+
"""Given a user-supplied URL, ensure it points to our web server."""
|
101
|
+
if not url:
|
102
|
+
return url_for("FabIndexView.index")
|
103
|
+
|
104
|
+
# If the url contains semicolon, redirect it to homepage to avoid
|
105
|
+
# potential XSS. (Similar to https://github.com/python/cpython/pull/24297/files (bpo-42967))
|
106
|
+
if ";" in unquote(url):
|
107
|
+
return url_for("FabIndexView.index")
|
108
|
+
|
109
|
+
url = url.lstrip(_WHATWG_C0_CONTROL_OR_SPACE)
|
110
|
+
|
111
|
+
host_url = urlsplit(request.host_url)
|
112
|
+
redirect_url = urlsplit(urljoin(request.host_url, url))
|
113
|
+
if not (redirect_url.scheme in ("http", "https") and host_url.netloc == redirect_url.netloc):
|
114
|
+
return url_for("FabIndexView.index")
|
115
|
+
|
116
|
+
# This will ensure we only redirect to the right scheme/netloc
|
117
|
+
return redirect_url.geturl()
|
118
|
+
|
119
|
+
|
120
|
+
def method_not_allowed(error):
|
121
|
+
"""Show Method Not Allowed on screen for any error in the Webserver."""
|
122
|
+
return (
|
123
|
+
render_template(
|
124
|
+
"airflow/error.html",
|
125
|
+
status_code=405,
|
126
|
+
error_message="Received an invalid request.",
|
127
|
+
),
|
128
|
+
405,
|
129
|
+
)
|