apache-airflow-providers-fab 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. airflow/providers/fab/__init__.py +1 -1
  2. airflow/providers/fab/alembic.ini +133 -0
  3. airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +4 -2
  4. airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +110 -8
  5. airflow/providers/fab/auth_manager/cli_commands/db_command.py +52 -0
  6. airflow/providers/fab/auth_manager/cli_commands/definition.py +61 -0
  7. airflow/providers/fab/auth_manager/cli_commands/user_command.py +6 -5
  8. airflow/providers/fab/auth_manager/fab_auth_manager.py +30 -6
  9. airflow/providers/fab/auth_manager/models/__init__.py +21 -8
  10. airflow/providers/fab/auth_manager/models/anonymous_user.py +7 -1
  11. airflow/providers/fab/auth_manager/models/db.py +107 -0
  12. airflow/providers/fab/auth_manager/security_manager/override.py +5 -3
  13. airflow/providers/fab/get_provider_info.py +4 -1
  14. airflow/providers/fab/migrations/README +1 -0
  15. airflow/providers/fab/migrations/__init__.py +16 -0
  16. airflow/providers/fab/migrations/env.py +125 -0
  17. airflow/providers/fab/migrations/script.py.mako +45 -0
  18. airflow/providers/fab/migrations/versions/0001_1_4_0_placeholder_migration.py +45 -0
  19. airflow/providers/fab/migrations/versions/__init__.py +16 -0
  20. {apache_airflow_providers_fab-1.3.0.dist-info → apache_airflow_providers_fab-1.4.0.dist-info}/METADATA +8 -6
  21. {apache_airflow_providers_fab-1.3.0.dist-info → apache_airflow_providers_fab-1.4.0.dist-info}/RECORD +23 -14
  22. {apache_airflow_providers_fab-1.3.0.dist-info → apache_airflow_providers_fab-1.4.0.dist-info}/WHEEL +0 -0
  23. {apache_airflow_providers_fab-1.3.0.dist-info → apache_airflow_providers_fab-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -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"})
@@ -18,27 +18,129 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import logging
21
- from functools import partial
22
- from typing import Any, cast
21
+ import os
22
+ from functools import wraps
23
+ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, TypeVar, cast
23
24
 
25
+ import kerberos
26
+ from flask import Response, current_app, g, make_response, request
24
27
  from requests_kerberos import HTTPKerberosAuth
25
28
 
26
- from airflow.api.auth.backend.kerberos_auth import (
27
- init_app as base_init_app,
28
- requires_authentication as base_requires_authentication,
29
- )
29
+ from airflow.configuration import conf
30
30
  from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
31
+ from airflow.utils.net import getfqdn
31
32
  from airflow.www.extensions.init_auth_manager import get_auth_manager
32
33
 
34
+ if TYPE_CHECKING:
35
+ from airflow.auth.managers.models.base_user import BaseUser
36
+
33
37
  log = logging.getLogger(__name__)
34
38
 
35
39
  CLIENT_AUTH: tuple[str, str] | Any | None = HTTPKerberosAuth(service="airflow")
36
40
 
37
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
+
38
117
  def find_user(username=None, email=None):
39
118
  security_manager = cast(FabAirflowSecurityManagerOverride, get_auth_manager().security_manager)
40
119
  return security_manager.find_user(username=username, email=email)
41
120
 
42
121
 
43
- init_app = base_init_app
44
- requires_authentication = partial(base_requires_authentication, find_user=find_user)
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
 
@@ -23,6 +23,7 @@ import datetime
23
23
  # Copyright 2013, Daniel Vaz Gaspar
24
24
  from typing import TYPE_CHECKING
25
25
 
26
+ import packaging.version
26
27
  from flask import current_app, g
27
28
  from flask_appbuilder.models.sqla import Model
28
29
  from sqlalchemy import (
@@ -32,6 +33,7 @@ from sqlalchemy import (
32
33
  ForeignKey,
33
34
  Index,
34
35
  Integer,
36
+ MetaData,
35
37
  String,
36
38
  Table,
37
39
  UniqueConstraint,
@@ -39,16 +41,11 @@ from sqlalchemy import (
39
41
  func,
40
42
  select,
41
43
  )
42
- from sqlalchemy.orm import backref, declared_attr, relationship
44
+ from sqlalchemy.orm import backref, declared_attr, registry, relationship
43
45
 
46
+ from airflow import __version__ as airflow_version
44
47
  from airflow.auth.managers.models.base_user import BaseUser
45
- from airflow.models.base import Base
46
-
47
- """
48
- Compatibility note: The models in this file are duplicated from Flask AppBuilder.
49
- """
50
- # Use airflow metadata to create the tables
51
- Model.metadata = Base.metadata
48
+ from airflow.models.base import _get_schema, naming_convention
52
49
 
53
50
  if TYPE_CHECKING:
54
51
  try:
@@ -56,6 +53,22 @@ if TYPE_CHECKING:
56
53
  except Exception:
57
54
  Identity = None
58
55
 
56
+ """
57
+ Compatibility note: The models in this file are duplicated from Flask AppBuilder.
58
+ """
59
+
60
+ metadata = MetaData(schema=_get_schema(), naming_convention=naming_convention)
61
+ mapper_registry = registry(metadata=metadata)
62
+
63
+ if packaging.version.parse(packaging.version.parse(airflow_version).base_version) >= packaging.version.parse(
64
+ "3.0.0"
65
+ ):
66
+ Model.metadata = metadata
67
+ else:
68
+ from airflow.models.base import Base
69
+
70
+ Model.metadata = Base.metadata
71
+
59
72
 
60
73
  class Action(Model):
61
74
  """Represents permission actions such as `can_read`."""
@@ -29,10 +29,13 @@ class AnonymousUser(AnonymousUserMixin, BaseUser):
29
29
  _roles: set[tuple[str, str]] = set()
30
30
  _perms: set[tuple[str, str]] = set()
31
31
 
32
+ first_name = "Anonymous"
33
+ last_name = ""
34
+
32
35
  @property
33
36
  def roles(self):
34
37
  if not self._roles:
35
- public_role = current_app.appbuilder.get_app.config["AUTH_ROLE_PUBLIC"]
38
+ public_role = current_app.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
36
39
  self._roles = {current_app.appbuilder.sm.find_role(public_role)} if public_role else set()
37
40
  return self._roles
38
41
 
@@ -48,3 +51,6 @@ class AnonymousUser(AnonymousUserMixin, BaseUser):
48
51
  (perm.action.name, perm.resource.name) for role in self.roles for perm in role.permissions
49
52
  }
50
53
  return self._perms
54
+
55
+ def get_name(self) -> str:
56
+ return "Anonymous"
@@ -0,0 +1,107 @@
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
+ import os
20
+
21
+ import airflow
22
+ from airflow import settings
23
+ from airflow.exceptions import AirflowException
24
+ from airflow.providers.fab.auth_manager.models import metadata
25
+ from airflow.utils.db import _offline_migration, print_happy_cat
26
+ from airflow.utils.db_manager import BaseDBManager
27
+
28
+ PACKAGE_DIR = os.path.dirname(airflow.__file__)
29
+
30
+ _REVISION_HEADS_MAP: dict[str, str] = {
31
+ "1.4.0": "6709f7a774b9",
32
+ }
33
+
34
+
35
+ class FABDBManager(BaseDBManager):
36
+ """Manages FAB database."""
37
+
38
+ metadata = metadata
39
+ version_table_name = "alembic_version_fab"
40
+ migration_dir = os.path.join(PACKAGE_DIR, "providers/fab/migrations")
41
+ alembic_file = os.path.join(PACKAGE_DIR, "providers/fab/alembic.ini")
42
+ supports_table_dropping = True
43
+
44
+ def upgradedb(self, to_revision=None, from_revision=None, show_sql_only=False):
45
+ """Upgrade the database."""
46
+ if from_revision and not show_sql_only:
47
+ raise AirflowException("`from_revision` only supported with `sql_only=True`.")
48
+
49
+ # alembic adds significant import time, so we import it lazily
50
+ if not settings.SQL_ALCHEMY_CONN:
51
+ raise RuntimeError("The settings.SQL_ALCHEMY_CONN not set. This is a critical assertion.")
52
+ from alembic import command
53
+
54
+ config = self.get_alembic_config()
55
+
56
+ if show_sql_only:
57
+ if settings.engine.dialect.name == "sqlite":
58
+ raise SystemExit("Offline migration not supported for SQLite.")
59
+ if not from_revision:
60
+ from_revision = self.get_current_revision()
61
+
62
+ if not to_revision:
63
+ script = self.get_script_object(config)
64
+ to_revision = script.get_current_head()
65
+
66
+ if to_revision == from_revision:
67
+ print_happy_cat("No migrations to apply; nothing to do.")
68
+ return
69
+ _offline_migration(command.upgrade, config, f"{from_revision}:{to_revision}")
70
+ return # only running sql; our job is done
71
+
72
+ if not self.get_current_revision():
73
+ # New DB; initialize and exit
74
+ self.initdb()
75
+ return
76
+
77
+ command.upgrade(config, revision=to_revision or "heads")
78
+
79
+ def downgrade(self, to_revision, from_revision=None, show_sql_only=False):
80
+ if from_revision and not show_sql_only:
81
+ raise ValueError(
82
+ "`from_revision` can't be combined with `show_sql_only=False`. When actually "
83
+ "applying a downgrade (instead of just generating sql), we always "
84
+ "downgrade from current revision."
85
+ )
86
+
87
+ if not settings.SQL_ALCHEMY_CONN:
88
+ raise RuntimeError("The settings.SQL_ALCHEMY_CONN not set.")
89
+
90
+ # alembic adds significant import time, so we import it lazily
91
+ from alembic import command
92
+
93
+ self.log.info("Attempting downgrade of FAB migration to revision %s", to_revision)
94
+ config = self.get_alembic_config()
95
+
96
+ if show_sql_only:
97
+ self.log.warning("Generating sql scripts for manual migration.")
98
+ if not from_revision:
99
+ from_revision = self.get_current_revision()
100
+ if from_revision is None:
101
+ self.log.info("No revision found")
102
+ return
103
+ revision_range = f"{from_revision}:{to_revision}"
104
+ _offline_migration(command.downgrade, config=config, revision=revision_range)
105
+ else:
106
+ self.log.info("Applying FAB downgrade migrations.")
107
+ command.downgrade(config, revision=to_revision, sql=show_sql_only)
@@ -609,7 +609,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
609
609
  @property
610
610
  def auth_role_public(self):
611
611
  """Get the public role."""
612
- return self.appbuilder.get_app.config["AUTH_ROLE_PUBLIC"]
612
+ return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None)
613
613
 
614
614
  @property
615
615
  def oauth_providers(self):
@@ -832,7 +832,6 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
832
832
  app = self.appbuilder.get_app
833
833
  # Base Security Config
834
834
  app.config.setdefault("AUTH_ROLE_ADMIN", "Admin")
835
- app.config.setdefault("AUTH_ROLE_PUBLIC", "Public")
836
835
  app.config.setdefault("AUTH_TYPE", AUTH_DB)
837
836
  # Self Registration
838
837
  app.config.setdefault("AUTH_USER_REGISTRATION", False)
@@ -955,7 +954,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
955
954
  self.add_role(role_name)
956
955
  if self.auth_role_admin not in self._builtin_roles:
957
956
  self.add_role(self.auth_role_admin)
958
- self.add_role(self.auth_role_public)
957
+ if self.auth_role_public:
958
+ self.add_role(self.auth_role_public)
959
959
  if self.count_users() == 0 and self.auth_role_public != self.auth_role_admin:
960
960
  log.warning(const.LOGMSG_WAR_SEC_NO_USER)
961
961
  except Exception:
@@ -1073,6 +1073,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1073
1073
  dags = dagbag.dags.values()
1074
1074
 
1075
1075
  for dag in dags:
1076
+ # TODO: Remove this when the minimum version of Airflow is bumped to 3.0
1076
1077
  root_dag_id = (getattr(dag, "parent_dag", None) or dag).dag_id
1077
1078
  for resource_name, resource_values in self.RESOURCE_DETAILS_MAP.items():
1078
1079
  dag_resource_name = self._resource_name(root_dag_id, resource_name)
@@ -2828,6 +2829,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
2828
2829
  ).all()
