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.

Reply via email to