Added get_random_password mock with tests

This commit is contained in:
zane 2018-07-16 12:39:59 -07:00
parent ba1ceee95f
commit 6c7a22c7d7
5 changed files with 253 additions and 6 deletions

View File

@ -13,3 +13,17 @@ class ResourceNotFoundException(SecretsManagerClientError):
"ResourceNotFoundException",
"Secrets Manager can't find the specified secret"
)
class ClientError(SecretsManagerClientError):
def __init__(self, message):
super(ClientError, self).__init__(
'InvalidParameterValue',
message)
class InvalidParameterException(SecretsManagerClientError):
def __init__(self, message):
super(InvalidParameterException, self).__init__(
'InvalidParameterException',
message)

View File

@ -6,7 +6,12 @@ import json
import boto3
from moto.core import BaseBackend, BaseModel
from .exceptions import ResourceNotFoundException
from .exceptions import (
ResourceNotFoundException,
InvalidParameterException,
ClientError
)
from .utils import random_password, secret_arn
class SecretsManager(BaseModel):
@ -40,7 +45,7 @@ class SecretsManagerBackend(BaseBackend):
raise ResourceNotFoundException()
response = json.dumps({
"ARN": self.secret_arn(self.region, self.secret_id),
"ARN": secret_arn(self.region, self.secret_id),
"Name": self.secret_id,
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
"SecretString": self.secret_string,
@ -58,16 +63,41 @@ class SecretsManagerBackend(BaseBackend):
self.secret_id = name
response = json.dumps({
"ARN": self.secret_arn(self.region, name),
"ARN": secret_arn(self.region, name),
"Name": self.secret_id,
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
})
return response
def secret_arn(self, region, secret_id):
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
region, secret_id)
def get_random_password(self, password_length,
exclude_characters, exclude_numbers,
exclude_punctuation, exclude_uppercase,
exclude_lowercase, include_space,
require_each_included_type):
# password size must have value less than or equal to 4096
if password_length > 4096:
raise ClientError(
"ClientError: An error occurred (ValidationException) \
when calling the GetRandomPassword operation: 1 validation error detected: Value '{}' at 'passwordLength' \
failed to satisfy constraint: Member must have value less than or equal to 4096".format(password_length))
if password_length < 4:
raise InvalidParameterException(
"InvalidParameterException: An error occurred (InvalidParameterException) \
when calling the GetRandomPassword operation: Password length is too short based on the required types.")
response = json.dumps({
"RandomPassword": random_password(password_length,
exclude_characters,
exclude_numbers,
exclude_punctuation,
exclude_uppercase,
exclude_lowercase,
include_space,
require_each_included_type)
})
return response
available_regions = (

View File

@ -23,3 +23,24 @@ class SecretsManagerResponse(BaseResponse):
name=name,
secret_string=secret_string
)
def get_random_password(self):
password_length = self._get_param('PasswordLength', if_none=32)
exclude_characters = self._get_param('ExcludeCharacters', if_none='')
exclude_numbers = self._get_param('ExcludeNumbers', if_none=False)
exclude_punctuation = self._get_param('ExcludePunctuation', if_none=False)
exclude_uppercase = self._get_param('ExcludeUppercase', if_none=False)
exclude_lowercase = self._get_param('ExcludeLowercase', if_none=False)
include_space = self._get_param('IncludeSpace', if_none=False)
require_each_included_type = self._get_param(
'RequireEachIncludedType', if_none=True)
return secretsmanager_backends[self.region].get_random_password(
password_length=password_length,
exclude_characters=exclude_characters,
exclude_numbers=exclude_numbers,
exclude_punctuation=exclude_punctuation,
exclude_uppercase=exclude_uppercase,
exclude_lowercase=exclude_lowercase,
include_space=include_space,
require_each_included_type=require_each_included_type
)

View File

@ -0,0 +1,72 @@
from __future__ import unicode_literals
import random
import string
import six
import re
def random_password(password_length, exclude_characters, exclude_numbers,
exclude_punctuation, exclude_uppercase, exclude_lowercase,
include_space, require_each_included_type):
password = ''
required_characters = ''
if not exclude_lowercase and not exclude_uppercase:
password += string.ascii_letters
required_characters += random.choice(_exclude_characters(
string.ascii_lowercase, exclude_characters))
required_characters += random.choice(_exclude_characters(
string.ascii_uppercase, exclude_characters))
elif not exclude_lowercase:
password += string.ascii_lowercase
required_characters += random.choice(_exclude_characters(
string.ascii_lowercase, exclude_characters))
elif not exclude_uppercase:
password += string.ascii_uppercase
required_characters += random.choice(_exclude_characters(
string.ascii_uppercase, exclude_characters))
if not exclude_numbers:
password += string.digits
required_characters += random.choice(_exclude_characters(
string.digits, exclude_characters))
if not exclude_punctuation:
password += string.punctuation
required_characters += random.choice(_exclude_characters(
string.punctuation, exclude_characters))
if include_space:
password += " "
required_characters += " "
password = ''.join(
six.text_type(random.choice(password))
for x in range(password_length))
if require_each_included_type:
password = _add_password_require_each_included_type(
password, required_characters)
password = _exclude_characters(password, exclude_characters)
return password
def secret_arn(region, secret_id):
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
region, secret_id)
def _exclude_characters(password, exclude_characters):
for c in exclude_characters:
if c in string.punctuation:
# Escape punctuation regex usage
c = "\{0}".format(c)
password = re.sub(c, '', str(password))
return password
def _add_password_require_each_included_type(password, required_characters):
password_with_required_char = password[:-len(required_characters)]
password_with_required_char += required_characters
return password_with_required_char

