apache-airflow-providers-fab 2.4.1rc1__py3-none-any.whl → 2.4.2rc1__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/fab_auth_manager.py +45 -40
- airflow/providers/fab/auth_manager/models/__init__.py +2 -2
- airflow/providers/fab/auth_manager/security_manager/override.py +71 -45
- airflow/providers/fab/get_provider_info.py +14 -0
- airflow/providers/fab/migrations/versions/0001_1_4_0_create_ab_tables_if_missing.py +220 -0
- airflow/providers/fab/www/package-lock.json +117 -106
- airflow/providers/fab/www/package.json +8 -8
- airflow/providers/fab/www/security/permissions.py +7 -8
- airflow/providers/fab/www/static/dist/{743.27a753a06671118f1c5c.js → 743.fc7a7c6ef9d09365976e.js} +1 -1
- airflow/providers/fab/www/static/dist/{main.810554d06c3e30f2484e.js → main.3cf3be1a0c5439bb640d.js} +1 -1
- airflow/providers/fab/www/static/dist/manifest.json +13 -13
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.dist-info}/METADATA +7 -8
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.dist-info}/RECORD +31 -31
- airflow/providers/fab/migrations/versions/0001_1_4_0_placeholder_migration.py +0 -45
- /airflow/providers/fab/www/static/dist/{743.27a753a06671118f1c5c.js.LICENSE.txt → 743.fc7a7c6ef9d09365976e.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.56d4475fdae7883d3454.css → airflowDefaultTheme.ff5a35f322070b094aa2.css} +0 -0
- /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.56d4475fdae7883d3454.js → airflowDefaultTheme.ff5a35f322070b094aa2.js} +0 -0
- /airflow/providers/fab/www/static/dist/{flash.0951d47c62bc8906be65.css → flash.5583a9e0cf11f2be93da.css} +0 -0
- /airflow/providers/fab/www/static/dist/{flash.0951d47c62bc8906be65.js → flash.5583a9e0cf11f2be93da.js} +0 -0
- /airflow/providers/fab/www/static/dist/{loadingDots.deaad0ce0e7691ed6251.css → loadingDots.2e5f555f0753107b0300.css} +0 -0
- /airflow/providers/fab/www/static/dist/{loadingDots.deaad0ce0e7691ed6251.js → loadingDots.2e5f555f0753107b0300.js} +0 -0
- /airflow/providers/fab/www/static/dist/{main.810554d06c3e30f2484e.css → main.3cf3be1a0c5439bb640d.css} +0 -0
- /airflow/providers/fab/www/static/dist/{main.810554d06c3e30f2484e.js.LICENSE.txt → main.3cf3be1a0c5439bb640d.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{materialIcons.b0c6cc32cdacff89f7c2.css → materialIcons.3e67dd6fbfcc4f3b5105.css} +0 -0
- /airflow/providers/fab/www/static/dist/{materialIcons.b0c6cc32cdacff89f7c2.js → materialIcons.3e67dd6fbfcc4f3b5105.js} +0 -0
- /airflow/providers/fab/www/static/dist/{moment.518a43bcfaf149ae2836.js → moment.9baee5ec3d7639a10897.js} +0 -0
- /airflow/providers/fab/www/static/dist/{runtime.4a925577de9ab84d8e00.js → runtime.ad800fc1845ad5c6ddeb.js} +0 -0
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
- {apache_airflow_providers_fab-2.4.1rc1.dist-info → apache_airflow_providers_fab-2.4.2rc1.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.4.
|
32
|
+
__version__ = "2.4.2"
|
33
33
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
35
35
|
"3.0.2"
|
@@ -75,6 +75,7 @@ from airflow.providers.fab.www.extensions.init_views import (
|
|
75
75
|
)
|
76
76
|
from airflow.providers.fab.www.security import permissions
|
77
77
|
from airflow.providers.fab.www.security.permissions import (
|
78
|
+
ACTION_CAN_READ,
|
78
79
|
RESOURCE_AUDIT_LOG,
|
79
80
|
RESOURCE_CLUSTER_ACTIVITY,
|
80
81
|
RESOURCE_CONFIG,
|
@@ -82,6 +83,7 @@ from airflow.providers.fab.www.security.permissions import (
|
|
82
83
|
RESOURCE_DAG,
|
83
84
|
RESOURCE_DAG_CODE,
|
84
85
|
RESOURCE_DAG_DEPENDENCIES,
|
86
|
+
RESOURCE_DAG_PREFIX,
|
85
87
|
RESOURCE_DAG_RUN,
|
86
88
|
RESOURCE_DAG_VERSION,
|
87
89
|
RESOURCE_DAG_WARNING,
|
@@ -323,40 +325,52 @@ class FabAuthManager(BaseAuthManager[User]):
|
|
323
325
|
|
324
326
|
There are multiple scenarios:
|
325
327
|
|
326
|
-
1. ``
|
327
|
-
|
328
|
-
|
329
|
-
|
328
|
+
1. ``method`` is "GET" and no details are provided which means the user wants to list Dags (or sub entities of Dags).
|
329
|
+
2. ``access_entity`` is not provided which means the user wants to access the DAG itself and not a sub
|
330
|
+
entity (e.g. Task instances).
|
331
|
+
3. ``access_entity`` is provided which means the user wants to access a sub entity of the DAG
|
332
|
+
(e.g. DAG runs).
|
330
333
|
|
331
334
|
a. If ``method`` is GET, then check the user has READ permissions on the DAG and the sub entity.
|
332
|
-
b. Else, check the user has EDIT permissions on the DAG and ``method`` on the sub entity.
|
333
|
-
if no specific DAG is targeted, just check the sub entity.
|
335
|
+
b. Else, check the user has EDIT permissions on the DAG and ``method`` on the sub entity.
|
334
336
|
|
335
337
|
:param method: The method to authorize.
|
336
338
|
:param user: The user performing the action.
|
337
339
|
:param access_entity: The dag access entity.
|
338
340
|
:param details: The dag details.
|
339
341
|
"""
|
340
|
-
if
|
342
|
+
if access_entity:
|
343
|
+
# If a sub-Dag entity is specified, check whether the user has access to it
|
344
|
+
resource_types = self._get_fab_resource_types(access_entity)
|
345
|
+
access_entity_authorized = all(
|
346
|
+
self._is_authorized(method=method, resource_type=resource_type, user=user)
|
347
|
+
for resource_type in resource_types
|
348
|
+
)
|
349
|
+
if access_entity == DagAccessEntity.RUN and details and details.id:
|
350
|
+
# Check using the deprecated permission prefix "DAG Run" to check whether the user has access to dag runs
|
351
|
+
is_authorized_run = self._is_authorized(
|
352
|
+
method=method,
|
353
|
+
resource_type=permissions.resource_name(details.id, RESOURCE_DAG_RUN),
|
354
|
+
user=user,
|
355
|
+
)
|
356
|
+
if not (is_authorized_run or access_entity_authorized):
|
357
|
+
return False
|
358
|
+
elif not access_entity_authorized:
|
359
|
+
return False
|
360
|
+
|
361
|
+
if method == "GET" and (not details or not details.id):
|
341
362
|
# Scenario 1
|
363
|
+
return self._is_authorized_list_dags(user=user)
|
364
|
+
if not access_entity:
|
365
|
+
# Scenario 2
|
342
366
|
return self._is_authorized_dag(method=method, details=details, user=user)
|
343
|
-
# Scenario
|
344
|
-
resource_types = self._get_fab_resource_types(access_entity)
|
367
|
+
# Scenario 3
|
345
368
|
dag_method: ResourceMethod = "GET" if method == "GET" else "PUT"
|
346
|
-
|
347
369
|
if (details and details.id) and not self._is_authorized_dag(
|
348
370
|
method=dag_method, details=details, user=user
|
349
371
|
):
|
350
372
|
return False
|
351
|
-
|
352
|
-
return all(
|
353
|
-
(
|
354
|
-
self._is_authorized(method=method, resource_type=resource_type, user=user)
|
355
|
-
if resource_type != RESOURCE_DAG_RUN or not hasattr(permissions, "resource_name")
|
356
|
-
else self._is_authorized_dag_run(method=method, details=details, user=user)
|
357
|
-
)
|
358
|
-
for resource_type in resource_types
|
359
|
-
)
|
373
|
+
return True
|
360
374
|
|
361
375
|
def is_authorized_backfill(
|
362
376
|
self,
|
@@ -545,7 +559,7 @@ class FabAuthManager(BaseAuthManager[User]):
|
|
545
559
|
|
546
560
|
:param method: the method to perform
|
547
561
|
:param resource_type: the type of resource the user attempts to perform the action on
|
548
|
-
:param user: the user
|
562
|
+
:param user: the user performing the action
|
549
563
|
|
550
564
|
:meta private:
|
551
565
|
"""
|
@@ -565,7 +579,7 @@ class FabAuthManager(BaseAuthManager[User]):
|
|
565
579
|
|
566
580
|
:param method: the method to perform
|
567
581
|
:param details: details about the DAG
|
568
|
-
:param user: the user
|
582
|
+
:param user: the user performing the action
|
569
583
|
|
570
584
|
:meta private:
|
571
585
|
"""
|
@@ -577,34 +591,25 @@ class FabAuthManager(BaseAuthManager[User]):
|
|
577
591
|
# Check whether the user has permissions to access a specific DAG
|
578
592
|
resource_dag_name = permissions.resource_name(details.id, RESOURCE_DAG)
|
579
593
|
return self._is_authorized(method=method, resource_type=resource_dag_name, user=user)
|
580
|
-
|
581
|
-
return len(authorized_dags) > 0
|
594
|
+
return False
|
582
595
|
|
583
|
-
def
|
584
|
-
self,
|
585
|
-
method: ResourceMethod,
|
586
|
-
details: DagDetails | None,
|
587
|
-
user: User,
|
588
|
-
) -> bool:
|
596
|
+
def _is_authorized_list_dags(self, *, user: User) -> bool:
|
589
597
|
"""
|
590
|
-
Return whether the user is authorized to
|
598
|
+
Return whether the user is authorized to list Dags.
|
591
599
|
|
592
|
-
:param
|
593
|
-
:param details: details about the DAG
|
594
|
-
:param user: the user to performing the action
|
600
|
+
:param user: the user performing the action
|
595
601
|
|
596
602
|
:meta private:
|
597
603
|
"""
|
598
|
-
is_global_authorized = self._is_authorized(method=
|
604
|
+
is_global_authorized = self._is_authorized(method="GET", resource_type=RESOURCE_DAG, user=user)
|
599
605
|
if is_global_authorized:
|
600
606
|
return True
|
601
607
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
return len(authorized_dags) > 0
|
608
|
+
user_permissions = self._get_user_permissions(user)
|
609
|
+
for action, resource in user_permissions:
|
610
|
+
if action == ACTION_CAN_READ and resource.startswith(RESOURCE_DAG_PREFIX):
|
611
|
+
return True
|
612
|
+
return False
|
608
613
|
|
609
614
|
@staticmethod
|
610
615
|
def _get_fab_action(method: ExtendedResourceMethod) -> str:
|
@@ -63,14 +63,13 @@ from flask_jwt_extended import JWTManager
|
|
63
63
|
from flask_login import LoginManager
|
64
64
|
from itsdangerous import want_bytes
|
65
65
|
from markupsafe import Markup, escape
|
66
|
-
from sqlalchemy import func, inspect, or_, select
|
66
|
+
from sqlalchemy import delete, func, inspect, or_, select
|
67
67
|
from sqlalchemy.exc import MultipleResultsFound
|
68
68
|
from sqlalchemy.orm import joinedload
|
69
69
|
from werkzeug.security import check_password_hash, generate_password_hash
|
70
70
|
|
71
71
|
from airflow.configuration import conf
|
72
72
|
from airflow.exceptions import AirflowException
|
73
|
-
from airflow.models import DagBag
|
74
73
|
from airflow.providers.fab.auth_manager.models import (
|
75
74
|
Action,
|
76
75
|
Group,
|
@@ -101,6 +100,7 @@ from airflow.providers.fab.auth_manager.views.user_edit import (
|
|
101
100
|
CustomUserInfoEditView,
|
102
101
|
)
|
103
102
|
from airflow.providers.fab.auth_manager.views.user_stats import CustomUserStatsChartView
|
103
|
+
from airflow.providers.fab.version_compat import AIRFLOW_V_3_1_PLUS
|
104
104
|
from airflow.providers.fab.www.security import permissions
|
105
105
|
from airflow.providers.fab.www.security_manager import AirflowSecurityManagerV2
|
106
106
|
from airflow.providers.fab.www.session import AirflowDatabaseSessionInterface
|
@@ -111,12 +111,29 @@ if TYPE_CHECKING:
|
|
111
111
|
RESOURCE_ASSET,
|
112
112
|
RESOURCE_ASSET_ALIAS,
|
113
113
|
)
|
114
|
+
from airflow.sdk import DAG
|
114
115
|
else:
|
115
116
|
from airflow.providers.common.compat.security.permissions import (
|
116
117
|
RESOURCE_ASSET,
|
117
118
|
RESOURCE_ASSET_ALIAS,
|
118
119
|
)
|
119
120
|
|
121
|
+
if AIRFLOW_V_3_1_PLUS:
|
122
|
+
from airflow.models.dagbag import DBDagBag
|
123
|
+
from airflow.utils.session import create_session
|
124
|
+
|
125
|
+
def _iter_dags() -> Iterable[DAG]:
|
126
|
+
with create_session() as session:
|
127
|
+
yield from DBDagBag().iter_all_latest_version_dags(session=session)
|
128
|
+
else:
|
129
|
+
from airflow.models.dagbag import DagBag
|
130
|
+
|
131
|
+
def _iter_dags() -> Iterable[DAG]:
|
132
|
+
dagbag = DagBag(read_dags_from_db=True) # type: ignore[call-arg]
|
133
|
+
dagbag.collect_dags_from_db() # type: ignore[attr-defined]
|
134
|
+
return dagbag.dags.values()
|
135
|
+
|
136
|
+
|
120
137
|
log = logging.getLogger(__name__)
|
121
138
|
|
122
139
|
# This is the limit of DB user sessions that we consider as "healthy". If you have more sessions that this
|
@@ -548,7 +565,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
548
565
|
interface = self.appbuilder.get_app.session_interface
|
549
566
|
session = interface.db.session
|
550
567
|
user_session_model = interface.sql_session_model
|
551
|
-
num_sessions = session.
|
568
|
+
num_sessions = session.scalars(select(func.count()).select_from(user_session_model)).one()
|
552
569
|
if num_sessions > MAX_NUM_DATABASE_USER_SESSIONS:
|
553
570
|
safe_username = escape(user.username)
|
554
571
|
self._cli_safe_flash(
|
@@ -563,7 +580,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
563
580
|
"warning",
|
564
581
|
)
|
565
582
|
else:
|
566
|
-
for s in session.
|
583
|
+
for s in session.scalars(select(user_session_model)).all():
|
567
584
|
session_details = interface.serializer.loads(want_bytes(s.data))
|
568
585
|
if session_details.get("_user_id") == user.id:
|
569
586
|
session.delete(s)
|
@@ -938,11 +955,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
938
955
|
if you only need to sync a single DAG.
|
939
956
|
"""
|
940
957
|
perms = self.get_all_permissions()
|
941
|
-
dagbag = DagBag(read_dags_from_db=True)
|
942
|
-
dagbag.collect_dags_from_db()
|
943
|
-
dags = dagbag.dags.values()
|
944
958
|
|
945
|
-
for dag in
|
959
|
+
for dag in _iter_dags():
|
960
|
+
print(dag)
|
946
961
|
for resource_name, resource_values in self.RESOURCE_DETAILS_MAP.items():
|
947
962
|
dag_resource_name = permissions.resource_name(dag.dag_id, resource_name)
|
948
963
|
for action_name in resource_values["actions"]:
|
@@ -1194,12 +1209,14 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1194
1209
|
"""FAB leaves faulty permissions that need to be cleaned up."""
|
1195
1210
|
self.log.debug("Cleaning faulty perms")
|
1196
1211
|
sesh = self.appbuilder.get_session
|
1197
|
-
perms = sesh.
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1212
|
+
perms = sesh.scalars(
|
1213
|
+
select(Permission).where(
|
1214
|
+
or_(
|
1215
|
+
Permission.action == None, # noqa: E711
|
1216
|
+
Permission.resource == None, # noqa: E711
|
1217
|
+
)
|
1201
1218
|
)
|
1202
|
-
)
|
1219
|
+
).all()
|
1203
1220
|
# Since FAB doesn't define ON DELETE CASCADE on these tables, we need
|
1204
1221
|
# to delete the _object_ so that SQLA knows to delete the many-to-many
|
1205
1222
|
# relationship object too. :(
|
@@ -1277,10 +1294,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1277
1294
|
|
1278
1295
|
:param name: the role name
|
1279
1296
|
"""
|
1280
|
-
return self.get_session.
|
1297
|
+
return self.get_session.scalars(select(self.role_model).filter_by(name=name)).unique().one_or_none()
|
1281
1298
|
|
1282
1299
|
def get_all_roles(self):
|
1283
|
-
return self.get_session.
|
1300
|
+
return self.get_session.scalars(select(self.role_model)).unique().all()
|
1284
1301
|
|
1285
1302
|
def delete_role(self, role_name: str) -> None:
|
1286
1303
|
"""
|
@@ -1289,10 +1306,10 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1289
1306
|
:param role_name: the name of a role in the ab_role table
|
1290
1307
|
"""
|
1291
1308
|
session = self.get_session
|
1292
|
-
role = session.
|
1309
|
+
role = session.scalars(select(Role).where(Role.name == role_name)).first()
|
1293
1310
|
if role:
|
1294
1311
|
log.info("Deleting role '%s'", role_name)
|
1295
|
-
session.delete(
|
1312
|
+
session.execute(delete(Role).where(Role.name == role_name))
|
1296
1313
|
session.commit()
|
1297
1314
|
else:
|
1298
1315
|
raise AirflowException(f"Role named '{role_name}' does not exist")
|
@@ -1323,7 +1340,11 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1323
1340
|
return _roles
|
1324
1341
|
|
1325
1342
|
def get_public_role(self):
|
1326
|
-
return
|
1343
|
+
return (
|
1344
|
+
self.get_session.scalars(select(self.role_model).filter_by(name=self.auth_role_public))
|
1345
|
+
.unique()
|
1346
|
+
.one_or_none()
|
1347
|
+
)
|
1327
1348
|
|
1328
1349
|
"""
|
1329
1350
|
-----------
|
@@ -1380,7 +1401,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1380
1401
|
|
1381
1402
|
def count_users(self):
|
1382
1403
|
"""Return the number of users in the database."""
|
1383
|
-
return self.get_session.
|
1404
|
+
return self.get_session.scalar(select(func.count(self.user_model.id)))
|
1384
1405
|
|
1385
1406
|
def add_register_user(self, username, first_name, last_name, email, password="", hashed_password=""):
|
1386
1407
|
"""
|
@@ -1412,22 +1433,22 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1412
1433
|
if username:
|
1413
1434
|
try:
|
1414
1435
|
if self.auth_username_ci:
|
1415
|
-
return (
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1436
|
+
return self.get_session.scalars(
|
1437
|
+
select(self.user_model).where(
|
1438
|
+
func.lower(self.user_model.username) == func.lower(username)
|
1439
|
+
)
|
1440
|
+
).one_or_none()
|
1441
|
+
return self.get_session.scalars(
|
1442
|
+
select(self.user_model).where(
|
1443
|
+
func.lower(self.user_model.username) == func.lower(username)
|
1419
1444
|
)
|
1420
|
-
|
1421
|
-
self.get_session.query(self.user_model)
|
1422
|
-
.filter(func.lower(self.user_model.username) == func.lower(username))
|
1423
|
-
.one_or_none()
|
1424
|
-
)
|
1445
|
+
).one_or_none()
|
1425
1446
|
except MultipleResultsFound:
|
1426
1447
|
log.error("Multiple results found for user %s", username)
|
1427
1448
|
return None
|
1428
1449
|
elif email:
|
1429
1450
|
try:
|
1430
|
-
return self.get_session.
|
1451
|
+
return self.get_session.scalars(select(self.user_model).filter_by(email=email)).one_or_none()
|
1431
1452
|
except MultipleResultsFound:
|
1432
1453
|
log.error("Multiple results found for user with email %s", email)
|
1433
1454
|
return None
|
@@ -1459,7 +1480,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1459
1480
|
return False
|
1460
1481
|
|
1461
1482
|
def get_all_users(self):
|
1462
|
-
return self.get_session.
|
1483
|
+
return self.get_session.scalars(select(self.user_model)).all()
|
1463
1484
|
|
1464
1485
|
def update_user_auth_stat(self, user, success=True):
|
1465
1486
|
"""
|
@@ -1499,7 +1520,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1499
1520
|
|
1500
1521
|
:param name: name
|
1501
1522
|
"""
|
1502
|
-
return self.get_session.
|
1523
|
+
return self.get_session.scalars(select(self.action_model).filter_by(name=name)).one_or_none()
|
1503
1524
|
|
1504
1525
|
def create_action(self, name):
|
1505
1526
|
"""
|
@@ -1532,11 +1553,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1532
1553
|
log.warning(const.LOGMSG_WAR_SEC_DEL_PERMISSION, name)
|
1533
1554
|
return False
|
1534
1555
|
try:
|
1535
|
-
perms = (
|
1536
|
-
self.
|
1537
|
-
|
1538
|
-
.all()
|
1539
|
-
)
|
1556
|
+
perms = self.get_session.scalars(
|
1557
|
+
select(self.permission_model).where(self.permission_model.action == action)
|
1558
|
+
).all()
|
1540
1559
|
if perms:
|
1541
1560
|
log.warning(const.LOGMSG_WAR_SEC_DEL_PERM_PVM, action, perms)
|
1542
1561
|
return False
|
@@ -1560,7 +1579,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1560
1579
|
|
1561
1580
|
:param name: Name of resource
|
1562
1581
|
"""
|
1563
|
-
return self.get_session.
|
1582
|
+
return self.get_session.scalars(select(self.resource_model).filter_by(name=name)).one_or_none()
|
1564
1583
|
|
1565
1584
|
def create_resource(self, name) -> Resource | None:
|
1566
1585
|
"""
|
@@ -1602,10 +1621,13 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1602
1621
|
resource = self.get_resource(resource_name)
|
1603
1622
|
if action and resource:
|
1604
1623
|
return (
|
1605
|
-
self.get_session.
|
1606
|
-
|
1624
|
+
self.get_session.scalars(
|
1625
|
+
select(self.permission_model).filter_by(action=action, resource=resource)
|
1626
|
+
)
|
1627
|
+
.unique()
|
1607
1628
|
.one_or_none()
|
1608
1629
|
)
|
1630
|
+
|
1609
1631
|
return None
|
1610
1632
|
|
1611
1633
|
def get_resource_permissions(self, resource: Resource) -> Permission:
|
@@ -1614,7 +1636,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1614
1636
|
|
1615
1637
|
:param resource: Object representing a single resource.
|
1616
1638
|
"""
|
1617
|
-
return self.get_session.
|
1639
|
+
return self.get_session.scalars(
|
1640
|
+
select(self.permission_model).filter_by(resource_id=resource.id)
|
1641
|
+
).all()
|
1618
1642
|
|
1619
1643
|
def create_permission(self, action_name, resource_name) -> Permission | None:
|
1620
1644
|
"""
|
@@ -1661,9 +1685,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1661
1685
|
perm = self.get_permission(action_name, resource_name)
|
1662
1686
|
if not perm:
|
1663
1687
|
return
|
1664
|
-
roles = (
|
1665
|
-
|
1666
|
-
)
|
1688
|
+
roles = self.get_session.scalars(
|
1689
|
+
select(self.role_model).where(self.role_model.permissions.contains(perm))
|
1690
|
+
).first()
|
1667
1691
|
if roles:
|
1668
1692
|
log.warning(const.LOGMSG_WAR_SEC_DEL_PERMVIEW, resource_name, action_name, roles)
|
1669
1693
|
return
|
@@ -1672,7 +1696,9 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
1672
1696
|
self.get_session.delete(perm)
|
1673
1697
|
self.get_session.commit()
|
1674
1698
|
# if no more permission on permission view, delete permission
|
1675
|
-
if not self.get_session.
|
1699
|
+
if not self.get_session.scalars(
|
1700
|
+
select(self.permission_model).filter_by(action=perm.action)
|
1701
|
+
).all():
|
1676
1702
|
self.delete_action(perm.action.name)
|
1677
1703
|
log.info(const.LOGMSG_INF_SEC_DEL_PERMVIEW, action_name, resource_name)
|
1678
1704
|
except Exception as e:
|
@@ -2178,7 +2204,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
|
|
2178
2204
|
def oauth_token_getter():
|
2179
2205
|
"""Get authentication (OAuth) token."""
|
2180
2206
|
token = session.get("oauth")
|
2181
|
-
log.debug("
|
2207
|
+
log.debug("OAuth token retrieved from session.")
|
2182
2208
|
return token
|
2183
2209
|
|
2184
2210
|
@staticmethod
|
@@ -30,6 +30,20 @@ def get_provider_info():
|
|
30
30
|
"fab": {
|
31
31
|
"description": "This section contains configs specific to FAB provider.",
|
32
32
|
"options": {
|
33
|
+
"cookie_secure": {
|
34
|
+
"description": "Cookie with the secure attribute is only sent to the server with an HTTPS connection.\n",
|
35
|
+
"version_added": "2.4.0",
|
36
|
+
"type": "boolean",
|
37
|
+
"example": None,
|
38
|
+
"default": "False",
|
39
|
+
},
|
40
|
+
"cookie_samesite": {
|
41
|
+
"description": "Whether the cookie is restricted to a first-party or same-site context.\n",
|
42
|
+
"version_added": "2.4.0",
|
43
|
+
"type": "string",
|
44
|
+
"example": None,
|
45
|
+
"default": "Lax",
|
46
|
+
},
|
33
47
|
"navbar_color": {
|
34
48
|
"description": "Define the color of navigation bar\n",
|
35
49
|
"version_added": "2.2.0",
|