2829
2830
 
2830
2831
  def _get_root_dag_id(self, dag_id: str) -> str:
2832
+ # TODO: The "root_dag_id" check can be remove when the minimum version of Airflow is bumped to 3.0
2831
2833
  if "." in dag_id and hasattr(DagModel, "root_dag_id"):
2832
2834
  dm = self.appbuilder.get_session.execute(
2833
2835
  select(DagModel.dag_id, DagModel.root_dag_id).where(DagModel.dag_id == dag_id)
@@ -28,8 +28,9 @@ def get_provider_info():
28
28
  "name": "Fab",
29
29
  "description": "`Flask App Builder <https://flask-appbuilder.readthedocs.io/>`__\n",
30
30
  "state": "ready",
31
- "source-date-epoch": 1723970140,
31
+ "source-date-epoch": 1726860772,
32
32
  "versions": [
33
+ "1.4.0",
33
34
  "1.3.0",
34
35
  "1.2.2",
35
36
  "1.2.1",
@@ -50,6 +51,8 @@ def get_provider_info():
50
51
  "google-re2>=1.0",
51
52
  "jmespath>=0.7.0",
52
53
  ],
54
+ "additional-extras": [{"name": "kerberos", "dependencies": ["kerberos>=1.3.0"]}],
55
+ "devel-dependencies": ["kerberos>=1.3.0"],
53
56
  "config": {
54
57
  "fab": {
55
58
  "description": "This section contains configs specific to FAB provider.",
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,16 @@
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.
@@ -0,0 +1,125 @@
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
+ import contextlib
20
+ from logging.config import fileConfig
21
+
22
+ from alembic import context
23
+
24
+ from airflow import settings
25
+ from airflow.providers.fab.auth_manager.models.db import FABDBManager
26
+
27
+ # this is the Alembic Config object, which provides
28
+ # access to the values within the .ini file in use.
29
+ config = context.config
30
+
31
+ version_table = FABDBManager.version_table_name
32
+
33
+ # Interpret the config file for Python logging.
34
+ # This line sets up loggers basically.
35
+ if config.config_file_name is not None:
36
+ fileConfig(config.config_file_name, disable_existing_loggers=False)
37
+
38
+ # add your model's MetaData object here
39
+ # for 'autogenerate' support
40
+ # from myapp import mymodel
41
+ # target_metadata = mymodel.Base.metadata
42
+ target_metadata = FABDBManager.metadata
43
+
44
+ # other values from the config, defined by the needs of env.py,
45
+ # can be acquired:
46
+ # my_important_option = config.get_main_option("my_important_option")
47
+ # ... etc.
48
+
49
+
50
+ def include_object(_, name, type_, *args):
51
+ if type_ == "table" and name not in target_metadata.tables:
52
+ return False
53
+ return True
54
+
55
+
56
+ def run_migrations_offline():
57
+ """
58
+ Run migrations in 'offline' mode.
59
+
60
+ This configures the context with just a URL
61
+ and not an Engine, though an Engine is acceptable
62
+ here as well. By skipping the Engine creation
63
+ we don't even need a DBAPI to be available.
64
+
65
+ Calls to context.execute() here emit the given string to the
66
+ script output.
67
+
68
+ """
69
+ context.configure(
70
+ url=settings.SQL_ALCHEMY_CONN,
71
+ target_metadata=target_metadata,
72
+ literal_binds=True,
73
+ compare_type=True,
74
+ compare_server_default=True,
75
+ render_as_batch=True,
76
+ version_table=version_table,
77
+ include_object=include_object,
78
+ )
79
+
80
+ with context.begin_transaction():
81
+ context.run_migrations()
82
+
83
+
84
+ def run_migrations_online():
85
+ """
86
+ Run migrations in 'online' mode.
87
+
88
+ In this scenario we need to create an Engine
89
+ and associate a connection with the context.
90
+
91
+ """
92
+
93
+ def process_revision_directives(context, revision, directives):
94
+ if getattr(config.cmd_opts, "autogenerate", False):
95
+ script = directives[0]
96
+ if script.upgrade_ops and script.upgrade_ops.is_empty():
97
+ directives[:] = []
98
+ print("No change detected in ORM schema, skipping revision.")
99
+
100
+ with contextlib.ExitStack() as stack:
101
+ connection = config.attributes.get("connection", None)
102
+
103
+ if not connection:
104
+ connection = stack.push(settings.engine.connect())
105
+
106
+ context.configure(
107
+ connection=connection,
108
+ transaction_per_migration=True,
109
+ target_metadata=target_metadata,
110
+ compare_type=True,
111
+ compare_server_default=True,
112
+ include_object=include_object,
113
+ render_as_batch=True,
114
+ process_revision_directives=process_revision_directives,
115
+ version_table=version_table,
116
+ )
117
+
118
+ with context.begin_transaction():
119
+ context.run_migrations()
120
+
121
+
122
+ if context.is_offline_mode():
123
+ run_migrations_offline()
124
+ else:
125
+ run_migrations_online()
@@ -0,0 +1,45 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+ """${message}
20
+
21
+ Revision ID: ${up_revision}
22
+ Revises: ${down_revision | comma,n}
23
+ Create Date: ${create_date}
24
+
25
+ """
26
+ from typing import Sequence, Union
27
+
28
+ from alembic import op
29
+ import sqlalchemy as sa
30
+ ${imports if imports else ""}
31
+
32
+ # revision identifiers, used by Alembic.
33
+ revision = ${repr(up_revision)}
34
+ down_revision = ${repr(down_revision)}
35
+ branch_labels = ${repr(branch_labels)}
36
+ depends_on = ${repr(depends_on)}
37
+ fab_version = None
38
+
39
+
40
+ def upgrade() -> None:
41
+ ${upgrades if upgrades else "pass"}
42
+
43
+
44
+ def downgrade() -> None:
45
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,45 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+ """
20
+ placeholder migration.
21
+
22
+ Revision ID: 6709f7a774b9
23
+ Revises:
24
+ Create Date: 2024-09-03 17:06:38.040510
25
+
26
+ Note: This is a placeholder migration used to stamp the migration
27
+ when we create the migration from the ORM. Otherwise, it will run
28
+ without stamping the migration, leading to subsequent changes to
29
+ the tables not being migrated.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ # revision identifiers, used by Alembic.
35
+ revision = "6709f7a774b9"
36
+ down_revision = None
37
+ branch_labels = None
38
+ depends_on = None
39
+ fab_version = "1.4.0"
40
+
41
+
42
+ def upgrade() -> None: ...
43
+
44
+
45
+ def downgrade() -> None: ...
@@ -0,0 +1,16 @@
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apache-airflow-providers-fab
3
- Version: 1.3.0
3
+ Version: 1.4.0
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``
84
+ Release: ``1.4.0``
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>`_.
@@ -1,38 +1,47 @@
1
1
  airflow/providers/fab/LICENSE,sha256=FFb4jd2AXnOOf7XLP04pQW6jbdhG49TxlGY6fFpCV1Y,13609
2
- airflow/providers/fab/__init__.py,sha256=kO58YtLm_XBzc8nZAjDWfM7_KtjjflLtkHjHaLfhCZ4,1490
3
- airflow/providers/fab/get_provider_info.py,sha256=ZaurwvXWOiDEyNg7_pCXctDAzEFYYEnPxA34Lf1emfQ,3189
2
+ airflow/providers/fab/__init__.py,sha256=cDsr9zaomdLZDM0OHErGAqqk62gOk00enaPd7Wggbzs,1490
3
+ airflow/providers/fab/alembic.ini,sha256=_1SvObfjMAkuD7DN5VR2S6Rd7_F81pORZT-w7GJldIA,4461
4
+ airflow/providers/fab/get_provider_info.py,sha256=pmrrrCTCFbUghKT42uXbxRWK5ZKnEaYEMRlleaRSHEg,3351
4
5
  airflow/providers/fab/auth_manager/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
5
- airflow/providers/fab/auth_manager/fab_auth_manager.py,sha256=lzUhvexJGkMHrNrTl7RMM-69KgrAvUEmkrvaNxDZnVQ,20830
6
+ airflow/providers/fab/auth_manager/fab_auth_manager.py,sha256=RQzFmULrGVre0GHr6MiVAL2_YqzKTf45LoLHpVjUkMY,21949
6
7
  airflow/providers/fab/auth_manager/api/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
7
8
  airflow/providers/fab/auth_manager/api/auth/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
8
9
  airflow/providers/fab/auth_manager/api/auth/backend/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
9
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py,sha256=UBGP5UHV6kNsChvL9rkYOQWHszEJtE4AkSFagcRsqrs,2502
10
- airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py,sha256=SvuJ9jo06ZfB_40FUKaiHb8cw2d3Jpxm-YYSOWEzA7I,1712
10
+ airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py,sha256=qj9dIyfNXPoZQUUv9JcxgfNK5swS4aTnPcRa0wcE9Gg,2608
11
+ airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py,sha256=KAFpis1g8xLDqZsBIzcLGXcGJSpZ_2DTgByHB9uEwqQ,5068
11
12
  airflow/providers/fab/auth_manager/api_endpoints/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
12
13
  airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py,sha256=jr-nna1-QLwfquCR0FJcETlQOMC-L1Mhknf2-bvqITw,7552
13
14
  airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py,sha256=TPT4C02jjOXAWI-lOwnaDeS-EEWzJCjQuFVc2E4JsPw,8484
14
15
  airflow/providers/fab/auth_manager/cli_commands/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
15
- airflow/providers/fab/auth_manager/cli_commands/definition.py,sha256=hjcb3IL-G1s120UcdOZcV5ssgC7uuTfTmHECGlmr7bk,9075
16
+ airflow/providers/fab/auth_manager/cli_commands/db_command.py,sha256=h0yRnHMKs1Dndujt5FKyWOv3yEZnX0L41rUadbyuP98,2189
17
+ airflow/providers/fab/auth_manager/cli_commands/definition.py,sha256=XIAPqoHwkFHshfbj6yGIs7xUrR-uSE1F7V71n44PttA,11483
16
18
  airflow/providers/fab/auth_manager/cli_commands/role_command.py,sha256=4w1tHTR5gBbsymeqGIJ4Rs8CmGdw0l49y58pfI0DB_s,9098
17
19
  airflow/providers/fab/auth_manager/cli_commands/sync_perm_command.py,sha256=VpW-rWhgHAL_ReU66D_BrsxlXQN4okfxzj6dyE5IfwA,1663
18
- airflow/providers/fab/auth_manager/cli_commands/user_command.py,sha256=qaYuBbIvF_P0C_SIdQuEFS-hcjRkyn74Gl9Jt0hh2Gs,10207
20
+ airflow/providers/fab/auth_manager/cli_commands/user_command.py,sha256=DMsGccMSs2u0cn1dQgRYkJoG37JhEE-m9dtn-sWEOXw,10275
19
21
  airflow/providers/fab/auth_manager/cli_commands/utils.py,sha256=yr4CMaP5Y6-0mRwsPl62aPEoiys8ImyOlO0e9CLyrGY,2043
20
22
  airflow/providers/fab/auth_manager/decorators/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
21
23
  airflow/providers/fab/auth_manager/decorators/auth.py,sha256=Z4_xiS1N1E0MqDP9OxHx_YJvKe0sH15afP0tn5LP3Ss,4948
22
- airflow/providers/fab/auth_manager/models/__init__.py,sha256=IEGXoz1HaCHxoH8DzI5DIoYHBKlrN1wjru7Ey_YhtkA,8827
23
- airflow/providers/fab/auth_manager/models/anonymous_user.py,sha256=ki1jM-SdxSEJzXhrsiulmziWHlKkU2LguQkXMaRmykE,1775
24
+ airflow/providers/fab/auth_manager/models/__init__.py,sha256=KZl07tphFlZ8kTnsV8k7eTMilaWtd6rV-RjtWOkb13E,9243
25
+ airflow/providers/fab/auth_manager/models/anonymous_user.py,sha256=CNwZHKyURxkTSkqhnyGioML9qBsngDP0wmfdFe04w8E,1893
26
+ airflow/providers/fab/auth_manager/models/db.py,sha256=lidEkcdFZ7jR-Sva0z3JLhfba0X3k4cOPmQM5wyzke4,4375
24
27
  airflow/providers/fab/auth_manager/openapi/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
25
28
  airflow/providers/fab/auth_manager/openapi/v1.yaml,sha256=xFlQMccLoGarx8SnQvGJ1DRfHo4jQ3p9DzoPqQINcCw,19380
26
29
  airflow/providers/fab/auth_manager/security_manager/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
27
30
  airflow/providers/fab/auth_manager/security_manager/constants.py,sha256=x1Sjl_Mu3wmaSy3NFZlHxK2z-juzWmMs1SrzJ0aiBBQ,907
28
- airflow/providers/fab/auth_manager/security_manager/override.py,sha256=TiZGNdAwpGe3GlcOEG2P0AI_EwBQFs1Hwtant0pKTVI,115241
31
+ airflow/providers/fab/auth_manager/security_manager/override.py,sha256=uzHY9zftcSdJXozsgKdfR9KK7HIK_RYQa7Ad1nP7IMY,115425
29
32
  airflow/providers/fab/auth_manager/views/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
30
33
  airflow/providers/fab/auth_manager/views/permissions.py,sha256=k5APUTqqKZqMxCWlKrUEgqdDVrGa9719HJ3UmFpiSLc,2886
31
34
  airflow/providers/fab/auth_manager/views/roles_list.py,sha256=o_VqnLbQa4sisKxLwyx6VCl3HmvjKcjkrJG23R81FMQ,1494
32
35
  airflow/providers/fab/auth_manager/views/user.py,sha256=KaBWeBMy5j8o9qwe0RDHhtToO4aC6pH_m7N9IcIj1xM,5995
33
36
  airflow/providers/fab/auth_manager/views/user_edit.py,sha256=d8wQ_8Rk8Ce95sCElZIXN7ATwbXrT2Tauqn5uTwuo2E,2140
34
37
  airflow/providers/fab/auth_manager/views/user_stats.py,sha256=zP2eX6e40rpEohJcvnvri4Khu3Q4ULLxjz1zOWvOr8A,1300
35
- apache_airflow_providers_fab-1.3.0.dist-info/entry_points.txt,sha256=m05kASp7vFi0ZmQ--CFp7GeJpPL7UT2RQF8EEP5XRX8,99
36
- apache_airflow_providers_fab-1.3.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
37
- apache_airflow_providers_fab-1.3.0.dist-info/METADATA,sha256=dnZF6-n5lZ054WHgGNObusWJiqjaiJYv1NuhZY7G_Hc,5012
38
- apache_airflow_providers_fab-1.3.0.dist-info/RECORD,,
38
+ airflow/providers/fab/migrations/README,sha256=heMzebYwlGhnE8_4CWJ4LS74WoEZjBy-S-mIJRxAEKI,39
39
+ airflow/providers/fab/migrations/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
40
+ airflow/providers/fab/migrations/env.py,sha256=brxmsuAiSRSwzG14Butue1chSJSXaLW53-ABihPLmKw,3951
41
+ airflow/providers/fab/migrations/script.py.mako,sha256=_krfPtzv1Unme2b9MCHPgXo0g73HcN2_zDgz3co2N4M,1353
42
+ airflow/providers/fab/migrations/versions/0001_1_4_0_placeholder_migration.py,sha256=wLZiBiWjfSofXZ4jXMjpEKkIc3jI0a6hpJ73xEYAzss,1370
43
+ airflow/providers/fab/migrations/versions/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
44
+ apache_airflow_providers_fab-1.4.0.dist-info/entry_points.txt,sha256=m05kASp7vFi0ZmQ--CFp7GeJpPL7UT2RQF8EEP5XRX8,99
45
+ apache_airflow_providers_fab-1.4.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
46
+ apache_airflow_providers_fab-1.4.0.dist-info/METADATA,sha256=xWTU1bQqt64yxjszpyj-br5r5Yh3fRIslwg7xotipVo,5090
47
+ apache_airflow_providers_fab-1.4.0.dist-info/RECORD,,