File move-tokens-in-place-with-an-atomic-operation.patch of Package salt.14915

From 1a15b40889ebd7aa5831997e12e497fad736544d Mon Sep 17 00:00:00 2001
From: "Daniel A. Wozniak" <dwozniak@saltstack.com>
Date: Mon, 2 Sep 2019 00:03:27 +0000
Subject: [PATCH] Move tokens in place with an atomic operation

---
 salt/tokens/localfs.py            |  4 ++-
 tests/unit/tokens/__init__.py     |  1 +
 tests/unit/tokens/test_localfs.py | 53 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit/tokens/__init__.py
 create mode 100644 tests/unit/tokens/test_localfs.py

diff --git a/salt/tokens/localfs.py b/salt/tokens/localfs.py
index 021bdb9e50..3660ee3186 100644
--- a/salt/tokens/localfs.py
+++ b/salt/tokens/localfs.py
@@ -34,6 +34,7 @@ def mk_token(opts, tdata):
     hash_type = getattr(hashlib, opts.get('hash_type', 'md5'))
     tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
     t_path = os.path.join(opts['token_dir'], tok)
+    temp_t_path = '{}.tmp'.format(t_path)
     while os.path.isfile(t_path):
         tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
         t_path = os.path.join(opts['token_dir'], tok)
@@ -41,8 +42,9 @@ def mk_token(opts, tdata):
     serial = salt.payload.Serial(opts)
     try:
         with salt.utils.files.set_umask(0o177):
-            with salt.utils.files.fopen(t_path, 'w+b') as fp_:
+            with salt.utils.files.fopen(temp_t_path, 'w+b') as fp_:
                 fp_.write(serial.dumps(tdata))
+        os.rename(temp_t_path, t_path)
     except (IOError, OSError):
         log.warning(
             'Authentication failure: can not write token file "%s".', t_path)
diff --git a/tests/unit/tokens/__init__.py b/tests/unit/tokens/__init__.py
new file mode 100644
index 0000000000..40a96afc6f
--- /dev/null
+++ b/tests/unit/tokens/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/tests/unit/tokens/test_localfs.py b/tests/unit/tokens/test_localfs.py
new file mode 100644
index 0000000000..f950091252
--- /dev/null
+++ b/tests/unit/tokens/test_localfs.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+import salt.utils.files
+import salt.tokens.localfs
+
+from tests.support.unit import TestCase, skipIf
+from tests.support.helpers import with_tempdir
+from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch
+
+
+class CalledWith(object):
+
+    def __init__(self, func, called_with=None):
+        self.func = func
+        if called_with is None:
+            self.called_with = []
+        else:
+            self.called_with = called_with
+
+    def __call__(self, *args, **kwargs):
+        self.called_with.append((args, kwargs))
+        return self.func(*args, **kwargs)
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+class WriteTokenTest(TestCase):
+
+    @with_tempdir()
+    def test_write_token(self, tmpdir):
+        '''
+        Validate tokens put in place with an atomic move
+        '''
+        opts = {
+            'token_dir': tmpdir
+        }
+        fopen = CalledWith(salt.utils.files.fopen)
+        rename = CalledWith(os.rename)
+        with patch('salt.utils.files.fopen', fopen), patch('os.rename', rename):
+            tdata = salt.tokens.localfs.mk_token(opts, {})
+        assert 'token' in tdata
+        t_path = os.path.join(tmpdir, tdata['token'])
+        temp_t_path = '{}.tmp'.format(t_path)
+        assert len(fopen.called_with) == 1, len(fopen.called_with)
+        assert fopen.called_with == [
+            ((temp_t_path, 'w+b'), {})
+        ], fopen.called_with
+        assert len(rename.called_with) == 1, len(rename.called_with)
+        assert rename.called_with == [
+            ((temp_t_path, t_path), {})
+        ], rename.called_with
-- 
2.16.4