apache-airflow-providers-fab 1.3.0rc1__tar.gz → 1.4.0rc1__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 (47) hide show
  1. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/PKG-INFO +8 -6
  2. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/README.rst +3 -3
  3. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/__init__.py +1 -1
  4. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/alembic.ini +133 -0
  5. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +4 -2
  6. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +146 -0
  7. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/cli_commands/db_command.py +52 -0
  8. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/definition.py +61 -0
  9. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/user_command.py +6 -5
  10. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/fab_auth_manager.py +30 -6
  11. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/models/__init__.py +21 -8
  12. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/models/anonymous_user.py +7 -1
  13. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/models/db.py +107 -0
  14. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/security_manager/override.py +5 -3
  15. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/get_provider_info.py +4 -1
  16. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/README +1 -0
  17. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/__init__.py +16 -0
  18. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/env.py +125 -0
  19. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/script.py.mako +45 -0
  20. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/versions/0001_1_4_0_placeholder_migration.py +45 -0
  21. apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/versions/__init__.py +16 -0
  22. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/pyproject.toml +7 -3
  23. apache_airflow_providers_fab-1.3.0rc1/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +0 -44
  24. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/LICENSE +0 -0
  25. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/__init__.py +0 -0
  26. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api/__init__.py +0 -0
  27. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api/auth/__init__.py +0 -0
  28. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api/auth/backend/__init__.py +0 -0
  29. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api_endpoints/__init__.py +0 -0
  30. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +0 -0
  31. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +0 -0
  32. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/__init__.py +0 -0
  33. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/role_command.py +0 -0
  34. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py +0 -0
  35. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/cli_commands/utils.py +0 -0
  36. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/decorators/__init__.py +0 -0
  37. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/decorators/auth.py +0 -0
  38. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/openapi/__init__.py +0 -0
  39. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/openapi/v1.yaml +0 -0
  40. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/security_manager/__init__.py +0 -0
  41. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/security_manager/constants.py +0 -0
  42. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/__init__.py +0 -0
  43. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/permissions.py +0 -0
  44. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/roles_list.py +0 -0
  45. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/user.py +0 -0
  46. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/user_edit.py +0 -0
  47. {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/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.3.0rc1
3
+ Version: 1.4.0rc1
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>
@@ -27,13 +27,15 @@ Requires-Dist: flask-login>=0.6.2
27
27
  Requires-Dist: flask>=2.2,<2.3
28
28
  Requires-Dist: google-re2>=1.0
29
29
  Requires-Dist: jmespath>=0.7.0
30
+ Requires-Dist: kerberos>=1.3.0 ; extra == "kerberos"
30
31
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
31
- Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.3.0/changelog.html
32
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.3.0
32
+ Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0/changelog.html
33
+ Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0
33
34
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
34
35
  Project-URL: Source Code, https://github.com/apache/airflow
35
36
  Project-URL: Twitter, https://twitter.com/ApacheAirflow
36
37
  Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
38
+ Provides-Extra: kerberos
37
39
 
38
40
 
39
41
  .. Licensed to the Apache Software Foundation (ASF) under one
@@ -79,7 +81,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
79
81
 
80
82
  Package ``apache-airflow-providers-fab``
81
83
 
82
- Release: ``1.3.0.rc1``
84
+ Release: ``1.4.0.rc1``
83
85
 
84
86
 
85
87
  `Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__
@@ -92,7 +94,7 @@ This is a provider package for ``fab`` provider. All classes for this provider p
92
94
  are in ``airflow.providers.fab`` python package.
93
95
 
94
96
  You can find package information and changelog for the provider
95
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.3.0/>`_.
97
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0/>`_.
96
98
 
97
99
  Installation
98
100
  ------------
@@ -118,4 +120,4 @@ PIP package Version required
118
120
  ==================== ==================
119
121
 
120
122
  The changelog for the provider package can be found in the
121
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.3.0/changelog.html>`_.
123
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0/changelog.html>`_.
@@ -42,7 +42,7 @@
42
42
 
43
43
  Package ``apache-airflow-providers-fab``
44
44
 
45
- Release: ``1.3.0.rc1``
45
+ Release: ``1.4.0.rc1``
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.3.0/>`_.
58
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0/>`_.
59
59
 
60
60
  Installation
61
61
  ------------
@@ -81,4 +81,4 @@ PIP package Version required
81
81
  ==================== ==================
82
82
 
83
83
  The changelog for the provider package can be found in the
84
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.3.0/changelog.html>`_.
84
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-fab/1.4.0/changelog.html>`_.
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "1.3.0"
32
+ __version__ = "1.4.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.9.0"
@@ -0,0 +1,133 @@
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
+
18
+ # A generic, single database configuration.
19
+
20
+ [alembic]
21
+ # path to migration scripts
22
+ # Use forward slashes (/) also on windows to provide an os agnostic path
23
+ script_location = %(here)s/migrations
24
+
25
+ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
26
+ # Uncomment the line below if you want the files to be prepended with date and time
27
+ # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
28
+ # for all available tokens
29
+ # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
30
+
31
+ # sys.path path, will be prepended to sys.path if present.
32
+ # defaults to the current working directory.
33
+ prepend_sys_path = .
34
+
35
+ # timezone to use when rendering the date within the migration file
36
+ # as well as the filename.
37
+ # If specified, requires the python>=3.9 or backports.zoneinfo library.
38
+ # Any required deps can installed by adding `alembic[tz]` to the pip requirements
39
+ # string value is passed to ZoneInfo()
40
+ # leave blank for localtime
41
+ # timezone =
42
+
43
+ # max length of characters to apply to the "slug" field
44
+ # truncate_slug_length = 40
45
+
46
+ # set to 'true' to run the environment during
47
+ # the 'revision' command, regardless of autogenerate
48
+ # revision_environment = false
49
+
50
+ # set to 'true' to allow .pyc and .pyo files without
51
+ # a source .py file to be detected as revisions in the
52
+ # versions/ directory
53
+ # sourceless = false
54
+
55
+ # version location specification; This defaults
56
+ # to alembic/versions. When using multiple version
57
+ # directories, initial revisions must be specified with --version-path.
58
+ # The path separator used here should be the separator specified by "version_path_separator" below.
59
+ # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
60
+
61
+ # version path separator; As mentioned above, this is the character used to split
62
+ # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
63
+ # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
64
+ # Valid values for version_path_separator are:
65
+ #
66
+ # version_path_separator = :
67
+ # version_path_separator = ;
68
+ # version_path_separator = space
69
+ version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
70
+
71
+ # set to 'true' to search source files recursively
72
+ # in each "version_locations" directory
73
+ # new in Alembic version 1.10
74
+ # recursive_version_locations = false
75
+
76
+ # the output encoding used when revision files
77
+ # are written from script.py.mako
78
+ # output_encoding = utf-8
79
+
80
+ sqlalchemy.url = scheme://localhost/airflow
81
+
82
+
83
+ [post_write_hooks]
84
+ # post_write_hooks defines scripts or Python functions that are run
85
+ # on newly generated revision scripts. See the documentation for further
86
+ # detail and examples
87
+
88
+ # format using "black" - use the console_scripts runner, against the "black" entrypoint
89
+ # hooks = black
90
+ # black.type = console_scripts
91
+ # black.entrypoint = black
92
+ # black.options = -l 79 REVISION_SCRIPT_FILENAME
93
+
94
+ # lint with attempts to fix using "ruff" - use the exec runner, execute a binary
95
+ # hooks = ruff
96
+ # ruff.type = exec
97
+ # ruff.executable = %(here)s/.venv/bin/ruff
98
+ # ruff.options = --fix REVISION_SCRIPT_FILENAME
99
+
100
+ # Logging configuration
101
+ [loggers]
102
+ keys = root,sqlalchemy,alembic
103
+
104
+ [handlers]
105
+ keys = console
106
+
107
+ [formatters]
108
+ keys = generic
109
+
110
+ [logger_root]
111
+ level = WARN
112
+ handlers = console
113
+ qualname =
114
+
115
+ [logger_sqlalchemy]
116
+ level = WARN
117
+ handlers =
118
+ qualname = sqlalchemy.engine
119
+
120
+ [logger_alembic]
121
+ level = INFO
122
+ handlers =
123
+ qualname = alembic
124
+
125
+ [handler_console]
126
+ class = StreamHandler
127
+ args = (sys.stderr,)
128
+ level = NOTSET
129
+ formatter = generic
130
+
131
+ [formatter_generic]
132
+ format = %(levelname)-5.5s [%(name)s] %(message)s
133
+ datefmt = %H:%M:%S
@@ -21,7 +21,7 @@ from __future__ import annotations
21
21
  from functools import wraps
22
22
  from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
23
23
 
24
- from flask import Response, request
24
+ from flask import Response, current_app, request
25
25
  from flask_appbuilder.const import AUTH_LDAP
26
26
  from flask_login import login_user
27
27
 
@@ -62,7 +62,9 @@ def requires_authentication(function: T):
62
62
 
63
63
  @wraps(function)
64
64
  def decorated(*args, **kwargs):
65
- if auth_current_user() is not None:
65
+ if auth_current_user() is not None or current_app.appbuilder.get_app.config.get(
66
+ "AUTH_ROLE_PUBLIC", None
67
+ ):
66
68
  return function(*args, **kwargs)
67
69
  else:
68
70
  return Response("Unauthorized", 401, {"WWW-Authenticate": "Basic"})
@@ -0,0 +1,146 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import os
22
+ from functools import wraps
23
+ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, TypeVar, cast
24
+
25
+ import kerberos
26
+ from flask import Response, current_app, g, make_response, request
27
+ from requests_kerberos import HTTPKerberosAuth
28
+
29
+ from airflow.configuration import conf
30
+ from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
31
+ from airflow.utils.net import getfqdn
32
+ from airflow.www.extensions.init_auth_manager import get_auth_manager
33
+
34
+ if TYPE_CHECKING:
35
+ from airflow.auth.managers.models.base_user import BaseUser
36
+
37
+ log = logging.getLogger(__name__)
38
+
39
+ CLIENT_AUTH: tuple[str, str] | Any | None = HTTPKerberosAuth(service="airflow")
40
+
41
+
42
+ class KerberosService:
43
+ """Class to keep information about the Kerberos Service initialized."""
44
+
45
+ def __init__(self):
46
+ self.service_name = None
47
+
48
+
49
+ class _KerberosAuth(NamedTuple):
50
+ return_code: int | None
51
+ user: str = ""
52
+ token: str | None = None
53
+
54
+
55
+ # Stores currently initialized Kerberos Service
56
+ _KERBEROS_SERVICE = KerberosService()
57
+
58
+
59
+ def init_app(app):
60
+ """Initialize application with kerberos."""
61
+ hostname = app.config.get("SERVER_NAME")
62
+ if not hostname:
63
+ hostname = getfqdn()
64
+ log.info("Kerberos: hostname %s", hostname)
65
+
66
+ service = "airflow"
67
+
68
+ _KERBEROS_SERVICE.service_name = f"{service}@{hostname}"
69
+
70
+ if "KRB5_KTNAME" not in os.environ:
71
+ os.environ["KRB5_KTNAME"] = conf.get("kerberos", "keytab")
72
+
73
+ try:
74
+ log.info("Kerberos init: %s %s", service, hostname)
75
+ principal = kerberos.getServerPrincipalDetails(service, hostname)
76
+ except kerberos.KrbError as err:
77
+ log.warning("Kerberos: %s", err)
78
+ else:
79
+ log.info("Kerberos API: server is %s", principal)
80
+
81
+
82
+ def _unauthorized():
83
+ """Indicate that authorization is required."""
84
+ return Response("Unauthorized", 401, {"WWW-Authenticate": "Negotiate"})
85
+
86
+
87
+ def _forbidden():
88
+ return Response("Forbidden", 403)
89
+
90
+
91
+ def _gssapi_authenticate(token) -> _KerberosAuth | None:
92
+ state = None
93
+ try:
94
+ return_code, state = kerberos.authGSSServerInit(_KERBEROS_SERVICE.service_name)
95
+ if return_code != kerberos.AUTH_GSS_COMPLETE:
96
+ return _KerberosAuth(return_code=None)
97
+
98
+ if (return_code := kerberos.authGSSServerStep(state, token)) == kerberos.AUTH_GSS_COMPLETE:
99
+ return _KerberosAuth(
100
+ return_code=return_code,
101
+ user=kerberos.authGSSServerUserName(state),
102
+ token=kerberos.authGSSServerResponse(state),
103
+ )
104
+ elif return_code == kerberos.AUTH_GSS_CONTINUE:
105
+ return _KerberosAuth(return_code=return_code)
106
+ return _KerberosAuth(return_code=return_code)
107
+ except kerberos.GSSError:
108
+ return _KerberosAuth(return_code=None)
109
+ finally:
110
+ if state:
111
+ kerberos.authGSSServerClean(state)
112
+
113
+
114
+ T = TypeVar("T", bound=Callable)
115
+
116
+
117
+ def find_user(username=None, email=None):
118
+ security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
119
+ return security_manager.find_user(username=username, email=email)
120
+
121
+
122
+ def requires_authentication(function: T, find_user: Callable[[str], BaseUser] | None = find_user):
123
+ """Decorate functions that require authentication with Kerberos."""
124
+
125
+ @wraps(function)
126
+ def decorated(*args, **kwargs):
127
+ if current_app.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None):
128
+ response = function(*args, **kwargs)
129
+ return make_response(response)
130
+
131
+ header = request.headers.get("Authorization")
132
+ if header:
133
+ token = "".join(header.split()[1:])
134
+ auth = _gssapi_authenticate(token)
135
+ if auth.return_code == kerberos.AUTH_GSS_COMPLETE:
136
+ g.user = find_user(auth.user)
137
+ response = function(*args, **kwargs)
138
+ response = make_response(response)
139
+ if auth.token is not None:
140
+ response.headers["WWW-Authenticate"] = f"negotiate {auth.token}"
141
+ return response
142
+ elif auth.return_code != kerberos.AUTH_GSS_CONTINUE:
143
+ return _forbidden()
144
+ return _unauthorized()
145
+
146
+ return cast(T, decorated)
@@ -0,0 +1,52 @@
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 airflow import settings
20
+ from airflow.cli.commands.db_command import run_db_downgrade_command, run_db_migrate_command
21
+ from airflow.providers.fab.auth_manager.models.db import _REVISION_HEADS_MAP, FABDBManager
22
+ from airflow.utils import cli as cli_utils
23
+ from airflow.utils.providers_configuration_loader import providers_configuration_loaded
24
+
25
+
26
+ @providers_configuration_loaded
27
+ def resetdb(args):
28
+ """Reset the metadata database."""
29
+ print(f"DB: {settings.engine.url!r}")
30
+ if not (args.yes or input("This will drop existing tables if they exist. Proceed? (y/n)").upper() == "Y"):
31
+ raise SystemExit("Cancelled")
32
+ FABDBManager(settings.Session()).resetdb(skip_init=args.skip_init)
33
+
34
+
35
+ @cli_utils.action_cli(check_db=False)
36
+ @providers_configuration_loaded
37
+ def migratedb(args):
38
+ """Migrates the metadata database."""
39
+ session = settings.Session()
40
+ upgrade_command = FABDBManager(session).upgradedb
41
+ run_db_migrate_command(
42
+ args, upgrade_command, revision_heads_map=_REVISION_HEADS_MAP, reserialize_dags=False
43
+ )
44
+
45
+
46
+ @cli_utils.action_cli(check_db=False)
47
+ @providers_configuration_loaded
48
+ def downgrade(args):
49
+ """Downgrades the metadata database."""
50
+ session = settings.Session()
51
+ dwongrade_command = FABDBManager(session).downgrade
52
+ run_db_downgrade_command(args, dwongrade_command, revision_heads_map=_REVISION_HEADS_MAP)
@@ -19,8 +19,17 @@ from __future__ import annotations
19
19
  import textwrap
20
20
 
21
21
  from airflow.cli.cli_config import (
22
+ ARG_DB_FROM_REVISION,
23
+ ARG_DB_FROM_VERSION,
24
+ ARG_DB_REVISION__DOWNGRADE,
25
+ ARG_DB_REVISION__UPGRADE,
26
+ ARG_DB_SKIP_INIT,
27
+ ARG_DB_SQL_ONLY,
28
+ ARG_DB_VERSION__DOWNGRADE,
29
+ ARG_DB_VERSION__UPGRADE,
22
30
  ARG_OUTPUT,
23
31
  ARG_VERBOSE,
32
+ ARG_YES,
24
33
  ActionCommand,
25
34
  Arg,
26
35
  lazy_load_command,
@@ -243,3 +252,55 @@ SYNC_PERM_COMMAND = ActionCommand(
243
252
  func=lazy_load_command("airflow.providers.fab.auth_manager.cli_commands.sync_perm_command.sync_perm"),
244
253
  args=(ARG_INCLUDE_DAGS, ARG_VERBOSE),
245
254
  )
255
+
256
+ DB_COMMANDS = (
257
+ ActionCommand(
258
+ name="migrate",
259
+ help="Migrates the FAB metadata database to the latest version",
260
+ description=(
261
+ "Migrate the schema of the FAB metadata database. "
262
+ "Create the database if it does not exist "
263
+ "To print but not execute commands, use option ``--show-sql-only``. "
264
+ "If using options ``--from-revision`` or ``--from-version``, you must also use "
265
+ "``--show-sql-only``, because if actually *running* migrations, we should only "
266
+ "migrate from the *current* Alembic revision."
267
+ ),
268
+ func=lazy_load_command("airflow.providers.fab.auth_manager.cli_commands.db_command.migratedb"),
269
+ args=(
270
+ ARG_DB_REVISION__UPGRADE,
271
+ ARG_DB_VERSION__UPGRADE,
272
+ ARG_DB_SQL_ONLY,
273
+ ARG_DB_FROM_REVISION,
274
+ ARG_DB_FROM_VERSION,
275
+ ARG_VERBOSE,
276
+ ),
277
+ ),
278
+ ActionCommand(
279
+ name="downgrade",
280
+ help="Downgrade the schema of the FAB metadata database.",
281
+ description=(
282
+ "Downgrade the schema of the FAB metadata database. "
283
+ "You must provide either `--to-revision` or `--to-version`. "
284
+ "To print but not execute commands, use option `--show-sql-only`. "
285
+ "If using options `--from-revision` or `--from-version`, you must also use `--show-sql-only`, "
286
+ "because if actually *running* migrations, we should only migrate from the *current* Alembic "
287
+ "revision."
288
+ ),
289
+ func=lazy_load_command("airflow.providers.fab.auth_manager.cli_commands.db_command.downgrade"),
290
+ args=(
291
+ ARG_DB_REVISION__DOWNGRADE,
292
+ ARG_DB_VERSION__DOWNGRADE,
293
+ ARG_DB_SQL_ONLY,
294
+ ARG_YES,
295
+ ARG_DB_FROM_REVISION,
296
+ ARG_DB_FROM_VERSION,
297
+ ARG_VERBOSE,
298
+ ),
299
+ ),
300
+ ActionCommand(
301
+ name="reset",
302
+ help="Burn down and rebuild the FAB metadata database",
303
+ func=lazy_load_command("airflow.providers.fab.auth_manager.cli_commands.db_command.resetdb"),
304
+ args=(ARG_YES, ARG_DB_SKIP_INIT, ARG_VERBOSE),
305
+ ),
306
+ )
@@ -212,10 +212,12 @@ def users_import(args):
212
212
 
213
213
  users_created, users_updated = _import_users(users_list)
214
214
  if users_created:
215
- print("Created the following users:\n\t{}".format("\n\t".join(users_created)))
215
+ users_created_str = "\n\t".join(users_created)
216
+ print(f"Created the following users:\n\t{users_created_str}")
216
217
 
217
218
  if users_updated:
218
- print("Updated the following users:\n\t{}".format("\n\t".join(users_updated)))
219
+ users_updated_str = "\n\t".join(users_updated)
220
+ print(f"Updated the following users:\n\t{users_updated_str}")
219
221
 
220
222
 
221
223
  def _import_users(users_list: list[dict[str, Any]]):
@@ -231,9 +233,8 @@ def _import_users(users_list: list[dict[str, Any]]):
231
233
  msg.append(f"[Item {row_num}]")
232
234
  for key, value in failure.items():
233
235
  msg.append(f"\t{key}: {value}")
234
- raise SystemExit(
235
- "Error: Input file didn't pass validation. See below:\n{}".format("\n".join(msg))
236
- )
236
+ msg_str = "\n".join(msg)
237
+ raise SystemExit(f"Error: Input file didn't pass validation. See below:\n{msg_str}")
237
238
 
238
239
  for user in users_list:
239
240
  roles = []
@@ -22,11 +22,14 @@ from functools import cached_property
22
22
  from pathlib import Path
23
23
  from typing import TYPE_CHECKING, Container
24
24
 
25
+ import packaging.version
25
26
  from connexion import FlaskApi
26
27
  from flask import Blueprint, url_for
28
+ from packaging.version import Version
27
29
  from sqlalchemy import select
28
30
  from sqlalchemy.orm import Session, joinedload
29
31
 
32
+ from airflow import __version__ as airflow_version
30
33
  from airflow.auth.managers.base_auth_manager import BaseAuthManager, ResourceMethod
31
34
  from airflow.auth.managers.models.resource_details import (
32
35
  AccessView,
@@ -47,6 +50,7 @@ from airflow.configuration import conf
47
50
  from airflow.exceptions import AirflowConfigException, AirflowException
48
51
  from airflow.models import DagModel
49
52
  from airflow.providers.fab.auth_manager.cli_commands.definition import (
53
+ DB_COMMANDS,
50
54
  ROLES_COMMANDS,
51
55
  SYNC_PERM_COMMAND,
52
56
  USERS_COMMANDS,
@@ -81,6 +85,7 @@ from airflow.security.permissions import (
81
85
  )
82
86
  from airflow.utils.session import NEW_SESSION, provide_session
83
87
  from airflow.utils.yaml import safe_load
88
+ from airflow.version import version
84
89
  from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED
85
90
  from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver
86
91
 
@@ -132,7 +137,7 @@ class FabAuthManager(BaseAuthManager):
132
137
  @staticmethod
133
138
  def get_cli_commands() -> list[CLICommand]:
134
139
  """Vends CLI commands to be included in Airflow CLI."""
135
- return [
140
+ commands: list[CLICommand] = [
136
141
  GroupCommand(
137
142
  name="users",
138
143
  help="Manage users",
@@ -145,6 +150,12 @@ class FabAuthManager(BaseAuthManager):
145
150
  ),
146
151
  SYNC_PERM_COMMAND, # not in a command group
147
152
  ]
153
+ # If Airflow version is 3.0.0 or higher, add the fab-db command group
154
+ if packaging.version.parse(
155
+ packaging.version.parse(airflow_version).base_version
156
+ ) >= packaging.version.parse("3.0.0"):
157
+ commands.append(GroupCommand(name="fab-db", help="Manage FAB", subcommands=DB_COMMANDS))
158
+ return commands
148
159
 
149
160
  def get_api_endpoints(self) -> None | Blueprint:
150
161
  folder = Path(__file__).parents[0].resolve() # this is airflow/auth/managers/fab/
@@ -179,7 +190,13 @@ class FabAuthManager(BaseAuthManager):
179
190
 
180
191
  def is_logged_in(self) -> bool:
181
192
  """Return whether the user is logged in."""
182
- return not self.get_user().is_anonymous
193
+ user = self.get_user()
194
+ if Version(Version(version).base_version) < Version("3.0.0"):
195
+ return not user.is_anonymous and user.is_active
196
+ else:
197
+ return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) or (
198
+ not user.is_anonymous and user.is_active
199
+ )
183
200
 
184
201
  def is_authorized_configuration(
185
202
  self,
@@ -364,10 +381,15 @@ class FabAuthManager(BaseAuthManager):
364
381
 
365
382
  def get_url_user_profile(self) -> str | None:
366
383
  """Return the url to a page displaying info about the current user."""
367
- if not self.security_manager.user_view:
384
+ if not self.security_manager.user_view or self.appbuilder.get_app.config.get(
385
+ "AUTH_ROLE_PUBLIC", None
386
+ ):
368
387
  return None
369
388
  return url_for(f"{self.security_manager.user_view.endpoint}.userinfo")
370
389
 
390
+ def register_views(self) -> None:
391
+ self.security_manager.register_views()
392
+
371
393
  def _is_authorized(
372
394
  self,
373
395
  *,
@@ -519,9 +541,11 @@ class FabAuthManager(BaseAuthManager):
519
541
  # Otherwise, when the name of a view or menu is changed, the framework
520
542
  # will add the new Views and Menus names to the backend, but will not
521
543
  # delete the old ones.
522
- if conf.getboolean(
523
- "fab", "UPDATE_FAB_PERMS", fallback=conf.getboolean("webserver", "UPDATE_FAB_PERMS")
524
- ):
544
+ if Version(Version(version).base_version) >= Version("3.0.0"):
545
+ fallback = None
546
+ else:
547
+ fallback = conf.getboolean("webserver", "UPDATE_FAB_PERMS")
548
+ if conf.getboolean("fab", "UPDATE_FAB_PERMS", fallback=fallback):
525
549
  self.security_manager.sync_roles()
526
550
 
527
551