Added get_random_password mock with tests
This commit is contained in:
parent
ba1ceee95f
commit
6c7a22c7d7
@ -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)
|
||||
|
@ -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 = (
|
||||
|
@ -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
|
||||
)
|
||||
|
72
moto/secretsmanager/utils.py
Normal file
72
moto/secretsmanager/utils.py
Normal 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
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user