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.
Files changed (37) hide show
  1. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/PKG-INFO +11 -10
  2. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/README.rst +5 -5
  3. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/__init__.py +1 -1
  4. {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
  5. {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
  6. {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
  7. {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
  8. {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
  9. {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
  10. {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
  11. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/decorators/auth.py +7 -4
  12. {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
  13. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/models/__init__.py +1 -1
  14. {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
  15. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/get_provider_info.py +32 -3
  16. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/pyproject.toml +6 -5
  17. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/LICENSE +0 -0
  18. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/__init__.py +0 -0
  19. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/api/__init__.py +0 -0
  20. {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
  21. {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
  22. {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
  23. {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
  24. {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
  25. {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
  26. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/decorators/__init__.py +0 -0
  27. {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
  28. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/openapi/__init__.py +0 -0
  29. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/openapi/v1.yaml +0 -0
  30. {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
  31. {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
  32. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/__init__.py +0 -0
  33. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/permissions.py +0 -0
  34. {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
  35. {apache_airflow_providers_fab-1.0.0rc1 → apache_airflow_providers_fab-1.0.2}/airflow/providers/fab/auth_manager/views/user.py +0 -0
  36. {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
  37. {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.0rc1
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.dev0
24
- Requires-Dist: flask-appbuilder==4.3.10
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.0/changelog.html
30
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.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.0.rc1``
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.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.3.10``
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.0/changelog.html>`_.
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.0.rc1``
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.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.3.10``
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.0/changelog.html>`_.
83
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.0.2/changelog.html>`_.
@@ -27,7 +27,7 @@ import packaging.version
27
27
 
28
28
  __all__ = ["__version__"]
29
29
 
30
- __version__ = "1.0.0"
30
+ __version__ = "1.0.2"
31
31
 
32
32
  try:
33
33
  from airflow import __version__ as airflow_version
@@ -15,6 +15,7 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
  """Basic authentication backend."""
18
+
18
19
  from __future__ import annotations
19
20
 
20
21
  from functools import wraps
@@ -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: AirflowSecurityManagerV2, perms: list[tuple[str, str]]) -> None:
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(permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE)
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(permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE)
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(permissions.ACTION_CAN_READ, permissions.RESOURCE_ACTION)
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(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_ROLE)
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(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE)
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(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_ROLE)
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(permissions.ACTION_CAN_READ, permissions.RESOURCE_USER)
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(permissions.ACTION_CAN_READ, permissions.RESOURCE_USER)
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(permissions.ACTION_CAN_CREATE, permissions.RESOURCE_USER)
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(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_USER)
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(permissions.ACTION_CAN_DELETE, permissions.RESOURCE_USER)
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.name for role in roles if role.name not in EXISTING_ROLES]
163
+ exporting_roles = [role for role in roles if role.name not in EXISTING_ROLES]
167
164
  filename = os.path.expanduser(args.file)
168
- kwargs = {} if not args.pretty else {"sort_keys": True, "indent": 4}
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(exporting_roles, f, **kwargs)
171
- print(f"{len(exporting_roles)} roles successfully exported to {filename}")
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, this function does not import the permissions for different roles and import them as well.
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 = [role for role in role_list if role not in existing_roles]
197
- for role_name in roles_to_import:
198
- appbuilder.sm.add_role(role_name)
199
- print(f"roles '{roles_to_import}' successfully imported")
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")
@@ -16,6 +16,7 @@
16
16
  # specific language governing permissions and limitations
17
17
  # under the License.
18
18
  """Sync permission command."""
19
+
19
20
  from __future__ import annotations
20
21
 
21
22
  from airflow.utils import cli as cli_utils
@@ -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
- if args.use_random_password:
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
- Factory for decorator that checks current user's permissions against required permissions.
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
- f"There are different dag_ids passed in the request: {unique_dag_ids}. Returning 403."
96
+ "There are different dag_ids passed in the request: %s. Returning 403.", unique_dag_ids
97
97
  )
98
98
  log.warning(
99
- f"kwargs: {dag_id_kwargs}, args: {dag_id_args}, "
100
- f"form: {dag_id_form}, json: {dag_id_json}"
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 warnings
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, AirflowProviderDeprecationWarning
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="GET", resource_type=_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[access_view], user=user
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, *, fab_action_name: str, fab_resource_name: str, user: BaseUser | None = None
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
- return (fab_action_name, fab_resource_name) in self._get_user_permissions(user)
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
- warnings.warn(
348
- "Please make your custom security manager inherit from "
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
- Returns the FAB resource name for a DAG id.
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
- perms = getattr(user, "perms") or []
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("webserver", "UPDATE_FAB_PERMS"):
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)