Browse Source

Ensure cryptostrings contain all char types

This patch aims to:
1. Validate that generated cryptographic strings (salts and passphrases)
   contain at least one uppercase letter, lowercase letter, number and
   symbol before returning it to the user.
2. Add new unit tests for the cryptostring methods.
3. Move existing unit tests for cryptostring methods to new test file.
4. Rename test_generate_cryptostring to test_passphrases as this is
   more accurate of the tests contained in the file.

Change-Id: I669831fb515209467b236cca63502f64a9263d86
Alexander Hughes 1 week ago
parent
commit
cecd24ed38

+ 75
- 4
pegleg/engine/util/cryptostring.py View File

@@ -25,9 +25,80 @@ class CryptoString(object):
25 25
         self._pool = string.ascii_letters + string.digits + punctuation
26 26
         self._random = random.SystemRandom()
27 27
 
28
-    def get_crypto_string(self, length=24):
28
+    def has_upper(self, crypto_str):
29
+        """Check if string contains an uppercase letter
30
+
31
+        :param str crypto_str: The string to test.
32
+        :returns: True if string contains at least one uppercase letter.
33
+        :rtype: boolean
34
+        """
35
+
36
+        return any(char in string.ascii_uppercase for char in crypto_str)
37
+
38
+    def has_lower(self, crypto_str):
39
+        """Check if string contains a lowercase letter
40
+
41
+        :param str crypto_str: The string to test.
42
+        :returns: True if string contains at least one lowercase letter.
43
+        :rtype: boolean
44
+        """
45
+
46
+        return any(char in string.ascii_lowercase for char in crypto_str)
47
+
48
+    def has_number(self, crypto_str):
49
+        """Check if string contains a number
50
+
51
+        :param str crypto_str: The string to test.
52
+        :returns: True if string contains at least one number.
53
+        :rtype: boolean
54
+        """
55
+
56
+        return any(char in string.digits for char in crypto_str)
57
+
58
+    def has_symbol(self, crypto_str):
59
+        """Check if string contains a symbol
60
+
61
+        :param str crypto_str: The string to test.
62
+        :returns: True if string contains at least one symbol.
63
+        :rtype: boolean
29 64
         """
30
-        Create and return a random cryptographic string of length ``length``.
65
+
66
+        return any(char in string.punctuation for char in crypto_str)
67
+
68
+    def validate_crypto_str(self, crypto_str):
69
+        """Ensure cryptostring contains characters from all sets
70
+
71
+        :param str crypto_str: The string to test.
72
+        :returns: True if string contains at least one each: uppercase letter,
73
+            lowercase letter, number and symbol
74
+        :rtype: boolean
31 75
         """
32
-        return ''.join(self._random.choice(self._pool)
33
-                       for _ in range(max(24, length)))
76
+
77
+        for test in [self.has_upper, self.has_lower, self.has_number,
78
+                     self.has_symbol]:
79
+            if not test(crypto_str):
80
+                return False
81
+
82
+        return True
83
+
84
+    def get_crypto_string(self, length=24):
85
+        """Create and return a random cryptographic string.
86
+
87
+        When the string is generated, it will be checked to determine if it
88
+        contains uppercase letters, lowercase letters, numbers and symbols.
89
+        If it does not contain at least one character from each set it will
90
+        be re-generated until it does.
91
+
92
+        :param int length: Length of crypto string to generate. If this length
93
+            is smaller than 24, or not defined, the length will default to 24.
94
+        :returns: The generated cryptographic string
95
+        :rtype: string
96
+        """
97
+
98
+        while True:
99
+            crypto_str = ''.join(self._random.choice(self._pool)
100
+                                 for _ in range(max(24, length)))
101
+            if self.validate_crypto_str(crypto_str):
102
+                break
103
+
104
+        return crypto_str

tests/unit/engine/test_generate_cryptostring.py → tests/unit/engine/test_generate_passphrases.py View File

@@ -19,11 +19,9 @@ import uuid
19 19
 from cryptography import fernet
20 20
 import mock
21 21
 import pytest
22
-import string
23 22
 from testfixtures import log_capture
24 23
 import yaml
25 24
 
26
-from pegleg.engine.util.cryptostring import CryptoString
27 25
 from pegleg.engine.generators.passphrase_generator import PassphraseGenerator
28 26
 from pegleg.engine.util import encryption
29 27
 from pegleg.engine import util
@@ -122,32 +120,6 @@ TEST_SITE_DOCUMENTS = [TEST_SITE_DEFINITION, TEST_PASSPHRASES_CATALOG]
122 120
 TEST_GLOBAL_SITE_DOCUMENTS = [TEST_SITE_DEFINITION, TEST_GLOBAL_PASSPHRASES_CATALOG]
123 121
 
124 122
 
