Repository: incubator-airflow
Updated Branches:
  refs/heads/master fa84d4999 -> 0bc248fc7


[AIRFLOW-940] Handle error on variable decrypt

Invalid variables could break the variable view by
unhandled
InvalidToken exception from Fernet.

This commit converts the fernet error into an
AirflowException, given
that fernet is loaded dynamically. Also, the
exception is handled in
the VariableView by showing the token "INVALID" in
the UI render.

Closes #2510 from edgarRd/erod-error-handling-var-
decrypt


Project: http://git-wip-us.apache.org/repos/asf/incubator-airflow/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-airflow/commit/0bc248fc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-airflow/tree/0bc248fc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-airflow/diff/0bc248fc

Branch: refs/heads/master
Commit: 0bc248fc7aa51f5890e550a63fcb28eb427f55a3
Parents: fa84d49
Author: Edgar Rodriguez <edgar.rodrig...@airbnb.com>
Authored: Thu Aug 10 17:26:05 2017 -0700
Committer: Alex Guziel <alex.guz...@airbnb.com>
Committed: Thu Aug 10 17:26:05 2017 -0700

----------------------------------------------------------------------
 airflow/models.py       |  7 +++++-
 airflow/www/views.py    |  5 +++-
 tests/www/test_views.py | 58 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/0bc248fc/airflow/models.py
----------------------------------------------------------------------
diff --git a/airflow/models.py b/airflow/models.py
index 35a09ef..c15491c 100755
--- a/airflow/models.py
+++ b/airflow/models.py
@@ -3831,7 +3831,12 @@ class Variable(Base):
                 raise AirflowException(
                     "Can't decrypt _val for key={}, FERNET_KEY configuration \
                     missing".format(self.key))
-            return fernet.decrypt(bytes(self._val, 'utf-8')).decode()
+            try:
+                return fernet.decrypt(bytes(self._val, 'utf-8')).decode()
+            except:
+                raise AirflowException(
+                    "Can't decrypt _val for key={}, invalid token or value"
+                    .format(self.key))
         else:
             return self._val
 

http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/0bc248fc/airflow/www/views.py
----------------------------------------------------------------------
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 07e0e18..f8dffb9 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -2283,7 +2283,10 @@ class VariableView(wwwutils.DataProfilingMixin, 
AirflowModelView):
     def hidden_field_formatter(view, context, model, name):
         if wwwutils.should_hide_value_for_key(model.key):
             return Markup('*' * 8)
-        return getattr(model, name)
+        try:
+            return getattr(model, name)
+        except AirflowException:
+            return Markup('<span class="label label-danger">Invalid</span>')
 
     form_columns = (
         'key',

http://git-wip-us.apache.org/repos/asf/incubator-airflow/blob/0bc248fc/tests/www/test_views.py
----------------------------------------------------------------------
diff --git a/tests/www/test_views.py b/tests/www/test_views.py
index 5c0eefa..f20dca1 100644
--- a/tests/www/test_views.py
+++ b/tests/www/test_views.py
@@ -82,6 +82,64 @@ class TestChartModelView(unittest.TestCase):
         self.assertIn('Sort by Owner', response.data.decode('utf-8'))
 
 
+class TestVariableView(unittest.TestCase):
+
+    CREATE_ENDPOINT = '/admin/variable/new/?url=/admin/variable/'
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestVariableView, cls).setUpClass()
+        session = Session()
+        session.query(models.Variable).delete()
+        session.commit()
+        session.close()
+
+    def setUp(self):
+        super(TestVariableView, self).setUp()
+        configuration.load_test_config()
+        app = application.create_app(testing=True)
+        app.config['WTF_CSRF_METHODS'] = []
+        self.app = app.test_client()
+        self.session = Session()
+        self.variable = {
+            'key': 'test_key',
+            'val': 'text_val',
+            'is_encrypted': True
+        }
+
+    def tearDown(self):
+        self.session.query(models.Variable).delete()
+        self.session.commit()
+        self.session.close()
+        super(TestVariableView, self).tearDown()
+
+    def test_can_handle_error_on_decrypt(self):
+        # create valid variable
+        response = self.app.post(
+            self.CREATE_ENDPOINT,
+            data=self.variable,
+            follow_redirects=True,
+        )
+        self.assertEqual(response.status_code, 200)
+
+        # update the variable with a wrong value, given that is encrypted
+        Var = models.Variable
+        (self.session.query(Var)
+            .filter(Var.key == self.variable['key'])
+            .update({
+                'val': 'failed_value_not_encrypted'
+            }, synchronize_session=False))
+        self.session.commit()
+
+        # retrieve Variables page, should not fail and contain the Invalid
+        # label for the variable
+        response = self.app.get('/admin/variable', follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(self.session.query(models.Variable).count(), 1)
+        self.assertIn('<span class="label label-danger">Invalid</span>',
+                      response.data.decode('utf-8'))
+
+
 class TestKnownEventView(unittest.TestCase):
 
     CREATE_ENDPOINT = '/admin/knownevent/new/?url=/admin/knownevent/'

Reply via email to