View File

@ -5,6 +5,8 @@ import boto3
from moto import mock_secretsmanager
from botocore.exceptions import ClientError
import sure # noqa
import string
import unittest
from nose.tools import assert_raises
@mock_secretsmanager
@ -33,3 +35,111 @@ def test_create_secret():
assert result['Name'] == 'test-secret'
secret = conn.get_secret_value(SecretId='test-secret')
assert secret['SecretString'] == 'foosecret'
@mock_secretsmanager
def test_get_random_password_default_length():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password()
assert len(random_password['RandomPassword']) == 32
@mock_secretsmanager
def test_get_random_password_default_requirements():
# When require_each_included_type, default true
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password()
# Should contain lowercase, upppercase, digit, special character
assert any(c.islower() for c in random_password['RandomPassword'])
assert any(c.isupper() for c in random_password['RandomPassword'])
assert any(c.isdigit() for c in random_password['RandomPassword'])
assert any(c in string.punctuation
for c in random_password['RandomPassword'])
@mock_secretsmanager
def test_get_random_password_custom_length():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=50)
assert len(random_password['RandomPassword']) == 50
@mock_secretsmanager
def test_get_random_exclude_lowercase():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=55,
ExcludeLowercase=True)
assert any(c.islower() for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_exclude_uppercase():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=55,
ExcludeUppercase=True)
assert any(c.isupper() for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_exclude_characters_and_symbols():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=20,
ExcludeCharacters='xyzDje@?!.')
assert any(c in 'xyzDje@?!.' for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_exclude_numbers():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=100,
ExcludeNumbers=True)
assert any(c.isdigit() for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_exclude_punctuation():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=100,
ExcludePunctuation=True)
assert any(c in string.punctuation
for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_include_space_false():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=300)
assert any(c.isspace() for c in random_password['RandomPassword']) == False
@mock_secretsmanager
def test_get_random_include_space_true():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=4,
IncludeSpace=True)
assert any(c.isspace() for c in random_password['RandomPassword']) == True
@mock_secretsmanager
def test_get_random_require_each_included_type():
conn = boto3.client('secretsmanager', region_name='us-west-2')
random_password = conn.get_random_password(PasswordLength=4,
RequireEachIncludedType=True)
assert any(c in string.punctuation for c in random_password['RandomPassword']) == True
assert any(c in string.ascii_lowercase for c in random_password['RandomPassword']) == True
assert any(c in string.ascii_uppercase for c in random_password['RandomPassword']) == True
assert any(c in string.digits for c in random_password['RandomPassword']) == True
@mock_secretsmanager
def test_get_random_too_short_password():
conn = boto3.client('secretsmanager', region_name='us-west-2')
with assert_raises(ClientError):
random_password = conn.get_random_password(PasswordLength=3)
@mock_secretsmanager
def test_get_random_too_long_password():
conn = boto3.client('secretsmanager', region_name='us-west-2')
with assert_raises(Exception):
random_password = conn.get_random_password(PasswordLength=5555)