125
-def test_cryptostring_default_len():
126
-    s_util = CryptoString()
127
-    s = s_util.get_crypto_string()
128
-    assert len(s) == 24
129
-    alphabet = set(string.punctuation + string.ascii_letters + string.digits)
130
-    assert any(c in alphabet for c in s)
131
-
132
-
133
-def test_cryptostring_short_len():
134
-    s_util = CryptoString()
135
-    s = s_util.get_crypto_string(0)
136
-    assert len(s) == 24
137
-    s = s_util.get_crypto_string(23)
138
-    assert len(s) == 24
139
-    s = s_util.get_crypto_string(-1)
140
-    assert len(s) == 24
141
-
142
-
143
-def test_cryptostring_long_len():
144
-    s_util = CryptoString()
145
-    s = s_util.get_crypto_string(25)
146
-    assert len(s) == 25
147
-    s = s_util.get_crypto_string(128)
148
-    assert len(s) == 128
149
-
150
-
151 123
 @mock.patch.object(
152 124
     util.definition,
153 125
     'documents_for_site',

+ 89
- 0
tests/unit/engine/util/test_cryptostring.py View File

@@ -0,0 +1,89 @@
1
+# Copyright 2018 AT&T Intellectual Property.  All other rights reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#     http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+from pegleg.engine.util.cryptostring import CryptoString
16
+import string
17
+
18
+
19
+def test_cryptostring_default_len():
20
+    s_util = CryptoString()
21
+    s = s_util.get_crypto_string()
22
+    assert len(s) == 24
23
+
24
+def test_cryptostring_short_len():
25
+    s_util = CryptoString()
26
+    s = s_util.get_crypto_string(0)
27
+    assert len(s) == 24
28
+    s = s_util.get_crypto_string(23)
29
+    assert len(s) == 24
30
+    s = s_util.get_crypto_string(-1)
31
+    assert len(s) == 24
32
+
33
+def test_cryptostring_long_len():
34
+    s_util = CryptoString()
35
+    s = s_util.get_crypto_string(25)
36
+    assert len(s) == 25
37
+    s = s_util.get_crypto_string(128)
38
+    assert len(s) == 128
39
+
40
+def test_cryptostring_has_upper():
41
+    s_util = CryptoString()
42
+    crypto_string = 'Th1sP@sswordH4sUppers!'
43
+    assert s_util.has_upper(crypto_string) is True
44
+    crypto_string = 'THISPASSWORDHASONLYUPPERS'
45
+    assert s_util.has_upper(crypto_string) is True
46
+    crypto_string = 'th1sp@sswordh4snouppers!'
47
+    assert s_util.has_upper(crypto_string) is False
48
+
49
+def test_cryptostring_has_lower():
50
+    s_util = CryptoString()
51
+    crypto_string = 'Th1sP@sswordH4sLowers!'
52
+    assert s_util.has_lower(crypto_string) is True
53
+    crypto_string = 'thispasswordhasonlylowers'
54
+    assert s_util.has_lower(crypto_string) is True
55
+    crypto_string = 'TH1SP@SSWORDH4SNOLOWERS!'
56
+    assert s_util.has_lower(crypto_string) is False
57
+
58
+def test_cryptostring_has_number():
59
+    s_util = CryptoString()
60
+    crypto_string = 'Th1sP@sswordH4sNumbers!'
61
+    assert s_util.has_number(crypto_string) is True
62
+    crypto_string = '123456789012345678901234567890'
63
+    assert s_util.has_number(crypto_string) is True
64
+    crypto_string = 'ThisP@sswordHasNoNumbers!'
65
+    assert s_util.has_number(crypto_string) is False
66
+
67
+def test_cryptostring_has_symbol():
68
+    s_util = CryptoString()
69
+    crypto_string = 'Th1sP@sswordH4sSymbols!'
70
+    assert s_util.has_symbol(crypto_string) is True
71
+    crypto_string = '!@#$%^&*()[]\}{|<>?,./~`'
72
+    assert s_util.has_symbol(crypto_string) is True
73
+    crypto_string = 'ThisPasswordH4sNoSymbols'
74
+    assert s_util.has_symbol(crypto_string) is False
75
+
76
+def test_cryptostring_has_all():
77
+    s_util = CryptoString()
78
+    crypto_string = s_util.get_crypto_string()
79
+    assert s_util.validate_crypto_str(crypto_string) is True
80
+    crypto_string = 'Th1sP@sswordH4sItAll!'
81
+    assert s_util.validate_crypto_str(crypto_string) is True
82
+    crypto_string = 'th1sp@sswordh4snouppers!'
83
+    assert s_util.validate_crypto_str(crypto_string) is False
84
+    crypto_string = 'TH1SP@SSWORDH4SNOLOWERS!'
85
+    assert s_util.validate_crypto_str(crypto_string) is False
86
+    crypto_string = 'ThisP@sswordHasNoNumbers!'
87
+    assert s_util.validate_crypto_str(crypto_string) is False
88
+    crypto_string = 'ThisPasswordH4sNoSymbols'
89
+    assert s_util.validate_crypto_str(crypto_string) is False

Loading…
Cancel
Save