diff --git a/moto/secretsmanager/exceptions.py b/moto/secretsmanager/exceptions.py index fa81b6d8b..7ef1a9239 100644 --- a/moto/secretsmanager/exceptions.py +++ b/moto/secretsmanager/exceptions.py @@ -7,11 +7,32 @@ class SecretsManagerClientError(JsonRESTError): class ResourceNotFoundException(SecretsManagerClientError): - def __init__(self): + def __init__(self, message): self.code = 404 super(ResourceNotFoundException, self).__init__( "ResourceNotFoundException", - "Secrets Manager can't find the specified secret" + message, + ) + + +# Using specialised exception due to the use of a non-ASCII character +class SecretNotFoundException(SecretsManagerClientError): + def __init__(self): + self.code = 404 + super(SecretNotFoundException, self).__init__( + "ResourceNotFoundException", + message=u"Secrets Manager can\u2019t find the specified secret." + ) + + +# Using specialised exception due to the use of a non-ASCII character +class SecretHasNoValueException(SecretsManagerClientError): + def __init__(self, version_stage): + self.code = 404 + super(SecretHasNoValueException, self).__init__( + "ResourceNotFoundException", + message=u"Secrets Manager can\u2019t find the specified secret " + u"value for staging label: {}".format(version_stage) ) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 63d847c49..e1a380c39 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import time @@ -9,7 +10,8 @@ import boto3 from moto.core import BaseBackend, BaseModel from .exceptions import ( - ResourceNotFoundException, + SecretNotFoundException, + SecretHasNoValueException, InvalidParameterException, ResourceExistsException, InvalidRequestException, @@ -46,7 +48,7 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException() + raise SecretNotFoundException() if not version_id and version_stage: # set version_id to match version_stage @@ -56,7 +58,7 @@ class SecretsManagerBackend(BaseBackend): version_id = ver_id break if not version_id: - raise ResourceNotFoundException() + raise SecretNotFoundException() # TODO check this part if 'deleted_date' in self.secrets[secret_id]: @@ -84,6 +86,9 @@ class SecretsManagerBackend(BaseBackend): if 'secret_binary' in secret_version: response_data["SecretBinary"] = secret_version['secret_binary'] + if 'secret_string' not in secret_version and 'secret_binary' not in secret_version: + raise SecretHasNoValueException(version_stage or u"AWSCURRENT") + response = json.dumps(response_data) return response @@ -169,7 +174,7 @@ class SecretsManagerBackend(BaseBackend): def describe_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise SecretNotFoundException() secret = self.secrets[secret_id] @@ -198,7 +203,7 @@ class SecretsManagerBackend(BaseBackend): rotation_days = 'AutomaticallyAfterDays' if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise SecretNotFoundException() if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -340,7 +345,7 @@ class SecretsManagerBackend(BaseBackend): def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise SecretNotFoundException() if 'deleted_date' in self.secrets[secret_id]: raise InvalidRequestException( @@ -370,7 +375,7 @@ class SecretsManagerBackend(BaseBackend): secret = self.secrets.get(secret_id, None) if not secret: - raise ResourceNotFoundException + raise SecretNotFoundException() arn = secret_arn(self.region, secret['secret_id']) name = secret['name'] @@ -380,7 +385,7 @@ class SecretsManagerBackend(BaseBackend): def restore_secret(self, secret_id): if not self._is_valid_identifier(secret_id): - raise ResourceNotFoundException + raise SecretNotFoundException() self.secrets[secret_id].pop('deleted_date', None) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 62de93bab..e2fc266ea 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import boto3 @@ -8,7 +9,7 @@ import string import pytz from datetime import datetime import sure # noqa -from nose.tools import assert_raises +from nose.tools import assert_raises, assert_equal from six import b DEFAULT_SECRET_NAME = 'test-secret' @@ -38,9 +39,14 @@ def test_get_secret_value_binary(): def test_get_secret_that_does_not_exist(): conn = boto3.client('secretsmanager', region_name='us-west-2') - with assert_raises(ClientError): + with assert_raises(ClientError) as cm: result = conn.get_secret_value(SecretId='i-dont-exist') + assert_equal( + u"Secrets Manager can\u2019t find the specified secret.", + cm.exception.response['Error']['Message'] + ) + @mock_secretsmanager def test_get_secret_that_does_not_match(): @@ -48,9 +54,14 @@ def test_get_secret_that_does_not_match(): create_secret = conn.create_secret(Name='java-util-test-password', SecretString="foosecret") - with assert_raises(ClientError): + with assert_raises(ClientError) as cm: result = conn.get_secret_value(SecretId='i-dont-match') + assert_equal( + u"Secrets Manager can\u2019t find the specified secret.", + cm.exception.response['Error']['Message'] + ) + @mock_secretsmanager def test_get_secret_value_that_is_marked_deleted(): @@ -65,6 +76,21 @@ def test_get_secret_value_that_is_marked_deleted(): result = conn.get_secret_value(SecretId='test-secret') +@mock_secretsmanager +def test_get_secret_that_has_no_value(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + create_secret = conn.create_secret(Name="java-util-test-password") + + with assert_raises(ClientError) as cm: + result = conn.get_secret_value(SecretId='java-util-test-password') + + assert_equal( + u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT", + cm.exception.response['Error']['Message'] + ) + + @mock_secretsmanager def test_create_secret(): conn = boto3.client('secretsmanager', region_name='us-east-1') diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index 23d823239..6955d8232 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import json @@ -49,7 +50,7 @@ def test_get_secret_that_does_not_exist(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -70,7 +71,27 @@ def test_get_secret_that_does_not_match(): "X-Amz-Target": "secretsmanager.GetSecretValue"}, ) json_data = json.loads(get_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." + assert json_data['__type'] == 'ResourceNotFoundException' + +@mock_secretsmanager +def test_get_secret_that_has_no_value(): + backend = server.create_backend_app('secretsmanager') + test_client = backend.test_client() + + create_secret = test_client.post('/', + data={"Name": DEFAULT_SECRET_NAME}, + headers={ + "X-Amz-Target": "secretsmanager.CreateSecret"}, + ) + get_secret = test_client.post('/', + data={"SecretId": DEFAULT_SECRET_NAME}, + headers={ + "X-Amz-Target": "secretsmanager.GetSecretValue"}, + ) + + json_data = json.loads(get_secret.data.decode("utf-8")) + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT" assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -158,7 +179,7 @@ def test_describe_secret_that_does_not_exist(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -182,7 +203,7 @@ def test_describe_secret_that_does_not_match(): ) json_data = json.loads(describe_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -283,7 +304,7 @@ def test_rotate_secret_that_does_not_exist(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager @@ -307,7 +328,7 @@ def test_rotate_secret_that_does_not_match(): ) json_data = json.loads(rotate_secret.data.decode("utf-8")) - assert json_data['message'] == "Secrets Manager can't find the specified secret" + assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret." assert json_data['__type'] == 'ResourceNotFoundException' @mock_secretsmanager