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
This commit is contained in:
Alexander Hughes 2019-04-11 10:09:48 -05:00
parent 85a2a898a1
commit cecd24ed38
3 changed files with 164 additions and 32 deletions

View File

@ -25,9 +25,80 @@ class CryptoString(object):
self._pool = string.ascii_letters + string.digits + punctuation
self._random = random.SystemRandom()
def has_upper(self, crypto_str):
"""Check if string contains an uppercase letter
:param str crypto_str: The string to test.
:returns: True if string contains at least one uppercase letter.
:rtype: boolean
"""
return any(char in string.ascii_uppercase for char in crypto_str)
def has_lower(self, crypto_str):
"""Check if string contains a lowercase letter
:param str crypto_str: The string to test.
:returns: True if string contains at least one lowercase letter.
:rtype: boolean
"""
return any(char in string.ascii_lowercase for char in crypto_str)
def has_number(self, crypto_str):
"""Check if string contains a number
:param str crypto_str: The string to test.
:returns: True if string contains at least one number.
:rtype: boolean
"""
return any(char in string.digits for char in crypto_str)
def has_symbol(self, crypto_str):
"""Check if string contains a symbol
:param str crypto_str: The string to test.
:returns: True if string contains at least one symbol.
:rtype: boolean
"""
return any(char in string.punctuation for char in crypto_str)
def validate_crypto_str(self, crypto_str):
"""Ensure cryptostring contains characters from all sets
:param str crypto_str: The string to test.
:returns: True if string contains at least one each: uppercase letter,
lowercase letter, number and symbol
:rtype: boolean
"""
for test in [self.has_upper, self.has_lower, self.has_number,
self.has_symbol]:
if not test(crypto_str):
return False
return True
def get_crypto_string(self, length=24):
"""Create and return a random cryptographic string.
When the string is generated, it will be checked to determine if it
contains uppercase letters, lowercase letters, numbers and symbols.
If it does not contain at least one character from each set it will
be re-generated until it does.
:param int length: Length of crypto string to generate. If this length
is smaller than 24, or not defined, the length will default to 24.
:returns: The generated cryptographic string
:rtype: string
"""
Create and return a random cryptographic string of length ``length``.
"""
return ''.join(self._random.choice(self._pool)
for _ in range(max(24, length)))
while True:
crypto_str = ''.join(self._random.choice(self._pool)
for _ in range(max(24, length)))
if self.validate_crypto_str(crypto_str):
break
return crypto_str

View File

@ -19,11 +19,9 @@ import uuid
from cryptography import fernet
import mock
import pytest
import string
from testfixtures import log_capture
import yaml
from pegleg.engine.util.cryptostring import CryptoString
from pegleg.engine.generators.passphrase_generator import PassphraseGenerator
from pegleg.engine.util import encryption
from pegleg.engine import util
@ -122,32 +120,6 @@ TEST_SITE_DOCUMENTS = [TEST_SITE_DEFINITION, TEST_PASSPHRASES_CATALOG]
TEST_GLOBAL_SITE_DOCUMENTS = [TEST_SITE_DEFINITION, TEST_GLOBAL_PASSPHRASES_CATALOG]
def test_cryptostring_default_len():
s_util = CryptoString()
s = s_util.get_crypto_string()
assert len(s) == 24
alphabet = set(string.punctuation + string.ascii_letters + string.digits)
assert any(c in alphabet for c in s)
def test_cryptostring_short_len():
s_util = CryptoString()
s = s_util.get_crypto_string(0)
assert len(s) == 24
s = s_util.get_crypto_string(23)
assert len(s) == 24
s = s_util.get_crypto_string(-1)
assert len(s) == 24
def test_cryptostring_long_len():
s_util = CryptoString()
s = s_util.get_crypto_string(25)
assert len(s) == 25
s = s_util.get_crypto_string(128)
assert len(s) == 128
@mock.patch.object(
util.definition,
'documents_for_site',

View File

@ -0,0 +1,89 @@
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# 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.
from pegleg.engine.util.cryptostring import CryptoString
import string
def test_cryptostring_default_len():
s_util = CryptoString()
s = s_util.get_crypto_string()
assert len(s) == 24
def test_cryptostring_short_len():
s_util = CryptoString()
s = s_util.get_crypto_string(0)
assert len(s) == 24
s = s_util.get_crypto_string(23)
assert len(s) == 24
s = s_util.get_crypto_string(-1)
assert len(s) == 24
def test_cryptostring_long_len():
s_util = CryptoString()
s = s_util.get_crypto_string(25)
assert len(s) == 25
s = s_util.get_crypto_string(128)
assert len(s) == 128
def test_cryptostring_has_upper():
s_util = CryptoString()
crypto_string = 'Th1sP@sswordH4sUppers!'
assert s_util.has_upper(crypto_string) is True
crypto_string = 'THISPASSWORDHASONLYUPPERS'
assert s_util.has_upper(crypto_string) is True
crypto_string = 'th1sp@sswordh4snouppers!'
assert s_util.has_upper(crypto_string) is False
def test_cryptostring_has_lower():
s_util = CryptoString()
crypto_string = 'Th1sP@sswordH4sLowers!'
assert s_util.has_lower(crypto_string) is True
crypto_string = 'thispasswordhasonlylowers'
assert s_util.has_lower(crypto_string) is True
crypto_string = 'TH1SP@SSWORDH4SNOLOWERS!'
assert s_util.has_lower(crypto_string) is False
def test_cryptostring_has_number():
s_util = CryptoString()
crypto_string = 'Th1sP@sswordH4sNumbers!'
assert s_util.has_number(crypto_string) is True
crypto_string = '123456789012345678901234567890'
assert s_util.has_number(crypto_string) is True
crypto_string = 'ThisP@sswordHasNoNumbers!'
assert s_util.has_number(crypto_string) is False
def test_cryptostring_has_symbol():
s_util = CryptoString()
crypto_string = 'Th1sP@sswordH4sSymbols!'
assert s_util.has_symbol(crypto_string) is True
crypto_string = '!@#$%^&*()[]\}{|<>?,./~`'
assert s_util.has_symbol(crypto_string) is True
crypto_string = 'ThisPasswordH4sNoSymbols'
assert s_util.has_symbol(crypto_string) is False
def test_cryptostring_has_all():
s_util = CryptoString()
crypto_string = s_util.get_crypto_string()
assert s_util.validate_crypto_str(crypto_string) is True
crypto_string = 'Th1sP@sswordH4sItAll!'
assert s_util.validate_crypto_str(crypto_string) is True
crypto_string = 'th1sp@sswordh4snouppers!'
assert s_util.validate_crypto_str(crypto_string) is False
crypto_string = 'TH1SP@SSWORDH4SNOLOWERS!'
assert s_util.validate_crypto_str(crypto_string) is False
crypto_string = 'ThisP@sswordHasNoNumbers!'
assert s_util.validate_crypto_str(crypto_string) is False
crypto_string = 'ThisPasswordH4sNoSymbols'
assert s_util.validate_crypto_str(crypto_string) is False