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.
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/PKG-INFO +8 -6
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/README.rst +3 -3
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/__init__.py +1 -1
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/alembic.ini +133 -0
- {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
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +146 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/cli_commands/db_command.py +52 -0
- {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
- {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
- {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
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/models/__init__.py +21 -8
- {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
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/models/db.py +107 -0
- {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
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/get_provider_info.py +4 -1
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/README +1 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/__init__.py +16 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/env.py +125 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/script.py.mako +45 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/versions/0001_1_4_0_placeholder_migration.py +45 -0
- apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/migrations/versions/__init__.py +16 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/pyproject.toml +7 -3
- apache_airflow_providers_fab-1.3.0rc1/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +0 -44
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/LICENSE +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/__init__.py +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/api/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/decorators/__init__.py +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/decorators/auth.py +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/openapi/__init__.py +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/openapi/v1.yaml +0 -0
- {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
- {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
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/__init__.py +0 -0
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/permissions.py +0 -0
- {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
- {apache_airflow_providers_fab-1.3.0rc1 → apache_airflow_providers_fab-1.4.0rc1}/airflow/providers/fab/auth_manager/views/user.py +0 -0
- {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
- {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
|
+
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.
|
32
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-fab/1.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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)
|
apache_airflow_providers_fab-1.4.0rc1/airflow/providers/fab/auth_manager/cli_commands/db_command.py
ADDED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
235
|
-
|
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
|
-
|
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
|
-
|
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
|
523
|
-
|
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
|
|