You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ur...@apache.org on 2021/08/18 11:56:27 UTC
[airflow] branch main updated: Avoid endless redirect loop when
user has no roles (#17613)
This is an automated email from the ASF dual-hosted git repository.
uranusjr 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 6868ca4 Avoid endless redirect loop when user has no roles (#17613)
6868ca4 is described below
commit 6868ca48b29915aae8c131d694ea851cff1717de
Author: Jed Cunningham <66...@users.noreply.github.com>
AuthorDate: Wed Aug 18 05:56:09 2021 -0600
Avoid endless redirect loop when user has no roles (#17613)
---
airflow/www/auth.py | 5 +++-
airflow/www/templates/airflow/no_roles.html | 37 ++++++++++++++++++++++++++
airflow/www/views.py | 14 ++++++++++
tests/www/views/test_views_acl.py | 41 +++++++++++++++++++++++++++++
4 files changed, 96 insertions(+), 1 deletion(-)
diff --git a/airflow/www/auth.py b/airflow/www/auth.py
index 8d42f51..b1218e0 100644
--- a/airflow/www/auth.py
+++ b/airflow/www/auth.py
@@ -18,7 +18,7 @@
from functools import wraps
from typing import Callable, Optional, Sequence, Tuple, TypeVar, cast
-from flask import current_app, flash, redirect, request, url_for
+from flask import current_app, flash, g, redirect, request, url_for
T = TypeVar("T", bound=Callable)
@@ -30,6 +30,9 @@ def has_access(permissions: Optional[Sequence[Tuple[str, str]]] = None) -> Calla
@wraps(func)
def decorated(*args, **kwargs):
appbuilder = current_app.appbuilder
+ if not g.user.is_anonymous and not g.user.roles:
+ return redirect(url_for("Airflow.no_roles"))
+
if appbuilder.sm.check_authorization(permissions, request.args.get('dag_id', None)):
return func(*args, **kwargs)
else:
diff --git a/airflow/www/templates/airflow/no_roles.html b/airflow/www/templates/airflow/no_roles.html
new file mode 100644
index 0000000..20f5eab
--- /dev/null
+++ b/airflow/www/templates/airflow/no_roles.html
@@ -0,0 +1,37 @@
+{#
+ 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.
+#}
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Airflow</title>
+ <link rel="icon" type="image/png" href="{{ url_for('static', filename='pin_32.png') }}">
+</head>
+<body>
+ <div style="font-family: verdana; text-align: center; margin-top: 200px;">
+ <img src="{{ url_for('static', filename='pin_100.png') }}" width="50px" alt="pin-logo" />
+ <h1>Your user has no roles!</h1>
+ <p>Unfortunately your user has no roles, and therefore you cannot use Airflow.</p>
+ <p>Please contact your Airflow administrator
+ (<a href="https://airflow.apache.org/docs/apache-airflow/stable/security/webserver.html#web-authentication">authentication</a>
+ may be misconfigured) or <a href="{{ logout_url }}">log out</a> to try again.</p>
+ <p>{{ hostname }}</p>
+ </div>
+</body>
+</html>
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 64a26a9..c3028b0 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -531,6 +531,20 @@ class Airflow(AirflowBaseView):
return wwwutils.json_response(payload)
+ @expose('/no_roles')
+ def no_roles(self):
+ """Show 'user has no roles' on screen (instead of an endless redirect loop)"""
+ if g.user.is_anonymous or g.user.roles:
+ return redirect(url_for("Airflow.index"))
+
+ return render_template(
+ 'airflow/no_roles.html',
+ hostname=socket.getfqdn()
+ if conf.getboolean('webserver', 'EXPOSE_HOSTNAME', fallback=True)
+ else 'redact',
+ logout_url=current_app.appbuilder.get_url_for_logout,
+ )
+
@expose('/home')
@auth.has_access(
[
diff --git a/tests/www/views/test_views_acl.py b/tests/www/views/test_views_acl.py
index 4dab386..740a70b 100644
--- a/tests/www/views/test_views_acl.py
+++ b/tests/www/views/test_views_acl.py
@@ -759,3 +759,44 @@ def test_get_logs_with_metadata_failure(dag_faker_client):
)
check_content_not_in_response('"message":', resp)
check_content_not_in_response('"metadata":', resp)
+
+
+@pytest.fixture(scope="module")
+def user_no_roles(acl_app):
+ user = create_user(
+ acl_app,
+ username="no_roles_user",
+ role_name="no_roles_user_role",
+ )
+ user.roles = []
+ return user
+
+
+@pytest.fixture()
+def client_no_roles(acl_app, user_no_roles):
+ return client_with_login(
+ acl_app,
+ username="no_roles_user",
+ password="no_roles_user",
+ )
+
+
+@pytest.fixture()
+def client_anonymous(acl_app):
+ return acl_app.test_client()
+
+
+@pytest.mark.parametrize(
+ "client, url, expected_content",
+ [
+ ["client_no_roles", "/home", "Your user has no roles!"],
+ ["client_no_roles", "/no_roles", "Your user has no roles!"],
+ ["client_all_dags", "/home", "DAGs - Airflow"],
+ ["client_all_dags", "/no_roles", "DAGs - Airflow"],
+ ["client_anonymous", "/home", "Sign In"],
+ ["client_anonymous", "/no_roles", "Sign In"],
+ ],
+)
+def test_no_roles_redirect(request, client, url, expected_content):
+ resp = request.getfixturevalue(client).get(url, follow_redirects=True)
+ check_content_in_response(expected_content, resp)