Repository: apache/airflow

Issue:
Unauthenticated access with RBAC to URL has_dag_access results lose redirection
<!--

**Apache Airflow version**: latest

**Environment**: Linux

- **Cloud provider or hardware configuration**:
- **OS** (e.g. from /etc/os-release):
- **Kernel** (e.g. `uname -a`):
- **Install tools**:
- **Others**:

**What happened**:
Unauthenticated access with RBAC to URL has_dag_access results lose redirection

**What you expected to happen**:
Redirection maintained, user can login and redirected to proper page

Missing next URL parameter

**How to reproduce it**:
Setup airflow with RBAC
Have a user who hasn't log in access a URL that uses decorator has_dag_access, i.e:
  http://localhost/graph?dag_id=dag_id
User will be sent to login page, (note the next= argument that required to redirect to original page is gone)
Login properly, user then will be sent to bad page


If this is a UI bug, please provide a screenshot of the bug or a link to a youtube video of the bug in action



Discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!


Resolving patch:
diff --git a/airflow/www/decorators.py b/airflow/www/decorators.py
--- a/airflow/www/decorators.py
+++ b/airflow/www/decorators.py
@@ -116,7 +116,8 @@ def wrapper(self, *args, **kwargs):
                 if self.appbuilder.sm.can_read_dag(dag_id):
                     return f(self, *args, **kwargs)
             flash("Access is Denied", "danger")
-            return redirect(url_for(self.appbuilder.sm.auth_view.__class__.__name__ + ".login"))
+            return redirect(url_for(self.appbuilder.sm.auth_view.__class__.__name__ + ".login",
+                                    next=request.url))
 
         return cast(T, wrapper)
 


Test patch:
diff --git a/tests/www/test_decorators.py b/tests/www/test_decorators.py
new file mode 100644
--- /dev/null
+++ b/tests/www/test_decorators.py
@@ -0,0 +1,47 @@
+# 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 unittest.mock import Mock
+
+from flask_appbuilder import BaseView
+
+from airflow.www import app
+from airflow.www.decorators import has_dag_access
+
+
+class TestDecorators():
+
+    def test_has_dag_access_redirection(self):
+        class DummyView(BaseView):
+
+            def __init__(self, appbuilder):
+                BaseView.__init__(self)
+                self.appbuilder = appbuilder
+
+            @has_dag_access(can_dag_edit=True)
+            def dummy_function(self):
+                pass
+
+        testing_app = app.create_app(testing=True)
+        appbuilder = Mock()
+        appbuilder.sm.can_edit_dag.return_value = False
+        appbuilder.sm.auth_view.__class__.__name__ = 'AuthDBView'
+        view = DummyView(appbuilder)
+
+        with testing_app.test_request_context('/landingpage'):
+            response = view.dummy_function()
+            assert response.headers['Location'] == '/login/?next=http%3A%2F%2Flocalhost%2Flandingpage'