From 16ebe554952ee8a2c7c71fd0cdbd2828645f6eb6 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Wed, 23 Nov 2016 19:07:00 +0100
Subject: [PATCH 44/45] Add general sanitisers

Add filename sanitiser

Add hostname sanitiser

Rename "general.py" to "sanitisers.py"

Add a stub for ID sanitiser (at the moment same as hostname)

Rename for american spelling

Remove trailing dots

Sanitize ID input for key generator

Add test case for sanitizers

Fix imports: builtins was deprecated

Sanitize only non-None values
---
 salt/utils/sanitizers.py            | 61 +++++++++++++++++++++++++++++++++++++
 salt/wheel/key.py                   |  5 +++
 tests/unit/utils/sanitizers_test.py | 57 ++++++++++++++++++++++++++++++++++
 3 files changed, 123 insertions(+)
 create mode 100644 salt/utils/sanitizers.py
 create mode 100644 tests/unit/utils/sanitizers_test.py

diff --git a/salt/utils/sanitizers.py b/salt/utils/sanitizers.py
new file mode 100644
index 0000000000..a80d8e22eb
--- /dev/null
+++ b/salt/utils/sanitizers.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2016 SUSE LLC
+#
+# Licensed 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.
+
+import re
+import os.path
+from salt.ext.six import text_type as text
+
+from salt.exceptions import CommandExecutionError
+
+
+class InputSanitizer(object):
+    @staticmethod
+    def trim(value):
+        '''
+        Raise an exception if value is empty. Otherwise strip it down.
+        :param value:
+        :return:
+        '''
+        value = (value or '').strip()
+        if not value:
+            raise CommandExecutionError("Empty value during sanitation")
+
+        return text(value)
+
+    @staticmethod
+    def filename(value):
+        '''
+        Remove everything that would affect paths in the filename
+
+        :param value:
+        :return:
+        '''
+        return re.sub('[^a-zA-Z0-9.-_ ]', '', os.path.basename(InputSanitizer.trim(value)))
+
+    @staticmethod
+    def hostname(value):
+        '''
+        Clean value for RFC1123.
+
+        :param value:
+        :return:
+        '''
+        return re.sub(r'[^a-zA-Z0-9.-]', '', InputSanitizer.trim(value)).strip('.')
+
+    id = hostname
+
+
+clean = InputSanitizer()
diff --git a/salt/wheel/key.py b/salt/wheel/key.py
index 6b69e150ad..8730ca783d 100644
--- a/salt/wheel/key.py
+++ b/salt/wheel/key.py
@@ -11,6 +11,8 @@ import hashlib
 # Import salt libs
 import salt.key
 import salt.crypt
+from salt.utils.sanitizers import clean
+
 
 __func_alias__ = {
     'list_': 'list'
@@ -117,6 +119,8 @@ def gen(id_=None, keysize=2048):
     '''
     if id_ is None:
         id_ = hashlib.sha512(os.urandom(32)).hexdigest()
+    else:
+        id_ = clean.id(id_)
     ret = {'priv': '',
            'pub': ''}
     priv = salt.crypt.gen_keys(__opts__['pki_dir'], id_, keysize)
@@ -135,6 +139,7 @@ def gen_accept(id_, keysize=2048, force=False):
     Generate a key pair then accept the public key. This function returns the
     key pair in a dict, only the public key is preserved on the master.
     '''
+    id_ = clean.id(id_)
     ret = gen(id_, keysize)
     acc_path = os.path.join(__opts__['pki_dir'], 'minions', id_)
     if os.path.isfile(acc_path) and not force:
diff --git a/tests/unit/utils/sanitizers_test.py b/tests/unit/utils/sanitizers_test.py
new file mode 100644
index 0000000000..a80d80d4cf
--- /dev/null
+++ b/tests/unit/utils/sanitizers_test.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+# Import python libs
+from __future__ import absolute_import
+from salt.ext.six import text_type as text
+
+# Import Salt Libs
+from salt.utils.sanitizers import clean
+
+# Import Salt Testing Libs
+from salttesting import TestCase, skipIf
+from salttesting.mock import NO_MOCK, NO_MOCK_REASON
+from salttesting.helpers import ensure_in_syspath
+
+ensure_in_syspath('../../')
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+class SanitizersTestCase(TestCase):
+    '''
+    TestCase for sanitizers
+    '''
+    def test_sanitized_trim(self):
+        '''
+        Test sanitized input for trimming
+        '''
+        value = u' sample '
+        response = clean.trim(value)
+        assert response == 'sample'
+        assert type(response) == text
+
+    def test_sanitized_filename(self):
+        '''
+        Test sanitized input for filename
+        '''
+        value = '/absolute/path/to/the/file.txt'
+        response = clean.filename(value)
+        assert response == 'file.txt'
+
+        value = '../relative/path/to/the/file.txt'
+        response = clean.filename(value)
+        assert response == 'file.txt'
+
+    def test_sanitized_hostname(self):
+        '''
+        Test sanitized input for hostname (id)
+        '''
+        value = '   ../ ../some/dubious/hostname      '
+        response = clean.hostname(value)
+        assert response == 'somedubioushostname'
+
+    test_sanitized_id = test_sanitized_hostname
+
+
+if __name__ == '__main__':
+    from integration import run_tests
+    run_tests(ArgsTestCase, needs_daemon=False)
-- 
2.11.0

