apache-airflow-providers-fab 1.5.3rc1__py3-none-any.whl → 2.0.0b1__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/__init__.py +3 -3
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +3 -3
- 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 -13
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +12 -12
- 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 +152 -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 +10 -9
- airflow/providers/fab/auth_manager/fab_auth_manager.py +231 -126
- 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/schemas/user_schema.py +1 -1
- airflow/providers/fab/auth_manager/security_manager/override.py +71 -632
- 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 +22 -16
- airflow/providers/fab/www/airflow_flask_app.py +31 -0
- airflow/providers/fab/www/api_connexion/__init__.py +17 -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 +112 -0
- airflow/providers/fab/www/auth.py +350 -0
- airflow/providers/fab/www/constants.py +28 -0
- airflow/providers/fab/www/extensions/__init__.py +16 -0
- airflow/providers/fab/www/extensions/init_appbuilder.py +602 -0
- airflow/providers/fab/www/extensions/init_jinja_globals.py +82 -0
- airflow/providers/fab/www/extensions/init_manifest_files.py +61 -0
- airflow/providers/fab/www/extensions/init_security.py +61 -0
- airflow/providers/fab/www/extensions/init_session.py +64 -0
- airflow/providers/fab/www/extensions/init_views.py +177 -0
- airflow/providers/fab/www/package-lock.json +10127 -0
- airflow/providers/fab/www/package.json +81 -0
- 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/bootstrap-theme.css +6215 -0
- airflow/providers/fab/www/static/css/flash.css +57 -0
- airflow/providers/fab/www/static/css/loading-dots.css +60 -0
- airflow/providers/fab/www/static/css/main.css +676 -0
- airflow/providers/fab/www/static/css/material-icons.css +84 -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/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.ec1d38d994d72bb083cd.css +18 -0
- airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.js +2 -0
- airflow/providers/fab/www/static/dist/main.ec1d38d994d72bb083cd.js.LICENSE.txt +18 -0
- airflow/providers/fab/www/static/dist/manifest.json +17 -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.4d28b37c229bdfc54575.js +2 -0
- airflow/providers/fab/www/static/dist/moment.4d28b37c229bdfc54575.js.LICENSE.txt +11 -0
- airflow/providers/fab/www/static/dist/oss-licenses.json +29 -0
- airflow/providers/fab/www/static/js/datetime_utils.js +134 -0
- airflow/providers/fab/www/static/js/main.js +324 -0
- airflow/providers/fab/www/static/sort_asc.png +0 -0
- airflow/providers/fab/www/static/sort_both.png +0 -0
- airflow/providers/fab/www/static/sort_desc.png +0 -0
- airflow/providers/fab/www/templates/airflow/_messages.html +30 -0
- airflow/providers/fab/www/templates/airflow/error.html +35 -0
- airflow/providers/fab/www/templates/airflow/main.html +78 -0
- airflow/providers/fab/www/templates/airflow/traceback.html +53 -0
- airflow/providers/fab/www/templates/appbuilder/flash.html +34 -0
- airflow/providers/fab/www/templates/appbuilder/index.html +20 -0
- airflow/providers/fab/www/templates/appbuilder/navbar.html +60 -0
- airflow/providers/fab/www/templates/appbuilder/navbar_menu.html +60 -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 +213 -0
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/METADATA +18 -36
- apache_airflow_providers_fab-2.0.0b1.dist-info/RECORD +122 -0
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/WHEEL +1 -1
- airflow/providers/fab/auth_manager/decorators/auth.py +0 -126
- apache_airflow_providers_fab-1.5.3rc1.dist-info/RECORD +0 -51
- /airflow/providers/fab/{auth_manager/decorators → www}/__init__.py +0 -0
- {apache_airflow_providers_fab-1.5.3rc1.dist-info → apache_airflow_providers_fab-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
{
|
2
|
+
"name": "airflow-www",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
|
5
|
+
"scripts": {
|
6
|
+
"test": "jest",
|
7
|
+
"dev": "NODE_ENV=development webpack --watch --progress --devtool eval-cheap-source-map --mode development",
|
8
|
+
"prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --mode production --progress",
|
9
|
+
"build": "NODE_ENV=production webpack --progress --mode production",
|
10
|
+
"lint": "eslint --ignore-path=.eslintignore --max-warnings=0 --ext .js .",
|
11
|
+
"lint:fix": "eslint --fix --ignore-path=.eslintignore --ext .js .",
|
12
|
+
"format": "yarn prettier --write ."
|
13
|
+
},
|
14
|
+
"author": "Apache",
|
15
|
+
"license": "Apache-2.0",
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "git+https://github.com/apache/airflow.git"
|
19
|
+
},
|
20
|
+
"homepage": "https://airflow.apache.org/",
|
21
|
+
"keywords": [
|
22
|
+
"big",
|
23
|
+
"data",
|
24
|
+
"workflow",
|
25
|
+
"airflow",
|
26
|
+
"database",
|
27
|
+
"flask"
|
28
|
+
],
|
29
|
+
"browserslist": {
|
30
|
+
"production": [
|
31
|
+
">0.2%",
|
32
|
+
"not dead",
|
33
|
+
"not op_mini all"
|
34
|
+
],
|
35
|
+
"development": [
|
36
|
+
"last 1 chrome version",
|
37
|
+
"last 1 firefox version",
|
38
|
+
"last 1 safari version"
|
39
|
+
]
|
40
|
+
},
|
41
|
+
"devDependencies": {
|
42
|
+
"@babel/core": "^7.24.7",
|
43
|
+
"@babel/eslint-parser": "^7.24.7",
|
44
|
+
"@babel/plugin-transform-runtime": "^7.24.7",
|
45
|
+
"@babel/preset-env": "^7.24.7",
|
46
|
+
"babel-jest": "^27.3.1",
|
47
|
+
"babel-loader": "^9.1.0",
|
48
|
+
"clean-webpack-plugin": "^3.0.0",
|
49
|
+
"copy-webpack-plugin": "^6.0.3",
|
50
|
+
"css-loader": "5.2.7",
|
51
|
+
"css-minimizer-webpack-plugin": "^4.0.0",
|
52
|
+
"eslint": "^8.6.0",
|
53
|
+
"eslint-config-prettier": "^8.6.0",
|
54
|
+
"eslint-plugin-html": "^6.0.2",
|
55
|
+
"eslint-plugin-import": "^2.27.5",
|
56
|
+
"eslint-plugin-node": "^11.1.0",
|
57
|
+
"eslint-plugin-standard": "^4.0.1",
|
58
|
+
"file-loader": "^6.0.0",
|
59
|
+
"imports-loader": "^1.1.0",
|
60
|
+
"mini-css-extract-plugin": "^1.6.2",
|
61
|
+
"moment": "^2.29.4",
|
62
|
+
"moment-locales-webpack-plugin": "^1.2.0",
|
63
|
+
"prettier": "^2.8.4",
|
64
|
+
"style-loader": "^1.2.1",
|
65
|
+
"stylelint": "^15.10.1",
|
66
|
+
"terser-webpack-plugin": "<6.0.0",
|
67
|
+
"url-loader": "4.1.0",
|
68
|
+
"web-worker": "^1.2.0",
|
69
|
+
"webpack": "^5.94.0",
|
70
|
+
"webpack-cli": "^4.0.0",
|
71
|
+
"webpack-license-plugin": "^4.2.1",
|
72
|
+
"webpack-manifest-plugin": "^4.0.0"
|
73
|
+
},
|
74
|
+
"dependencies": {
|
75
|
+
"jquery-ui": "^1.14.1",
|
76
|
+
"moment-timezone": "^0.5.43"
|
77
|
+
},
|
78
|
+
"resolutions": {
|
79
|
+
"moment-timezone": ">=0.5.35"
|
80
|
+
}
|
81
|
+
}
|
@@ -0,0 +1,17 @@
|
|
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.
|
@@ -0,0 +1,126 @@
|
|
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 typing import TypedDict
|
20
|
+
|
21
|
+
# Resource Constants
|
22
|
+
RESOURCE_ACTION = "Permissions"
|
23
|
+
RESOURCE_ADMIN_MENU = "Admin"
|
24
|
+
RESOURCE_AUDIT_LOG = "Audit Logs"
|
25
|
+
RESOURCE_BACKFILL = "Backfills"
|
26
|
+
RESOURCE_BROWSE_MENU = "Browse"
|
27
|
+
RESOURCE_CONFIG = "Configurations"
|
28
|
+
RESOURCE_CONNECTION = "Connections"
|
29
|
+
RESOURCE_DAG = "DAGs"
|
30
|
+
RESOURCE_DAG_CODE = "DAG Code"
|
31
|
+
RESOURCE_DAG_DEPENDENCIES = "DAG Dependencies"
|
32
|
+
RESOURCE_DAG_PREFIX = "DAG:"
|
33
|
+
RESOURCE_DAG_RUN = "DAG Runs"
|
34
|
+
RESOURCE_DAG_RUN_PREFIX = "DAG Run:"
|
35
|
+
RESOURCE_DAG_VERSION = "DAG Versions"
|
36
|
+
RESOURCE_DAG_WARNING = "DAG Warnings"
|
37
|
+
RESOURCE_CLUSTER_ACTIVITY = "Cluster Activity"
|
38
|
+
RESOURCE_ASSET = "Assets"
|
39
|
+
RESOURCE_ASSET_ALIAS = "Asset Aliases"
|
40
|
+
RESOURCE_DOCS = "Documentation"
|
41
|
+
RESOURCE_DOCS_MENU = "Docs"
|
42
|
+
RESOURCE_IMPORT_ERROR = "ImportError"
|
43
|
+
RESOURCE_JOB = "Jobs"
|
44
|
+
RESOURCE_MY_PASSWORD = "My Password"
|
45
|
+
RESOURCE_MY_PROFILE = "My Profile"
|
46
|
+
RESOURCE_PASSWORD = "Passwords"
|
47
|
+
RESOURCE_PERMISSION = "Permission Views" # Refers to a Perm <-> View mapping, not an MVC View.
|
48
|
+
RESOURCE_PLUGIN = "Plugins"
|
49
|
+
RESOURCE_POOL = "Pools"
|
50
|
+
RESOURCE_PROVIDER = "Providers"
|
51
|
+
RESOURCE_RESOURCE = "View Menus"
|
52
|
+
RESOURCE_ROLE = "Roles"
|
53
|
+
RESOURCE_SLA_MISS = "SLA Misses"
|
54
|
+
RESOURCE_TASK_INSTANCE = "Task Instances"
|
55
|
+
RESOURCE_TASK_LOG = "Task Logs"
|
56
|
+
RESOURCE_TASK_RESCHEDULE = "Task Reschedules"
|
57
|
+
RESOURCE_TRIGGER = "Triggers"
|
58
|
+
RESOURCE_USER = "Users"
|
59
|
+
RESOURCE_USER_STATS_CHART = "User Stats Chart"
|
60
|
+
RESOURCE_VARIABLE = "Variables"
|
61
|
+
RESOURCE_WEBSITE = "Website"
|
62
|
+
RESOURCE_XCOM = "XComs"
|
63
|
+
|
64
|
+
# Action Constants
|
65
|
+
ACTION_CAN_CREATE = "can_create"
|
66
|
+
ACTION_CAN_READ = "can_read"
|
67
|
+
ACTION_CAN_EDIT = "can_edit"
|
68
|
+
ACTION_CAN_DELETE = "can_delete"
|
69
|
+
ACTION_CAN_ACCESS_MENU = "menu_access"
|
70
|
+
DEPRECATED_ACTION_CAN_DAG_READ = "can_dag_read"
|
71
|
+
DEPRECATED_ACTION_CAN_DAG_EDIT = "can_dag_edit"
|
72
|
+
|
73
|
+
|
74
|
+
class ResourceDetails(TypedDict):
|
75
|
+
"""Details of a resource (actions and prefix)."""
|
76
|
+
|
77
|
+
actions: set[str]
|
78
|
+
prefix: str
|
79
|
+
|
80
|
+
|
81
|
+
# Keeping DAG_ACTIONS to keep the compatibility with outdated versions of FAB provider
|
82
|
+
DAG_ACTIONS = {ACTION_CAN_READ, ACTION_CAN_EDIT, ACTION_CAN_DELETE}
|
83
|
+
|
84
|
+
RESOURCE_DETAILS_MAP = {
|
85
|
+
RESOURCE_DAG: ResourceDetails(
|
86
|
+
actions={ACTION_CAN_READ, ACTION_CAN_EDIT, ACTION_CAN_DELETE}, prefix=RESOURCE_DAG_PREFIX
|
87
|
+
),
|
88
|
+
RESOURCE_DAG_RUN: ResourceDetails(
|
89
|
+
actions={ACTION_CAN_READ, ACTION_CAN_CREATE, ACTION_CAN_DELETE, ACTION_CAN_ACCESS_MENU},
|
90
|
+
prefix=RESOURCE_DAG_RUN_PREFIX,
|
91
|
+
),
|
92
|
+
}
|
93
|
+
PREFIX_LIST = [details["prefix"] for details in RESOURCE_DETAILS_MAP.values()]
|
94
|
+
PREFIX_RESOURCES_MAP = {details["prefix"]: resource for resource, details in RESOURCE_DETAILS_MAP.items()}
|
95
|
+
|
96
|
+
|
97
|
+
def resource_name(root_dag_id: str, resource: str) -> str:
|
98
|
+
"""
|
99
|
+
Return the resource name for a DAG id.
|
100
|
+
|
101
|
+
Note that since a sub-DAG should follow the permission of its
|
102
|
+
parent DAG, you should pass ``DagModel.root_dag_id`` to this function,
|
103
|
+
for a subdag. A normal dag should pass the ``DagModel.dag_id``.
|
104
|
+
"""
|
105
|
+
if root_dag_id in RESOURCE_DETAILS_MAP.keys():
|
106
|
+
return root_dag_id
|
107
|
+
if root_dag_id.startswith(tuple(PREFIX_RESOURCES_MAP.keys())):
|
108
|
+
return root_dag_id
|
109
|
+
return f"{RESOURCE_DETAILS_MAP[resource]['prefix']}{root_dag_id}"
|
110
|
+
|
111
|
+
|
112
|
+
def resource_name_for_dag(root_dag_id: str) -> str:
|
113
|
+
"""
|
114
|
+
Return the resource name for a DAG id.
|
115
|
+
|
116
|
+
Note that since a sub-DAG should follow the permission of its
|
117
|
+
parent DAG, you should pass ``DagModel.root_dag_id`` to this function,
|
118
|
+
for a subdag. A normal dag should pass the ``DagModel.dag_id``.
|
119
|
+
|
120
|
+
Note: This function is kept for backwards compatibility.
|
121
|
+
"""
|
122
|
+
if root_dag_id == RESOURCE_DAG:
|
123
|
+
return root_dag_id
|
124
|
+
if root_dag_id.startswith(RESOURCE_DAG_PREFIX):
|
125
|
+
return root_dag_id
|
126
|
+
return f"{RESOURCE_DAG_PREFIX}{root_dag_id}"
|
@@ -0,0 +1,44 @@
|
|
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 typing import TYPE_CHECKING
|
20
|
+
|
21
|
+
from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from sqlalchemy.orm import Session
|
25
|
+
|
26
|
+
|
27
|
+
class FakeAppBuilder:
|
28
|
+
"""
|
29
|
+
Stand-in class to replace a Flask App Builder.
|
30
|
+
|
31
|
+
The only purpose is to provide the ``self.appbuilder.get_session`` interface
|
32
|
+
for ``ApplessAirflowSecurityManager`` so it can be used without a real Flask
|
33
|
+
app, which is slow to create.
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self, session: Session | None = None) -> None:
|
37
|
+
self.get_session = session
|
38
|
+
|
39
|
+
|
40
|
+
class ApplessAirflowSecurityManager(FabAirflowSecurityManagerOverride):
|
41
|
+
"""Security Manager that doesn't need the whole flask app."""
|
42
|
+
|
43
|
+
def __init__(self, session: Session | None = None):
|
44
|
+
self.appbuilder = FakeAppBuilder(session)
|
@@ -0,0 +1,122 @@
|
|
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 typing import Callable
|
20
|
+
|
21
|
+
from flask import g
|
22
|
+
from flask_limiter import Limiter
|
23
|
+
from flask_limiter.util import get_remote_address
|
24
|
+
|
25
|
+
from airflow.api_fastapi.app import get_auth_manager
|
26
|
+
from airflow.providers.fab.www.utils import CustomSQLAInterface, get_method_from_fab_action_map
|
27
|
+
from airflow.utils.log.logging_mixin import LoggingMixin
|
28
|
+
|
29
|
+
EXISTING_ROLES = {
|
30
|
+
"Admin",
|
31
|
+
"Viewer",
|
32
|
+
"User",
|
33
|
+
"Op",
|
34
|
+
"Public",
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
class AirflowSecurityManagerV2(LoggingMixin):
|
39
|
+
"""
|
40
|
+
Minimal security manager needed to run a Flask application.
|
41
|
+
|
42
|
+
This one is used to run the Flask application needed to run Airflow 2 plugins unless Fab auth manager
|
43
|
+
is configured in the environment. In that case, ``FabAirflowSecurityManagerOverride`` is used.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, appbuilder) -> None:
|
47
|
+
super().__init__()
|
48
|
+
self.appbuilder = appbuilder
|
49
|
+
|
50
|
+
# Setup Flask-Limiter
|
51
|
+
self.limiter = self.create_limiter()
|
52
|
+
|
53
|
+
# Go and fix up the SQLAInterface used from the stock one to our subclass.
|
54
|
+
# This is needed to support the "hack" where we had to edit
|
55
|
+
# FieldConverter.conversion_table in place in utils
|
56
|
+
for attr in dir(self):
|
57
|
+
if attr.endswith("view"):
|
58
|
+
view = getattr(self, attr, None)
|
59
|
+
if view and getattr(view, "datamodel", None):
|
60
|
+
view.datamodel = CustomSQLAInterface(view.datamodel.obj)
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def before_request():
|
64
|
+
"""Run hook before request."""
|
65
|
+
if hasattr(get_auth_manager(), "get_user"):
|
66
|
+
g.user = get_auth_manager().get_user()
|
67
|
+
|
68
|
+
def create_limiter(self) -> Limiter:
|
69
|
+
app = self.appbuilder.get_app
|
70
|
+
limiter = Limiter(key_func=app.config.get("RATELIMIT_KEY_FUNC", get_remote_address))
|
71
|
+
limiter.init_app(app)
|
72
|
+
return limiter
|
73
|
+
|
74
|
+
def has_access(
|
75
|
+
self, action_name: str, resource_name: str, user=None, resource_pk: str | None = None
|
76
|
+
) -> bool:
|
77
|
+
"""
|
78
|
+
Verify whether a given user could perform a certain action on the given resource.
|
79
|
+
|
80
|
+
Example actions might include can_read, can_write, can_delete, etc.
|
81
|
+
|
82
|
+
This function is called by FAB when accessing a view. See
|
83
|
+
https://github.com/dpgaspar/Flask-AppBuilder/blob/c6fecdc551629e15467fde5d06b4437379d90592/flask_appbuilder/security/decorators.py#L134
|
84
|
+
|
85
|
+
:param action_name: action_name on resource (e.g can_read, can_edit).
|
86
|
+
:param resource_name: name of view-menu or resource.
|
87
|
+
:param user: user
|
88
|
+
:param resource_pk: the resource primary key (e.g. the connection ID)
|
89
|
+
:return: Whether user could perform certain action on the resource.
|
90
|
+
"""
|
91
|
+
if not user:
|
92
|
+
user = g.user
|
93
|
+
|
94
|
+
is_authorized_method = self._get_auth_manager_is_authorized_method(resource_name)
|
95
|
+
return is_authorized_method(action_name, resource_pk, user)
|
96
|
+
|
97
|
+
def add_limit_view(self, baseview):
|
98
|
+
if not baseview.limits:
|
99
|
+
return
|
100
|
+
|
101
|
+
for limit in baseview.limits:
|
102
|
+
self.limiter.limit(
|
103
|
+
limit_value=limit.limit_value,
|
104
|
+
key_func=limit.key_func,
|
105
|
+
per_method=limit.per_method,
|
106
|
+
methods=limit.methods,
|
107
|
+
error_message=limit.error_message,
|
108
|
+
exempt_when=limit.exempt_when,
|
109
|
+
override_defaults=limit.override_defaults,
|
110
|
+
deduct_when=limit.deduct_when,
|
111
|
+
on_breach=limit.on_breach,
|
112
|
+
cost=limit.cost,
|
113
|
+
)(baseview.blueprint)
|
114
|
+
|
115
|
+
def _get_auth_manager_is_authorized_method(self, fab_resource_name: str) -> Callable:
|
116
|
+
# The user is trying to access a page specific to the auth manager
|
117
|
+
# (e.g. the user list view in FabAuthManager) or a page defined in a plugin
|
118
|
+
return lambda action, resource_pk, user: get_auth_manager().is_authorized_custom_view(
|
119
|
+
method=get_method_from_fab_action_map().get(action, action),
|
120
|
+
resource_name=fab_resource_name,
|
121
|
+
user=user,
|
122
|
+
)
|
@@ -0,0 +1,41 @@
|
|
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 request
|
20
|
+
from flask.sessions import SecureCookieSessionInterface
|
21
|
+
from flask_session.sessions import SqlAlchemySessionInterface
|
22
|
+
|
23
|
+
|
24
|
+
class SessionExemptMixin:
|
25
|
+
"""Exempt certain blueprints/paths from autogenerated sessions."""
|
26
|
+
|
27
|
+
def save_session(self, *args, **kwargs):
|
28
|
+
"""Prevent creating session from REST API and health requests."""
|
29
|
+
if request.blueprint == "/api/v1":
|
30
|
+
return None
|
31
|
+
if request.path == "/health":
|
32
|
+
return None
|
33
|
+
return super().save_session(*args, **kwargs)
|
34
|
+
|
35
|
+
|
36
|
+
class AirflowDatabaseSessionInterface(SessionExemptMixin, SqlAlchemySessionInterface):
|
37
|
+
"""Session interface that exempts some routes and stores session data in the database."""
|
38
|
+
|
39
|
+
|
40
|
+
class AirflowSecureCookieSessionInterface(SessionExemptMixin, SecureCookieSessionInterface):
|
41
|
+
"""Session interface that exempts some routes and stores session data in a signed cookie."""
|