apache-airflow-providers-fab 1.0.0rc1__tar.gz → 1.0.2__tar.gz
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.
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/PKG-INFO +11 -10
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/README.rst +5 -5
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/__init__.py +1 -1
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +1 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +7 -8
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +5 -5
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/definition.py +21 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/role_command.py +50 -17
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py +1 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/user_command.py +28 -10
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/decorators/auth.py +7 -4
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/fab_auth_manager.py +30 -31
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/models/__init__.py +1 -1
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/security_manager/override.py +151 -61
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/get_provider_info.py +32 -3
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/pyproject.toml +6 -5
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/LICENSE +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/auth/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/auth/backend/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api_endpoints/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/cli_commands/utils.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/decorators/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/models/anonymous_user.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/openapi/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/openapi/v1.yaml +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/security_manager/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/security_manager/constants.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/__init__.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/permissions.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/roles_list.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/user.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/user_edit.py +0 -0
- {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/user_stats.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apache-airflow-providers-fab
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.2
|
4
4
|
Summary: Provider package apache-airflow-providers-fab for Apache Airflow
|
5
5
|
Keywords: airflow-provider,fab,airflow,integration
|
6
6
|
Author-email: Apache Software Foundation <dev@airflow.apache.org>
|
@@ -19,15 +19,16 @@ Classifier: Programming Language :: Python :: 3.8
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.9
|
20
20
|
Classifier: Programming Language :: Python :: 3.10
|
21
21
|
Classifier: Programming Language :: Python :: 3.11
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
22
23
|
Classifier: Topic :: System :: Monitoring
|
23
|
-
Requires-Dist: apache-airflow>=2.9.0
|
24
|
-
Requires-Dist: flask-appbuilder==4.
|
24
|
+
Requires-Dist: apache-airflow>=2.9.0
|
25
|
+
Requires-Dist: flask-appbuilder==4.4.1
|
25
26
|
Requires-Dist: flask-login>=0.6.2
|
26
27
|
Requires-Dist: flask>=2.2,<2.3
|
27
28
|
Requires-Dist: google-re2>=1.0
|
28
29
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
29
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
30
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
30
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html
|
31
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2
|
31
32
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
32
33
|
Project-URL: Source Code, https://github.com/apache/airflow
|
33
34
|
Project-URL: Twitter, https://twitter.com/ApacheAirflow
|
@@ -77,7 +78,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
77
78
|
|
78
79
|
Package ``apache-airflow-providers-fab``
|
79
80
|
|
80
|
-
Release: ``1.0.
|
81
|
+
Release: ``1.0.2``
|
81
82
|
|
82
83
|
|
83
84
|
`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__
|
@@ -90,7 +91,7 @@ This is a provider package for ``fab`` provider. All classes for this provider p
|
|
90
91
|
are in ``airflow.providers.fab`` python package.
|
91
92
|
|
92
93
|
You can find package information and changelog for the provider
|
93
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
94
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/>`_.
|
94
95
|
|
95
96
|
Installation
|
96
97
|
------------
|
@@ -99,7 +100,7 @@ You can install this package on top of an existing Airflow 2 installation (see `
|
|
99
100
|
for the minimum Airflow version supported) via
|
100
101
|
``pip install apache-airflow-providers-fab``
|
101
102
|
|
102
|
-
The package supports the following python versions: 3.8,3.9,3.10,3.11
|
103
|
+
The package supports the following python versions: 3.8,3.9,3.10,3.11,3.12
|
103
104
|
|
104
105
|
Requirements
|
105
106
|
------------
|
@@ -109,10 +110,10 @@ PIP package Version required
|
|
109
110
|
==================== ==================
|
110
111
|
``apache-airflow`` ``>=2.9.0``
|
111
112
|
``flask`` ``>=2.2,<2.3``
|
112
|
-
``flask-appbuilder`` ``==4.
|
113
|
+
``flask-appbuilder`` ``==4.4.1``
|
113
114
|
``flask-login`` ``>=0.6.2``
|
114
115
|
``google-re2`` ``>=1.0``
|
115
116
|
==================== ==================
|
116
117
|
|
117
118
|
The changelog for the provider package can be found in the
|
118
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
119
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html>`_.
|
@@ -42,7 +42,7 @@
|
|
42
42
|
|
43
43
|
Package ``apache-airflow-providers-fab``
|
44
44
|
|
45
|
-
Release: ``1.0.
|
45
|
+
Release: ``1.0.2``
|
46
46
|
|
47
47
|
|
48
48
|
`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__
|
@@ -55,7 +55,7 @@ This is a provider package for ``fab`` provider. All classes for this provider p
|
|
55
55
|
are in ``airflow.providers.fab`` python package.
|
56
56
|
|
57
57
|
You can find package information and changelog for the provider
|
58
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
58
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/>`_.
|
59
59
|
|
60
60
|
Installation
|
61
61
|
------------
|
@@ -64,7 +64,7 @@ You can install this package on top of an existing Airflow 2 installation (see `
|
|
64
64
|
for the minimum Airflow version supported) via
|
65
65
|
``pip install apache-airflow-providers-fab``
|
66
66
|
|
67
|
-
The package supports the following python versions: 3.8,3.9,3.10,3.11
|
67
|
+
The package supports the following python versions: 3.8,3.9,3.10,3.11,3.12
|
68
68
|
|
69
69
|
Requirements
|
70
70
|
------------
|
@@ -74,10 +74,10 @@ PIP package Version required
|
|
74
74
|
==================== ==================
|
75
75
|
``apache-airflow`` ``>=2.9.0``
|
76
76
|
``flask`` ``>=2.2,<2.3``
|
77
|
-
``flask-appbuilder`` ``==4.
|
77
|
+
``flask-appbuilder`` ``==4.4.1``
|
78
78
|
``flask-login`` ``>=0.6.2``
|
79
79
|
``google-re2`` ``>=1.0``
|
80
80
|
==================== ==================
|
81
81
|
|
82
82
|
The changelog for the provider package can be found in the
|
83
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.
|
83
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html>`_.
|
@@ -41,10 +41,9 @@ from airflow.www.extensions.init_auth_manager import get_auth_manager
|
|
41
41
|
|
42
42
|
if TYPE_CHECKING:
|
43
43
|
from airflow.api_connexion.types import APIResponse, UpdateMask
|
44
|
-
from airflow.www.security_manager import AirflowSecurityManagerV2
|
45
44
|
|
46
45
|
|
47
|
-
def _check_action_and_resource(sm:
|
46
|
+
def _check_action_and_resource(sm: FabAirflowSecurityManagerOverride, perms: list[tuple[str, str]]) -> None:
|
48
47
|
"""
|
49
48
|
Check if the action or resource exists and otherwise raise 400.
|
50
49
|
|
@@ -57,7 +56,7 @@ def _check_action_and_resource(sm: AirflowSecurityManagerV2, perms: list[tuple[s
|
|
57
56
|
raise BadRequest(detail=f"The specified resource: {resource!r} was not found")
|
58
57
|
|
59
58
|
|
60
|
-
@requires_access_custom_view(
|
59
|
+
@requires_access_custom_view("GET", permissions.RESOURCE_ROLE)
|
61
60
|
def get_role(*, role_name: str) -> APIResponse:
|
62
61
|
"""Get role."""
|
63
62
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -67,7 +66,7 @@ def get_role(*, role_name: str) -> APIResponse:
|
|
67
66
|
return role_schema.dump(role)
|
68
67
|
|
69
68
|
|
70
|
-
@requires_access_custom_view(
|
69
|
+
@requires_access_custom_view("GET", permissions.RESOURCE_ROLE)
|
71
70
|
@format_parameters({"limit": check_limit})
|
72
71
|
def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None) -> APIResponse:
|
73
72
|
"""Get roles."""
|
@@ -95,7 +94,7 @@ def get_roles(*, order_by: str = "name", limit: int, offset: int | None = None)
|
|
95
94
|
return role_collection_schema.dump(RoleCollection(roles=roles, total_entries=total_entries))
|
96
95
|
|
97
96
|
|
98
|
-
@requires_access_custom_view(
|
97
|
+
@requires_access_custom_view("GET", permissions.RESOURCE_ACTION)
|
99
98
|
@format_parameters({"limit": check_limit})
|
100
99
|
def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
|
101
100
|
"""Get permissions."""
|
@@ -107,7 +106,7 @@ def get_permissions(*, limit: int, offset: int | None = None) -> APIResponse:
|
|
107
106
|
return action_collection_schema.dump(ActionCollection(actions=actions, total_entries=total_entries))
|
108
107
|
|
109
108
|
|
110
|
-
@requires_access_custom_view(
|
109
|
+
@requires_access_custom_view("DELETE", permissions.RESOURCE_ROLE)
|
111
110
|
def delete_role(*, role_name: str) -> APIResponse:
|
112
111
|
"""Delete a role."""
|
113
112
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -119,7 +118,7 @@ def delete_role(*, role_name: str) -> APIResponse:
|
|
119
118
|
return NoContent, HTTPStatus.NO_CONTENT
|
120
119
|
|
121
120
|
|
122
|
-
@requires_access_custom_view(
|
121
|
+
@requires_access_custom_view("PUT", permissions.RESOURCE_ROLE)
|
123
122
|
def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse:
|
124
123
|
"""Update a role."""
|
125
124
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -152,7 +151,7 @@ def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse
|
|
152
151
|
return role_schema.dump(role)
|
153
152
|
|
154
153
|
|
155
|
-
@requires_access_custom_view(
|
154
|
+
@requires_access_custom_view("POST", permissions.RESOURCE_ROLE)
|
156
155
|
def post_role() -> APIResponse:
|
157
156
|
"""Create a new role."""
|
158
157
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -44,7 +44,7 @@ if TYPE_CHECKING:
|
|
44
44
|
from airflow.providers.fab.auth_manager.models import Role
|
45
45
|
|
46
46
|
|
47
|
-
@requires_access_custom_view(
|
47
|
+
@requires_access_custom_view("GET", permissions.RESOURCE_USER)
|
48
48
|
def get_user(*, username: str) -> APIResponse:
|
49
49
|
"""Get a user."""
|
50
50
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -54,7 +54,7 @@ def get_user(*, username: str) -> APIResponse:
|
|
54
54
|
return user_collection_item_schema.dump(user)
|
55
55
|
|
56
56
|
|
57
|
-
@requires_access_custom_view(
|
57
|
+
@requires_access_custom_view("GET", permissions.RESOURCE_USER)
|
58
58
|
@format_parameters({"limit": check_limit})
|
59
59
|
def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) -> APIResponse:
|
60
60
|
"""Get users."""
|
@@ -86,7 +86,7 @@ def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) ->
|
|
86
86
|
return user_collection_schema.dump(UserCollection(users=users, total_entries=total_entries))
|
87
87
|
|
88
88
|
|
89
|
-
@requires_access_custom_view(
|
89
|
+
@requires_access_custom_view("POST", permissions.RESOURCE_USER)
|
90
90
|
def post_user() -> APIResponse:
|
91
91
|
"""Create a new user."""
|
92
92
|
try:
|
@@ -129,7 +129,7 @@ def post_user() -> APIResponse:
|
|
129
129
|
return user_schema.dump(user)
|
130
130
|
|
131
131
|
|
132
|
-
@requires_access_custom_view(
|
132
|
+
@requires_access_custom_view("PUT", permissions.RESOURCE_USER)
|
133
133
|
def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
|
134
134
|
"""Update a user."""
|
135
135
|
try:
|
@@ -198,7 +198,7 @@ def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
|
|
198
198
|
return user_schema.dump(user)
|
199
199
|
|
200
200
|
|
201
|
-
@requires_access_custom_view(
|
201
|
+
@requires_access_custom_view("DELETE", permissions.RESOURCE_USER)
|
202
202
|
def delete_user(*, username: str) -> APIResponse:
|
203
203
|
"""Delete a user."""
|
204
204
|
security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
|
@@ -136,6 +136,27 @@ USERS_COMMANDS = (
|
|
136
136
|
" --email admin@example.org"
|
137
137
|
),
|
138
138
|
),
|
139
|
+
ActionCommand(
|
140
|
+
name="reset-password",
|
141
|
+
help="Reset a user's password",
|
142
|
+
func=lazy_load_command(
|
143
|
+
"airflow.providers.fab.auth_manager.cli_commands.user_command.user_reset_password"
|
144
|
+
),
|
145
|
+
args=(
|
146
|
+
ARG_USERNAME_OPTIONAL,
|
147
|
+
ARG_EMAIL_OPTIONAL,
|
148
|
+
ARG_PASSWORD,
|
149
|
+
ARG_USE_RANDOM_PASSWORD,
|
150
|
+
ARG_VERBOSE,
|
151
|
+
),
|
152
|
+
epilog=(
|
153
|
+
"examples:\n"
|
154
|
+
'To reset an user with username equals to "admin", run:\n'
|
155
|
+
"\n"
|
156
|
+
" $ airflow users reset-password \\\n"
|
157
|
+
" --username admin"
|
158
|
+
),
|
159
|
+
),
|
139
160
|
ActionCommand(
|
140
161
|
name="delete",
|
141
162
|
help="Delete a user",
|
@@ -16,11 +16,13 @@
|
|
16
16
|
# specific language governing permissions and limitations
|
17
17
|
# under the License.
|
18
18
|
"""Roles sub-commands."""
|
19
|
+
|
19
20
|
from __future__ import annotations
|
20
21
|
|
21
22
|
import itertools
|
22
23
|
import json
|
23
24
|
import os
|
25
|
+
from argparse import Namespace
|
24
26
|
from collections import defaultdict
|
25
27
|
from typing import TYPE_CHECKING
|
26
28
|
|
@@ -155,30 +157,38 @@ def roles_del_perms(args):
|
|
155
157
|
@suppress_logs_and_warning
|
156
158
|
@providers_configuration_loaded
|
157
159
|
def roles_export(args):
|
158
|
-
"""
|
159
|
-
Export all the roles from the database to a file.
|
160
|
-
|
161
|
-
Note, this function does not export the permissions associated for each role.
|
162
|
-
Strictly, it exports the role names into the passed role json file.
|
163
|
-
"""
|
160
|
+
"""Export all the roles from the database to a file including permissions."""
|
164
161
|
with get_application_builder() as appbuilder:
|
165
162
|
roles = appbuilder.sm.get_all_roles()
|
166
|
-
exporting_roles = [role
|
163
|
+
exporting_roles = [role for role in roles if role.name not in EXISTING_ROLES]
|
167
164
|
filename = os.path.expanduser(args.file)
|
168
|
-
|
165
|
+
|
166
|
+
permission_map: dict[tuple[str, str], list[str]] = defaultdict(list)
|
167
|
+
for role in exporting_roles:
|
168
|
+
if role.permissions:
|
169
|
+
for permission in role.permissions:
|
170
|
+
permission_map[(role.name, permission.resource.name)].append(permission.action.name)
|
171
|
+
else:
|
172
|
+
permission_map[(role.name, "")].append("")
|
173
|
+
export_data = [
|
174
|
+
{"name": role, "resource": resource, "action": ",".join(sorted(permissions))}
|
175
|
+
for (role, resource), permissions in permission_map.items()
|
176
|
+
]
|
177
|
+
kwargs = {} if not args.pretty else {"sort_keys": False, "indent": 4}
|
169
178
|
with open(filename, "w", encoding="utf-8") as f:
|
170
|
-
json.dump(
|
171
|
-
print(
|
179
|
+
json.dump(export_data, f, **kwargs)
|
180
|
+
print(
|
181
|
+
f"{len(exporting_roles)} roles with {len(export_data)} linked permissions successfully exported to {filename}"
|
182
|
+
)
|
172
183
|
|
173
184
|
|
174
185
|
@cli_utils.action_cli
|
175
186
|
@suppress_logs_and_warning
|
176
187
|
def roles_import(args):
|
177
188
|
"""
|
178
|
-
Import all the roles into the db from the given json file.
|
189
|
+
Import all the roles into the db from the given json file including their permissions.
|
179
190
|
|
180
|
-
Note,
|
181
|
-
Strictly, it imports the role names in the role json file passed.
|
191
|
+
Note, if a role already exists in the db, it is not overwritten, even when the permissions change.
|
182
192
|
"""
|
183
193
|
json_file = args.file
|
184
194
|
try:
|
@@ -193,7 +203,30 @@ def roles_import(args):
|
|
193
203
|
|
194
204
|
with get_application_builder() as appbuilder:
|
195
205
|
existing_roles = [role.name for role in appbuilder.sm.get_all_roles()]
|
196
|
-
roles_to_import = [
|
197
|
-
for
|
198
|
-
appbuilder.sm.
|
199
|
-
|
206
|
+
roles_to_import = [role_dict for role_dict in role_list if role_dict["name"] not in existing_roles]
|
207
|
+
for role_dict in roles_to_import:
|
208
|
+
if role_dict["name"] not in appbuilder.sm.get_all_roles():
|
209
|
+
if role_dict["action"] == "" or role_dict["resource"] == "":
|
210
|
+
appbuilder.sm.add_role(role_dict["name"])
|
211
|
+
else:
|
212
|
+
appbuilder.sm.add_role(role_dict["name"])
|
213
|
+
role_args = Namespace(
|
214
|
+
subcommand="add-perms",
|
215
|
+
role=[role_dict["name"]],
|
216
|
+
resource=[role_dict["resource"]],
|
217
|
+
action=role_dict["action"].split(","),
|
218
|
+
)
|
219
|
+
__roles_add_or_remove_permissions(role_args)
|
220
|
+
|
221
|
+
if role_dict["name"] in appbuilder.sm.get_all_roles():
|
222
|
+
if role_dict["action"] == "" or role_dict["resource"] == "":
|
223
|
+
pass
|
224
|
+
else:
|
225
|
+
role_args = Namespace(
|
226
|
+
subcommand="add-perms",
|
227
|
+
role=[role_dict["name"]],
|
228
|
+
resource=[role_dict["resource"]],
|
229
|
+
action=role_dict["action"].split(","),
|
230
|
+
)
|
231
|
+
__roles_add_or_remove_permissions(role_args)
|
232
|
+
print("roles and permissions successfully imported")
|
@@ -15,6 +15,7 @@
|
|
15
15
|
# specific language governing permissions and limitations
|
16
16
|
# under the License.
|
17
17
|
"""User sub-commands."""
|
18
|
+
|
18
19
|
from __future__ import annotations
|
19
20
|
|
20
21
|
import functools
|
@@ -70,16 +71,7 @@ def users_create(args):
|
|
70
71
|
if not role:
|
71
72
|
valid_roles = appbuilder.sm.get_all_roles()
|
72
73
|
raise SystemExit(f"{args.role} is not a valid role. Valid roles are: {valid_roles}")
|
73
|
-
|
74
|
-
password = "".join(random.choices(string.printable, k=16))
|
75
|
-
elif args.password:
|
76
|
-
password = args.password
|
77
|
-
else:
|
78
|
-
password = getpass.getpass("Password:")
|
79
|
-
password_confirmation = getpass.getpass("Repeat for confirmation:")
|
80
|
-
if password != password_confirmation:
|
81
|
-
raise SystemExit("Passwords did not match")
|
82
|
-
|
74
|
+
password = _create_password(args)
|
83
75
|
if appbuilder.sm.find_user(args.username):
|
84
76
|
print(f"{args.username} already exist in the db")
|
85
77
|
return
|
@@ -106,6 +98,32 @@ def _find_user(args):
|
|
106
98
|
return user
|
107
99
|
|
108
100
|
|
101
|
+
@cli_utils.action_cli
|
102
|
+
@providers_configuration_loaded
|
103
|
+
def user_reset_password(args):
|
104
|
+
"""Reset user password user from DB."""
|
105
|
+
user = _find_user(args)
|
106
|
+
password = _create_password(args)
|
107
|
+
with get_application_builder() as appbuilder:
|
108
|
+
if appbuilder.sm.reset_password(user.id, password):
|
109
|
+
print(f'User "{user.username}" password reset successfully')
|
110
|
+
else:
|
111
|
+
raise SystemExit("Failed to reset user password")
|
112
|
+
|
113
|
+
|
114
|
+
def _create_password(args):
|
115
|
+
if args.use_random_password:
|
116
|
+
password = "".join(random.choices(string.printable, k=16))
|
117
|
+
elif args.password:
|
118
|
+
password = args.password
|
119
|
+
else:
|
120
|
+
password = getpass.getpass("Password:")
|
121
|
+
password_confirmation = getpass.getpass("Repeat for confirmation:")
|
122
|
+
if password != password_confirmation:
|
123
|
+
raise SystemExit("Passwords did not match")
|
124
|
+
return password
|
125
|
+
|
126
|
+
|
109
127
|
@cli_utils.action_cli
|
110
128
|
@providers_configuration_loaded
|
111
129
|
def users_delete(args):
|
@@ -67,7 +67,7 @@ def _requires_access_fab(permissions: Sequence[tuple[str, str]] | None = None) -
|
|
67
67
|
|
68
68
|
def _has_access_fab(permissions: Sequence[tuple[str, str]] | None = None) -> Callable[[T], T]:
|
69
69
|
"""
|
70
|
-
|
70
|
+
Check current user's permissions against required permissions.
|
71
71
|
|
72
72
|
This decorator is only kept for backward compatible reasons. The decorator
|
73
73
|
``airflow.www.auth.has_access``, which redirects to this decorator, is widely used in user plugins.
|
@@ -93,11 +93,14 @@ def _has_access_fab(permissions: Sequence[tuple[str, str]] | None = None) -> Cal
|
|
93
93
|
|
94
94
|
if len(unique_dag_ids) > 1:
|
95
95
|
log.warning(
|
96
|
-
|
96
|
+
"There are different dag_ids passed in the request: %s. Returning 403.", unique_dag_ids
|
97
97
|
)
|
98
98
|
log.warning(
|
99
|
-
|
100
|
-
|
99
|
+
"kwargs: %s, args: %s, form: %s, json: %s",
|
100
|
+
dag_id_kwargs,
|
101
|
+
dag_id_args,
|
102
|
+
dag_id_form,
|
103
|
+
dag_id_json,
|
101
104
|
)
|
102
105
|
return (
|
103
106
|
render_template(
|
@@ -17,7 +17,7 @@
|
|
17
17
|
# under the License.
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
-
import
|
20
|
+
import argparse
|
21
21
|
from functools import cached_property
|
22
22
|
from pathlib import Path
|
23
23
|
from typing import TYPE_CHECKING, Container
|
@@ -40,10 +40,11 @@ from airflow.auth.managers.models.resource_details import (
|
|
40
40
|
)
|
41
41
|
from airflow.auth.managers.utils.fab import get_fab_action_from_method_map, get_method_from_fab_action_map
|
42
42
|
from airflow.cli.cli_config import (
|
43
|
+
DefaultHelpParser,
|
43
44
|
GroupCommand,
|
44
45
|
)
|
45
46
|
from airflow.configuration import conf
|
46
|
-
from airflow.exceptions import AirflowException
|
47
|
+
from airflow.exceptions import AirflowException
|
47
48
|
from airflow.models import DagModel
|
48
49
|
from airflow.providers.fab.auth_manager.cli_commands.definition import (
|
49
50
|
ROLES_COMMANDS,
|
@@ -53,8 +54,6 @@ from airflow.providers.fab.auth_manager.cli_commands.definition import (
|
|
53
54
|
from airflow.providers.fab.auth_manager.models import Permission, Role, User
|
54
55
|
from airflow.security import permissions
|
55
56
|
from airflow.security.permissions import (
|
56
|
-
ACTION_CAN_ACCESS_MENU,
|
57
|
-
ACTION_CAN_READ,
|
58
57
|
RESOURCE_AUDIT_LOG,
|
59
58
|
RESOURCE_CLUSTER_ACTIVITY,
|
60
59
|
RESOURCE_CONFIG,
|
@@ -83,6 +82,7 @@ from airflow.security.permissions import (
|
|
83
82
|
)
|
84
83
|
from airflow.utils.session import NEW_SESSION, provide_session
|
85
84
|
from airflow.utils.yaml import safe_load
|
85
|
+
from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
|
86
86
|
from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver
|
87
87
|
|
88
88
|
if TYPE_CHECKING:
|
@@ -96,7 +96,6 @@ _MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE: dict[DagAccessEntity, tuple[str, ..
|
|
96
96
|
DagAccessEntity.AUDIT_LOG: (RESOURCE_AUDIT_LOG,),
|
97
97
|
DagAccessEntity.CODE: (RESOURCE_DAG_CODE,),
|
98
98
|
DagAccessEntity.DEPENDENCIES: (RESOURCE_DAG_DEPENDENCIES,),
|
99
|
-
DagAccessEntity.IMPORT_ERRORS: (RESOURCE_IMPORT_ERROR,),
|
100
99
|
DagAccessEntity.RUN: (RESOURCE_DAG_RUN,),
|
101
100
|
DagAccessEntity.SLA_MISS: (RESOURCE_SLA_MISS,),
|
102
101
|
# RESOURCE_TASK_INSTANCE has been originally misused. RESOURCE_TASK_INSTANCE referred to task definition
|
@@ -115,6 +114,7 @@ _MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE: dict[DagAccessEntity, tuple[str, ..
|
|
115
114
|
_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE = {
|
116
115
|
AccessView.CLUSTER_ACTIVITY: RESOURCE_CLUSTER_ACTIVITY,
|
117
116
|
AccessView.DOCS: RESOURCE_DOCS,
|
117
|
+
AccessView.IMPORT_ERRORS: RESOURCE_IMPORT_ERROR,
|
118
118
|
AccessView.JOBS: RESOURCE_JOB,
|
119
119
|
AccessView.PLUGINS: RESOURCE_PLUGIN,
|
120
120
|
AccessView.PROVIDERS: RESOURCE_PROVIDER,
|
@@ -155,9 +155,7 @@ class FabAuthManager(BaseAuthManager):
|
|
155
155
|
specification=specification,
|
156
156
|
resolver=_LazyResolver(),
|
157
157
|
base_path="/auth/fab/v1",
|
158
|
-
options={
|
159
|
-
"swagger_ui": conf.getboolean("webserver", "enable_swagger_ui", fallback=True),
|
160
|
-
},
|
158
|
+
options={"swagger_ui": SWAGGER_ENABLED, "swagger_path": SWAGGER_BUNDLE.__fspath__()},
|
161
159
|
strict_validation=True,
|
162
160
|
validate_responses=True,
|
163
161
|
validator_map={"body": _CustomErrorRequestBodyValidator},
|
@@ -263,16 +261,19 @@ class FabAuthManager(BaseAuthManager):
|
|
263
261
|
return self._is_authorized(method=method, resource_type=RESOURCE_VARIABLE, user=user)
|
264
262
|
|
265
263
|
def is_authorized_view(self, *, access_view: AccessView, user: BaseUser | None = None) -> bool:
|
264
|
+
# "Docs" are only links in the menu, there is no page associated
|
265
|
+
method: ResourceMethod = "MENU" if access_view == AccessView.DOCS else "GET"
|
266
266
|
return self._is_authorized(
|
267
|
-
method=
|
267
|
+
method=method, resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user
|
268
268
|
)
|
269
269
|
|
270
270
|
def is_authorized_custom_view(
|
271
|
-
self, *,
|
271
|
+
self, *, method: ResourceMethod, resource_name: str, user: BaseUser | None = None
|
272
272
|
):
|
273
273
|
if not user:
|
274
274
|
user = self.get_user()
|
275
|
-
|
275
|
+
fab_action_name = get_fab_action_from_method_map()[method]
|
276
|
+
return (fab_action_name, resource_name) in self._get_user_permissions(user)
|
276
277
|
|
277
278
|
@provide_session
|
278
279
|
def get_permitted_dag_ids(
|
@@ -334,20 +335,12 @@ class FabAuthManager(BaseAuthManager):
|
|
334
335
|
from airflow.providers.fab.auth_manager.security_manager.override import (
|
335
336
|
FabAirflowSecurityManagerOverride,
|
336
337
|
)
|
337
|
-
from airflow.www.security_manager import AirflowSecurityManagerV2
|
338
338
|
|
339
339
|
sm_from_config = self.appbuilder.get_app.config.get("SECURITY_MANAGER_CLASS")
|
340
340
|
if sm_from_config:
|
341
|
-
if not issubclass(sm_from_config, AirflowSecurityManagerV2):
|
342
|
-
raise Exception(
|
343
|
-
"""Your CUSTOM_SECURITY_MANAGER must extend AirflowSecurityManagerV2,
|
344
|
-
not FAB's own security manager."""
|
345
|
-
)
|
346
341
|
if not issubclass(sm_from_config, FabAirflowSecurityManagerOverride):
|
347
|
-
|
348
|
-
"
|
349
|
-
"FabAirflowSecurityManagerOverride instead of AirflowSecurityManager.",
|
350
|
-
AirflowProviderDeprecationWarning,
|
342
|
+
raise Exception(
|
343
|
+
"""Your CUSTOM_SECURITY_MANAGER must extend FabAirflowSecurityManagerOverride."""
|
351
344
|
)
|
352
345
|
return sm_from_config(self.appbuilder)
|
353
346
|
|
@@ -453,7 +446,7 @@ class FabAuthManager(BaseAuthManager):
|
|
453
446
|
|
454
447
|
def _resource_name_for_dag(self, dag_id: str) -> str:
|
455
448
|
"""
|
456
|
-
|
449
|
+
Return the FAB resource name for a DAG id.
|
457
450
|
|
458
451
|
:param dag_id: the DAG id
|
459
452
|
|
@@ -471,18 +464,11 @@ class FabAuthManager(BaseAuthManager):
|
|
471
464
|
"""
|
472
465
|
Return the user permissions.
|
473
466
|
|
474
|
-
ACTION_CAN_READ and ACTION_CAN_ACCESS_MENU are merged into because they are very similar.
|
475
|
-
We can assume that if a user has permissions to read variables, they also have permissions to access
|
476
|
-
the menu "Variables".
|
477
|
-
|
478
467
|
:param user: the user to get permissions for
|
479
468
|
|
480
469
|
:meta private:
|
481
470
|
"""
|
482
|
-
|
483
|
-
return [
|
484
|
-
(ACTION_CAN_READ if perm[0] == ACTION_CAN_ACCESS_MENU else perm[0], perm[1]) for perm in perms
|
485
|
-
]
|
471
|
+
return getattr(user, "perms") or []
|
486
472
|
|
487
473
|
def _get_root_dag_id(self, dag_id: str) -> str:
|
488
474
|
"""
|
@@ -508,5 +494,18 @@ class FabAuthManager(BaseAuthManager):
|
|
508
494
|
# Otherwise, when the name of a view or menu is changed, the framework
|
509
495
|
# will add the new Views and Menus names to the backend, but will not
|
510
496
|
# delete the old ones.
|
511
|
-
if conf.getboolean(
|
497
|
+
if conf.getboolean(
|
498
|
+
"fab", "UPDATE_FAB_PERMS", fallback=conf.getboolean("webserver", "UPDATE_FAB_PERMS")
|
499
|
+
):
|
512
500
|
self.security_manager.sync_roles()
|
501
|
+
|
502
|
+
|
503
|
+
def get_parser() -> argparse.ArgumentParser:
|
504
|
+
"""Generate documentation; used by Sphinx argparse."""
|
505
|
+
from airflow.cli.cli_parser import AirflowHelpFormatter, _add_command
|
506
|
+
|
507
|
+
parser = DefaultHelpParser(prog="airflow", formatter_class=AirflowHelpFormatter)
|
508
|
+
subparsers = parser.add_subparsers(dest="subcommand", metavar="GROUP_OR_COMMAND")
|
509
|
+
for group_command in FabAuthManager.get_cli_commands():
|
510
|
+
_add_command(subparsers, group_command)
|
511
|
+
return parser
|
@@ -152,7 +152,7 @@ class User(Model, BaseUser):
|
|
152
152
|
String(512).with_variant(String(512, collation="NOCASE"), "sqlite"), unique=True, nullable=False
|
153
153
|
)
|
154
154
|
password = Column(String(256))
|
155
|
-
active = Column(Boolean)
|
155
|
+
active = Column(Boolean, default=True)
|
156
156
|
email = Column(String(512), unique=True, nullable=False)
|
157
157
|
last_login = Column(DateTime)
|
158
158
|
login_count = Column(Integer)
|