apache-airflow-providers-fab 2.2.1rc2__py3-none-any.whl → 2.3.0rc1__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.
- airflow/providers/fab/__init__.py +1 -1
- airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +2 -1
- airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +2 -1
- airflow/providers/fab/auth_manager/api/auth/backend/session.py +2 -1
- airflow/providers/fab/auth_manager/api_fastapi/openapi/v2-fab-auth-manager-generated.yaml +2 -2
- airflow/providers/fab/auth_manager/api_fastapi/services/login.py +17 -8
- airflow/providers/fab/auth_manager/security_manager/override.py +13 -7
- airflow/providers/fab/version_compat.py +35 -0
- airflow/providers/fab/www/api_connexion/parameters.py +2 -2
- airflow/providers/fab/www/api_connexion/security.py +2 -1
- airflow/providers/fab/www/api_connexion/types.py +5 -7
- airflow/providers/fab/www/auth.py +2 -2
- airflow/providers/fab/www/extensions/init_views.py +9 -5
- airflow/providers/fab/www/package-lock.json +174 -130
- airflow/providers/fab/www/package.json +4 -4
- airflow/providers/fab/www/security_manager.py +1 -1
- airflow/providers/fab/www/static/dist/{743.b6629eaae7f541f4158f.js → 743.27a753a06671118f1c5c.js} +2 -2
- airflow/providers/fab/www/static/dist/{airflowDefaultTheme.5a4eda684d2f5f33fa6a.js → airflowDefaultTheme.56d4475fdae7883d3454.js} +1 -1
- airflow/providers/fab/www/static/dist/{flash.9f756672299e9e57a824.js → flash.0951d47c62bc8906be65.js} +1 -1
- airflow/providers/fab/www/static/dist/jquery-ui.min.js +1 -1
- airflow/providers/fab/www/static/dist/{loadingDots.fc35e1feb443bd3a8d80.js → loadingDots.deaad0ce0e7691ed6251.js} +1 -1
- airflow/providers/fab/www/static/dist/main.810554d06c3e30f2484e.js +2 -0
- airflow/providers/fab/www/static/dist/manifest.json +13 -13
- airflow/providers/fab/www/static/dist/materialIcons.b0c6cc32cdacff89f7c2.js +1 -0
- airflow/providers/fab/www/static/dist/moment.518a43bcfaf149ae2836.js +1 -0
- airflow/providers/fab/www/static/dist/runtime.4a925577de9ab84d8e00.js +1 -0
- airflow/providers/fab/www/static/pin_100.png +0 -0
- airflow/providers/fab/www/static/pin_32.png +0 -0
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/METADATA +8 -9
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/RECORD +41 -38
- airflow/providers/fab/www/static/dist/main.26e36c3998f451900260.js +0 -2
- airflow/providers/fab/www/static/dist/materialIcons.e84a12f5be6e8d456d02.js +0 -1
- airflow/providers/fab/www/static/dist/moment.9023635a87b836172067.js +0 -1
- airflow/providers/fab/www/static/dist/runtime.78bb8f6146c25d99b9e2.js +0 -1
- /airflow/providers/fab/www/static/dist/{743.b6629eaae7f541f4158f.js.LICENSE.txt → 743.27a753a06671118f1c5c.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.5a4eda684d2f5f33fa6a.css → airflowDefaultTheme.56d4475fdae7883d3454.css} +0 -0
- /airflow/providers/fab/www/static/dist/{flash.9f756672299e9e57a824.css → flash.0951d47c62bc8906be65.css} +0 -0
- /airflow/providers/fab/www/static/dist/{loadingDots.fc35e1feb443bd3a8d80.css → loadingDots.deaad0ce0e7691ed6251.css} +0 -0
- /airflow/providers/fab/www/static/dist/{main.26e36c3998f451900260.css → main.810554d06c3e30f2484e.css} +0 -0
- /airflow/providers/fab/www/static/dist/{main.26e36c3998f451900260.js.LICENSE.txt → main.810554d06c3e30f2484e.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{materialIcons.e84a12f5be6e8d456d02.css → materialIcons.b0c6cc32cdacff89f7c2.css} +0 -0
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
- {apache_airflow_providers_fab-2.2.1rc2.dist-info → apache_airflow_providers_fab-2.3.0rc1.dist-info}/licenses/NOTICE +0 -0
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
29
29
|
|
30
30
|
__all__ = ["__version__"]
|
31
31
|
|
32
|
-
__version__ = "2.
|
32
|
+
__version__ = "2.3.0"
|
33
33
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
35
35
|
"3.0.2"
|
@@ -18,8 +18,9 @@
|
|
18
18
|
|
19
19
|
from __future__ import annotations
|
20
20
|
|
21
|
+
from collections.abc import Callable
|
21
22
|
from functools import wraps
|
22
|
-
from typing import TYPE_CHECKING, Any,
|
23
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
23
24
|
|
24
25
|
from flask import Response, current_app, request
|
25
26
|
from flask_appbuilder.const import AUTH_LDAP
|
@@ -19,8 +19,9 @@ from __future__ import annotations
|
|
19
19
|
|
20
20
|
import logging
|
21
21
|
import os
|
22
|
+
from collections.abc import Callable
|
22
23
|
from functools import wraps
|
23
|
-
from typing import TYPE_CHECKING, Any,
|
24
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar, cast
|
24
25
|
|
25
26
|
import kerberos
|
26
27
|
from flask import Response, current_app, g, make_response, request
|
@@ -6,7 +6,7 @@ info:
|
|
6
6
|
endpoints to manage users and permissions managed by the FAB auth manager.
|
7
7
|
version: 0.1.0
|
8
8
|
paths:
|
9
|
-
/token:
|
9
|
+
/auth/token:
|
10
10
|
post:
|
11
11
|
tags:
|
12
12
|
- FabAuthManager
|
@@ -44,7 +44,7 @@ paths:
|
|
44
44
|
application/json:
|
45
45
|
schema:
|
46
46
|
$ref: '#/components/schemas/HTTPValidationError'
|
47
|
-
/token/cli:
|
47
|
+
/auth/token/cli:
|
48
48
|
post:
|
49
49
|
tags:
|
50
50
|
- FabAuthManager
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
18
18
|
|
19
19
|
from typing import TYPE_CHECKING, cast
|
20
20
|
|
21
|
+
from flask_appbuilder.const import AUTH_LDAP
|
21
22
|
from starlette import status
|
22
23
|
from starlette.exceptions import HTTPException
|
23
24
|
|
@@ -44,14 +45,22 @@ class FABAuthManagerLogin:
|
|
44
45
|
)
|
45
46
|
|
46
47
|
auth_manager = cast("FabAuthManager", get_auth_manager())
|
47
|
-
user: User =
|
48
|
+
user: User | None = None
|
49
|
+
|
50
|
+
if auth_manager.security_manager.auth_type == AUTH_LDAP:
|
51
|
+
user = auth_manager.security_manager.auth_user_ldap(
|
52
|
+
body.username, body.password, rotate_session_id=False
|
53
|
+
)
|
54
|
+
if user is None:
|
55
|
+
user = auth_manager.security_manager.auth_user_db(
|
56
|
+
body.username, body.password, rotate_session_id=False
|
57
|
+
)
|
58
|
+
|
48
59
|
if not user:
|
49
|
-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid
|
60
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
|
50
61
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
user=user, expiration_time_in_seconds=expiration_time_in_seconds
|
55
|
-
)
|
62
|
+
return LoginResponse(
|
63
|
+
access_token=auth_manager.generate_jwt(
|
64
|
+
user=user, expiration_time_in_seconds=expiration_time_in_seconds
|
56
65
|
)
|
57
|
-
|
66
|
+
)
|
@@ -62,7 +62,7 @@ from flask_babel import lazy_gettext
|
|
62
62
|
from flask_jwt_extended import JWTManager
|
63
63
|
from flask_login import LoginManager
|
64
64
|
from itsdangerous import want_bytes
|
65
|
-
from markupsafe import Markup
|
65
|
+
from markupsafe import Markup, escape
|
66
66
|
from sqlalchemy import func, inspect, or_, select
|
67
67
|
from sqlalchemy.exc import MultipleResultsFound
|
68
68
|
from sqlalchemy.orm import joinedload
|
@@ -547,8 +547,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
547
547
|
user_session_model = interface.sql_session_model
|
548
548
|
num_sessions = session.query(user_session_model).count()
|
549
549
|
if num_sessions > MAX_NUM_DATABASE_USER_SESSIONS:
|
550
|
+
safe_username = escape(user.username)
|
550
551
|
self._cli_safe_flash(
|
551
|
-
f"The old sessions for user {
|
552
|
+
f"The old sessions for user {safe_username} have <b>NOT</b> been deleted!<br>"
|
552
553
|
f"You have a lot ({num_sessions}) of user sessions in the 'SESSIONS' table in "
|
553
554
|
f"your database.<br> "
|
554
555
|
"This indicates that this deployment might have an automated API calls that create "
|
@@ -565,9 +566,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
565
566
|
session.delete(s)
|
566
567
|
session.commit()
|
567
568
|
else:
|
569
|
+
safe_username = escape(user.username)
|
568
570
|
self._cli_safe_flash(
|
569
571
|
"Since you are using `securecookie` session backend mechanism, we cannot prevent "
|
570
|
-
f"some old sessions for user {
|
572
|
+
f"some old sessions for user {safe_username} to be reused.<br> If you want to make sure "
|
571
573
|
"that the user is logged out from all sessions, you should consider using "
|
572
574
|
"`database` session backend mechanism.<br> You can also change the 'secret_key` "
|
573
575
|
"webserver configuration for all your webserver instances and restart the webserver. "
|
@@ -1723,7 +1725,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1723
1725
|
--------------------
|
1724
1726
|
"""
|
1725
1727
|
|
1726
|
-
def auth_user_ldap(self, username, password):
|
1728
|
+
def auth_user_ldap(self, username, password, rotate_session_id=True):
|
1727
1729
|
"""
|
1728
1730
|
Authenticate user with LDAP.
|
1729
1731
|
|
@@ -1890,7 +1892,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1890
1892
|
|
1891
1893
|
# LOGIN SUCCESS (only if user is now registered)
|
1892
1894
|
if user:
|
1893
|
-
|
1895
|
+
if rotate_session_id:
|
1896
|
+
self._rotate_session_id()
|
1894
1897
|
self.update_user_auth_stat(user)
|
1895
1898
|
return user
|
1896
1899
|
return None
|
@@ -1919,7 +1922,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1919
1922
|
return False
|
1920
1923
|
return check_password_hash(user.password, password)
|
1921
1924
|
|
1922
|
-
def auth_user_db(self, username, password):
|
1925
|
+
def auth_user_db(self, username, password, rotate_session_id=True):
|
1923
1926
|
"""
|
1924
1927
|
Authenticate user, auth db style.
|
1925
1928
|
|
@@ -1927,6 +1930,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1927
1930
|
The username or registered email address
|
1928
1931
|
:param password:
|
1929
1932
|
The password, will be tested against hashed password on db
|
1933
|
+
:param rotate_session_id:
|
1934
|
+
Whether to rotate the session ID
|
1930
1935
|
"""
|
1931
1936
|
if username is None or username == "":
|
1932
1937
|
return None
|
@@ -1942,7 +1947,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1942
1947
|
log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
|
1943
1948
|
return None
|
1944
1949
|
if check_password_hash(user.password, password):
|
1945
|
-
|
1950
|
+
if rotate_session_id:
|
1951
|
+
self._rotate_session_id()
|
1946
1952
|
self.update_user_auth_stat(user, True)
|
1947
1953
|
return user
|
1948
1954
|
self.update_user_auth_stat(user, False)
|
@@ -0,0 +1,35 @@
|
|
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
|
+
# NOTE! THIS FILE IS COPIED MANUALLY IN OTHER PROVIDERS DELIBERATELY TO AVOID ADDING UNNECESSARY
|
19
|
+
# DEPENDENCIES BETWEEN PROVIDERS. IF YOU WANT TO ADD CONDITIONAL CODE IN YOUR PROVIDER THAT DEPENDS
|
20
|
+
# ON AIRFLOW VERSION, PLEASE COPY THIS FILE TO THE ROOT PACKAGE OF YOUR PROVIDER AND IMPORT
|
21
|
+
# THOSE CONSTANTS FROM IT RATHER THAN IMPORTING THEM FROM ANOTHER PROVIDER OR TEST CODE
|
22
|
+
#
|
23
|
+
from __future__ import annotations
|
24
|
+
|
25
|
+
|
26
|
+
def get_base_airflow_version_tuple() -> tuple[int, int, int]:
|
27
|
+
from packaging.version import Version
|
28
|
+
|
29
|
+
from airflow import __version__
|
30
|
+
|
31
|
+
airflow_version = Version(__version__)
|
32
|
+
return airflow_version.major, airflow_version.minor, airflow_version.micro
|
33
|
+
|
34
|
+
|
35
|
+
AIRFLOW_V_3_1_PLUS = get_base_airflow_version_tuple() >= (3, 1, 0)
|
@@ -17,9 +17,9 @@
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
19
|
import logging
|
20
|
-
from collections.abc import Container
|
20
|
+
from collections.abc import Callable, Container
|
21
21
|
from functools import wraps
|
22
|
-
from typing import TYPE_CHECKING, Any,
|
22
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
23
23
|
|
24
24
|
from pendulum.parsing import ParserError
|
25
25
|
from sqlalchemy import text
|
@@ -16,8 +16,9 @@
|
|
16
16
|
# under the License.
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
+
from collections.abc import Callable
|
19
20
|
from functools import wraps
|
20
|
-
from typing import TYPE_CHECKING,
|
21
|
+
from typing import TYPE_CHECKING, TypeVar, cast
|
21
22
|
|
22
23
|
from flask import Response, current_app
|
23
24
|
|
@@ -17,14 +17,12 @@
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
19
|
from collections.abc import Mapping, Sequence
|
20
|
-
from typing import Any
|
20
|
+
from typing import Any
|
21
21
|
|
22
22
|
from flask import Response
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Mapping[str, Any], # JSON.
|
28
|
-
]
|
24
|
+
# tuple[object, int] For '(NoContent, 201)'.
|
25
|
+
# Mapping[str, Any] for json
|
26
|
+
APIResponse = Response | tuple[object, int] | Mapping[str, Any]
|
29
27
|
|
30
|
-
UpdateMask =
|
28
|
+
UpdateMask = Sequence[str] | None
|
@@ -18,9 +18,9 @@ from __future__ import annotations
|
|
18
18
|
|
19
19
|
import functools
|
20
20
|
import logging
|
21
|
-
from collections.abc import Sequence
|
21
|
+
from collections.abc import Callable, Sequence
|
22
22
|
from functools import wraps
|
23
|
-
from typing import TYPE_CHECKING,
|
23
|
+
from typing import TYPE_CHECKING, TypeVar, cast
|
24
24
|
|
25
25
|
from flask import flash, redirect, render_template, request, url_for
|
26
26
|
from flask_appbuilder._compat import as_unicode
|
@@ -26,6 +26,7 @@ from connexion.exceptions import BadRequestProblem, ProblemException
|
|
26
26
|
from flask import request
|
27
27
|
|
28
28
|
from airflow.api_fastapi.app import get_auth_manager
|
29
|
+
from airflow.providers.fab.version_compat import AIRFLOW_V_3_1_PLUS
|
29
30
|
from airflow.providers.fab.www.api_connexion.exceptions import common_error_handler
|
30
31
|
|
31
32
|
if TYPE_CHECKING:
|
@@ -120,11 +121,14 @@ def init_plugins(app):
|
|
120
121
|
log.debug("Adding view %s without menu", str(type(view["view"])))
|
121
122
|
appbuilder.add_view_no_menu(view["view"])
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
# Since Airflow 3.1 flask_appbuilder_menu_links are added to the Airflow 3 UI
|
125
|
+
# navbar..
|
126
|
+
if not AIRFLOW_V_3_1_PLUS:
|
127
|
+
for menu_link in sorted(
|
128
|
+
plugins_manager.flask_appbuilder_menu_links, key=lambda x: (x.get("category", ""), x["name"])
|
129
|
+
):
|
130
|
+
log.debug("Adding menu link %s to %s", menu_link["name"], menu_link["href"])
|
131
|
+
appbuilder.add_link(**menu_link)
|
128
132
|
|
129
133
|
for blue_print in plugins_manager.flask_blueprints:
|
130
134
|
log.debug("Adding blueprint %s:%s", blue_print["name"], blue_print["blueprint"].import_name)
|