This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 84941f86d7 Add back decorator `has_access` (#34786)
84941f86d7 is described below
commit 84941f86d760b73004ddae6eeedd0c0f717d3f8b
Author: Vincent <[email protected]>
AuthorDate: Thu Oct 12 01:15:50 2023 -0400
Add back decorator `has_access` (#34786)
---------
Co-authored-by: Jens Scheffler
<[email protected]>
---
airflow/auth/managers/fab/decorators/__init__.py | 17 +++++
airflow/auth/managers/fab/decorators/auth.py | 91 ++++++++++++++++++++++++
airflow/www/auth.py | 26 ++++++-
3 files changed, 133 insertions(+), 1 deletion(-)
diff --git a/airflow/auth/managers/fab/decorators/__init__.py
b/airflow/auth/managers/fab/decorators/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/fab/decorators/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow/auth/managers/fab/decorators/auth.py
b/airflow/auth/managers/fab/decorators/auth.py
new file mode 100644
index 0000000000..5f0f161470
--- /dev/null
+++ b/airflow/auth/managers/fab/decorators/auth.py
@@ -0,0 +1,91 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import logging
+from functools import wraps
+from typing import Callable, Sequence, TypeVar, cast
+
+from flask import current_app, render_template, request
+
+from airflow.configuration import conf
+from airflow.utils.net import get_hostname
+from airflow.www.auth import _has_access
+from airflow.www.extensions.init_auth_manager import get_auth_manager
+
+T = TypeVar("T", bound=Callable)
+
+log = logging.getLogger(__name__)
+
+
+def _has_access_fab(permissions: Sequence[tuple[str, str]] | None = None) ->
Callable[[T], T]:
+ """
+ Factory for decorator that checks current user's permissions against
required permissions.
+
+ This decorator is only kept for backward compatible reasons. The decorator
+ ``airflow.www.auth.has_access``, which redirects to this decorator, is
widely used in user plugins.
+ Thus, we need to keep it.
+ See https://github.com/apache/airflow/pull/33213#discussion_r1346287224
+
+ :meta private:
+ """
+
+ def requires_access_decorator(func: T):
+ @wraps(func)
+ def decorated(*args, **kwargs):
+ __tracebackhide__ = True # Hide from pytest traceback.
+
+ appbuilder = current_app.appbuilder
+
+ dag_id_kwargs = kwargs.get("dag_id")
+ dag_id_args = request.args.get("dag_id")
+ dag_id_form = request.form.get("dag_id")
+ dag_id_json = request.json.get("dag_id") if request.is_json else
None
+ all_dag_ids = [dag_id_kwargs, dag_id_args, dag_id_form,
dag_id_json]
+ unique_dag_ids = set(dag_id for dag_id in all_dag_ids if dag_id is
not None)
+
+ if len(unique_dag_ids) > 1:
+ log.warning(
+ f"There are different dag_ids passed in the request:
{unique_dag_ids}. Returning 403."
+ )
+ log.warning(
+ f"kwargs: {dag_id_kwargs}, args: {dag_id_args}, "
+ f"form: {dag_id_form}, json: {dag_id_json}"
+ )
+ return (
+ render_template(
+ "airflow/no_roles_permissions.html",
+ hostname=get_hostname()
+ if conf.getboolean("webserver", "EXPOSE_HOSTNAME")
+ else "redact",
+ logout_url=get_auth_manager().get_url_logout(),
+ ),
+ 403,
+ )
+ dag_id = unique_dag_ids.pop() if unique_dag_ids else None
+
+ return _has_access(
+ is_authorized=appbuilder.sm.check_authorization(permissions,
dag_id),
+ func=func,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ return cast(T, decorated)
+
+ return requires_access_decorator
diff --git a/airflow/www/auth.py b/airflow/www/auth.py
index c943779ab0..93ee8196bd 100644
--- a/airflow/www/auth.py
+++ b/airflow/www/auth.py
@@ -17,8 +17,9 @@
from __future__ import annotations
import logging
+import warnings
from functools import wraps
-from typing import TYPE_CHECKING, Callable, TypeVar, cast
+from typing import TYPE_CHECKING, Callable, Sequence, TypeVar, cast
from flask import flash, g, redirect, render_template, request
@@ -28,6 +29,7 @@ from airflow.auth.managers.models.resource_details import (
DagDetails,
)
from airflow.configuration import conf
+from airflow.exceptions import RemovedInAirflow3Warning
from airflow.utils.net import get_hostname
from airflow.www.extensions.init_auth_manager import get_auth_manager
@@ -44,6 +46,28 @@ def get_access_denied_message():
return conf.get("webserver", "access_denied_message")
+def has_access(permissions: Sequence[tuple[str, str]] | None = None) ->
Callable[[T], T]:
+ """
+ Factory for decorator that checks current user's permissions against
required permissions.
+
+ Deprecated. Do not use this decorator, use one of the decorator
`has_access_*` defined in
+ airflow/www/auth.py instead.
+ This decorator will only work with FAB authentication and not with other
auth providers.
+
+ This decorator is widely used in user plugins, do not remove it. See
+ https://github.com/apache/airflow/pull/33213#discussion_r1346287224
+ """
+ warnings.warn(
+ "The 'has_access' decorator is deprecated. Please use one of the
decorator `has_access_*`"
+ "defined in airflow/www/auth.py instead.",
+ RemovedInAirflow3Warning,
+ stacklevel=2,
+ )
+ from airflow.auth.managers.fab.decorators.auth import _has_access_fab
+
+ return _has_access_fab(permissions)
+
+
def _has_access_no_details(is_authorized_callback: Callable[[], bool]) ->
Callable[[T], T]:
"""
Generic Decorator that checks current user's permissions against required
permissions.