From 73bd3e1c770d6b5d53c7829a048887acaef5487a Mon Sep 17 00:00:00 2001 From: Robert C Jensen Date: Thu, 24 Jan 2019 20:39:55 -0500 Subject: [PATCH 01/15] [cognitoidp] feat: add update_identity_provider --- moto/cognitoidp/models.py | 13 ++++++++++ moto/cognitoidp/responses.py | 8 ++++++ tests/test_cognitoidp/test_cognitoidp.py | 32 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 00868f7b3..bdd279ba6 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -426,6 +426,19 @@ class CognitoIdpBackend(BaseBackend): return identity_provider + def update_identity_provider(self, user_pool_id, name, extended_config): + user_pool = self.user_pools.get(user_pool_id) + if not user_pool: + raise ResourceNotFoundError(user_pool_id) + + identity_provider = user_pool.identity_providers.get(name) + if not identity_provider: + raise ResourceNotFoundError(name) + + identity_provider.extended_config.update(extended_config) + + return identity_provider + def delete_identity_provider(self, user_pool_id, name): user_pool = self.user_pools.get(user_pool_id) if not user_pool: diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py index 8b3941c21..264910739 100644 --- a/moto/cognitoidp/responses.py +++ b/moto/cognitoidp/responses.py @@ -143,6 +143,14 @@ class CognitoIdpResponse(BaseResponse): "IdentityProvider": identity_provider.to_json(extended=True) }) + def update_identity_provider(self): + user_pool_id = self._get_param("UserPoolId") + name = self._get_param("ProviderName") + identity_provider = cognitoidp_backends[self.region].update_identity_provider(user_pool_id, name, self.parameters) + return json.dumps({ + "IdentityProvider": identity_provider.to_json(extended=True) + }) + def delete_identity_provider(self): user_pool_id = self._get_param("UserPoolId") name = self._get_param("ProviderName") diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 0ef082d5c..5706c9c8d 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -484,6 +484,38 @@ def test_describe_identity_providers(): result["IdentityProvider"]["ProviderDetails"]["thing"].should.equal(value) +@mock_cognitoidp +def test_update_identity_provider(): + conn = boto3.client("cognito-idp", "us-west-2") + + provider_name = str(uuid.uuid4()) + provider_type = "Facebook" + value = str(uuid.uuid4()) + new_value = str(uuid.uuid4()) + user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"] + conn.create_identity_provider( + UserPoolId=user_pool_id, + ProviderName=provider_name, + ProviderType=provider_type, + ProviderDetails={ + "thing": value + }, + ) + + result = conn.update_identity_provider( + UserPoolId=user_pool_id, + ProviderName=provider_name, + ProviderDetails={ + "thing": new_value + }, + ) + + result["IdentityProvider"]["UserPoolId"].should.equal(user_pool_id) + result["IdentityProvider"]["ProviderName"].should.equal(provider_name) + result["IdentityProvider"]["ProviderType"].should.equal(provider_type) + result["IdentityProvider"]["ProviderDetails"]["thing"].should.equal(new_value) + + @mock_cognitoidp def test_delete_identity_providers(): conn = boto3.client("cognito-idp", "us-west-2") From d53626ad9a1a0ac616ba7cdd77185a17b4efdc23 Mon Sep 17 00:00:00 2001 From: Andy Tumelty Date: Tue, 12 Mar 2019 16:27:37 +0000 Subject: [PATCH 02/15] Add support for iam update_user This covers both the NewPath and NewUserName parameters for update_user, but without regex validation for these values. --- IMPLEMENTATION_COVERAGE.md | 2 +- moto/iam/models.py | 12 ++++++++++++ moto/iam/responses.py | 12 ++++++++++++ tests/test_iam/test_iam.py | 13 +++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a5650f572..1c76b04cf 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2334,7 +2334,7 @@ - [ ] update_service_specific_credential - [X] update_signing_certificate - [ ] update_ssh_public_key -- [ ] update_user +- [X] update_user - [X] upload_server_certificate - [X] upload_signing_certificate - [ ] upload_ssh_public_key diff --git a/moto/iam/models.py b/moto/iam/models.py index 92ac19da7..ae993ebfd 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -892,6 +892,18 @@ class IAMBackend(BaseBackend): return users + def update_user(self, user_name, new_path=None, new_user_name=None): + try: + user = self.users[user_name] + except KeyError: + raise IAMNotFoundException("User {0} not found".format(user_name)) + + if new_path: + user.path = new_path + if new_user_name: + user.name = new_user_name + self.users[new_user_name] = self.users.pop(user_name) + def list_roles(self, path_prefix, marker, max_items): roles = None try: diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 5b19c9cdc..e5b4c9070 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -440,6 +440,18 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_USERS_TEMPLATE) return template.render(action='List', users=users) + def update_user(self): + user_name = self._get_param('UserName') + new_path = self._get_param('NewPath') + new_user_name = self._get_param('NewUserName') + iam_backend.update_user(user_name, new_path, new_user_name) + if new_user_name: + user = iam_backend.get_user(new_user_name) + else: + user = iam_backend.get_user(user_name) + template = self.response_template(USER_TEMPLATE) + return template.render(action='Update', user=user) + def create_login_profile(self): user_name = self._get_param('UserName') password = self._get_param('Password') diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index ceec5e06a..5875b747a 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -401,6 +401,19 @@ def test_get_user(): conn.get_user('my-user') +@mock_iam() +def test_update_user(): + conn = boto3.client('iam', region_name='us-east-1') + with assert_raises(conn.exceptions.NoSuchEntityException): + conn.update_user(UserName='my-user') + conn.create_user(UserName='my-user') + conn.update_user(UserName='my-user', NewPath='/new-path/', NewUserName='new-user') + response = conn.get_user(UserName='new-user') + response['User'].get('Path').should.equal('/new-path/') + with assert_raises(conn.exceptions.NoSuchEntityException): + conn.get_user(UserName='my-user') + + @mock_iam_deprecated() def test_get_current_user(): """If no user is specific, IAM returns the current user""" From 9ed80f14e8a3d7af81e51886b29f2a033678a34f Mon Sep 17 00:00:00 2001 From: Robert Jensen Date: Tue, 12 Mar 2019 17:52:34 -0400 Subject: [PATCH 03/15] fix coverage, update IMPLEMENTATION_COVERAGE --- IMPLEMENTATION_COVERAGE.md | 2 +- tests/test_cognitoidp/test_cognitoidp.py | 44 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a5650f572..1451f421a 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -916,7 +916,7 @@ - [ ] update_auth_event_feedback - [ ] update_device_status - [ ] update_group -- [ ] update_identity_provider +- [x] update_identity_provider - [ ] update_resource_server - [ ] update_user_attributes - [ ] update_user_pool diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 5706c9c8d..e4e38e821 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -516,6 +516,50 @@ def test_update_identity_provider(): result["IdentityProvider"]["ProviderDetails"]["thing"].should.equal(new_value) +@mock_cognitoidp +def test_update_identity_provider_no_user_pool(): + conn = boto3.client("cognito-idp", "us-west-2") + + new_value = str(uuid.uuid4()) + + with assert_raises(conn.exceptions.ResourceNotFoundException) as cm: + conn.update_identity_provider( + UserPoolId="foo", + ProviderName="bar", + ProviderDetails={ + "thing": new_value + }, + ) + + cm.exception.operation_name.should.equal('UpdateIdentityProvider') + cm.exception.response['Error']['Code'].should.equal('ResourceNotFoundException') + cm.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + + +@mock_cognitoidp +def test_update_identity_provider_no_identity_provider(): + conn = boto3.client("cognito-idp", "us-west-2") + + provider_name = str(uuid.uuid4()) + provider_type = "Facebook" + value = str(uuid.uuid4()) + new_value = str(uuid.uuid4()) + user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"] + + with assert_raises(conn.exceptions.ResourceNotFoundException) as cm: + conn.update_identity_provider( + UserPoolId=user_pool_id, + ProviderName="foo", + ProviderDetails={ + "thing": new_value + }, + ) + + cm.exception.operation_name.should.equal('UpdateIdentityProvider') + cm.exception.response['Error']['Code'].should.equal('ResourceNotFoundException') + cm.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + + @mock_cognitoidp def test_delete_identity_providers(): conn = boto3.client("cognito-idp", "us-west-2") From 11ff548d147c88727b2858b2ebc730e09899f228 Mon Sep 17 00:00:00 2001 From: Tomoya Iwata Date: Sun, 17 Mar 2019 17:54:34 +0900 Subject: [PATCH 04/15] fix #2113 moto must return Http status code 201 when lambda publish_version has succeeded --- moto/awslambda/responses.py | 2 +- tests/test_awslambda/test_lambda.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index d4eb73bc3..1c43ef84b 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -183,7 +183,7 @@ class LambdaResponse(BaseResponse): fn = self.lambda_backend.publish_function(function_name) if fn: config = fn.get_configuration() - return 200, {}, json.dumps(config) + return 201, {}, json.dumps(config) else: return 404, {}, "{}" diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 7f3b44b79..34fd9c9b3 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -4,6 +4,7 @@ import base64 import botocore.client import boto3 import hashlib +from http import HTTPStatus import io import json import time @@ -471,7 +472,8 @@ def test_publish(): function_list['Functions'].should.have.length_of(1) latest_arn = function_list['Functions'][0]['FunctionArn'] - conn.publish_version(FunctionName='testFunction') + res = conn.publish_version(FunctionName='testFunction') + assert res['ResponseMetadata']['HTTPStatusCode'] == HTTPStatus.CREATED function_list = conn.list_functions() function_list['Functions'].should.have.length_of(2) @@ -853,8 +855,8 @@ def test_list_versions_by_function(): Publish=True, ) - conn.publish_version(FunctionName='testFunction') - + res = conn.publish_version(FunctionName='testFunction') + assert res['ResponseMetadata']['HTTPStatusCode'] == HTTPStatus.CREATED versions = conn.list_versions_by_function(FunctionName='testFunction') assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' From 52ad71879dda750ff8d5626888365b81938ee14b Mon Sep 17 00:00:00 2001 From: Tomoya Iwata Date: Fri, 29 Mar 2019 15:52:47 +0900 Subject: [PATCH 05/15] modified test code for python2.7 --- tests/test_awslambda/test_lambda.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 34fd9c9b3..479aaaa8a 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -4,7 +4,6 @@ import base64 import botocore.client import boto3 import hashlib -from http import HTTPStatus import io import json import time @@ -473,7 +472,7 @@ def test_publish(): latest_arn = function_list['Functions'][0]['FunctionArn'] res = conn.publish_version(FunctionName='testFunction') - assert res['ResponseMetadata']['HTTPStatusCode'] == HTTPStatus.CREATED + assert res['ResponseMetadata']['HTTPStatusCode'] == 201 function_list = conn.list_functions() function_list['Functions'].should.have.length_of(2) @@ -856,7 +855,7 @@ def test_list_versions_by_function(): ) res = conn.publish_version(FunctionName='testFunction') - assert res['ResponseMetadata']['HTTPStatusCode'] == HTTPStatus.CREATED + assert res['ResponseMetadata']['HTTPStatusCode'] == 201 versions = conn.list_versions_by_function(FunctionName='testFunction') assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' From dbdc8925e35a0bb244c265be87429f7a81d543f4 Mon Sep 17 00:00:00 2001 From: Earl Robinson Date: Fri, 29 Mar 2019 21:02:25 -0400 Subject: [PATCH 06/15] add KeyId value to kms.responses.encrypt and kms.responses.decrypt --- moto/kms/responses.py | 4 ++-- tests/test_kms/test_kms.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 2674f765c..ed6accc78 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -249,11 +249,11 @@ class KmsResponse(BaseResponse): value = self.parameters.get("Plaintext") if isinstance(value, six.text_type): value = value.encode('utf-8') - return json.dumps({"CiphertextBlob": base64.b64encode(value).decode("utf-8")}) + return json.dumps({"CiphertextBlob": base64.b64encode(value).decode("utf-8"), 'KeyId': 'key_id'}) def decrypt(self): value = self.parameters.get("CiphertextBlob") - return json.dumps({"Plaintext": base64.b64decode(value).decode("utf-8")}) + return json.dumps({"Plaintext": base64.b64decode(value).decode("utf-8"), 'KeyId': 'key_id'}) def disable_key(self): key_id = self.parameters.get('KeyId') diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 95e39a0e7..79f95fa1b 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -171,6 +171,7 @@ def test_encrypt(): conn = boto.kms.connect_to_region("us-west-2") response = conn.encrypt('key_id', 'encryptme'.encode('utf-8')) response['CiphertextBlob'].should.equal(b'ZW5jcnlwdG1l') + response['KeyId'].should.equal('key_id') @mock_kms_deprecated @@ -178,6 +179,7 @@ def test_decrypt(): conn = boto.kms.connect_to_region('us-west-2') response = conn.decrypt('ZW5jcnlwdG1l'.encode('utf-8')) response['Plaintext'].should.equal(b'encryptme') + response['KeyId'].should.equal('key_id') @mock_kms_deprecated From 6b7282f93c4e45a2b26a9f3b732e8aabb9acd232 Mon Sep 17 00:00:00 2001 From: hsuhans Date: Sat, 30 Mar 2019 23:26:50 +0800 Subject: [PATCH 07/15] Fix #2129 EC2 tag should raise ClientError when resource is empty Raise MissingParameterError exception in models/validate_resource_ids of ec2. Add ec2 create tag with empty resource test case. Add ec2 delete tag with empty resource test case. Related: #2129 Reference boto3 create_tags https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.create_tags boto3 delete_tags https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.delete_tags Amazon EC2 API Reference Actions CreateTags https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateTags.html Amazon EC2 API Reference Actions DeleteTags https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteTags.html --- moto/ec2/models.py | 2 ++ tests/test_ec2/test_tags.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index ff766d7b8..1b4a6c112 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -134,6 +134,8 @@ def utc_date_and_time(): def validate_resource_ids(resource_ids): + if not resource_ids: + raise MissingParameterError(parameter='resourceIdSet') for resource_id in resource_ids: if not is_valid_resource_id(resource_id): raise InvalidID(resource_id=resource_id) diff --git a/tests/test_ec2/test_tags.py b/tests/test_ec2/test_tags.py index c92a4f81f..2294979ba 100644 --- a/tests/test_ec2/test_tags.py +++ b/tests/test_ec2/test_tags.py @@ -5,6 +5,7 @@ import itertools import boto import boto3 +from botocore.exceptions import ClientError from boto.exception import EC2ResponseError from boto.ec2.instance import Reservation import sure # noqa @@ -451,3 +452,31 @@ def test_create_snapshot_with_tags(): }] assert snapshot['Tags'] == expected_tags + + +@mock_ec2 +def test_create_tag_empty_resource(): + # create ec2 client in us-west-1 + client = boto3.client('ec2', region_name='us-west-1') + # create tag with empty resource + with assert_raises(ClientError) as ex: + client.create_tags( + Resources=[], + Tags=[{'Key': 'Value'}] + ) + ex.exception.response['Error']['Code'].should.equal('MissingParameter') + ex.exception.response['Error']['Message'].should.equal('The request must contain the parameter resourceIdSet') + + +@mock_ec2 +def test_delete_tag_empty_resource(): + # create ec2 client in us-west-1 + client = boto3.client('ec2', region_name='us-west-1') + # delete tag with empty resource + with assert_raises(ClientError) as ex: + client.delete_tags( + Resources=[], + Tags=[{'Key': 'Value'}] + ) + ex.exception.response['Error']['Code'].should.equal('MissingParameter') + ex.exception.response['Error']['Message'].should.equal('The request must contain the parameter resourceIdSet') From b85d21b8fe9ebef52cf2b11d95edf758de1e4400 Mon Sep 17 00:00:00 2001 From: Yaroslav Admin Date: Tue, 2 Apr 2019 15:30:01 +0200 Subject: [PATCH 08/15] Fixed copy-object from unversioned bucket to versioned bucket The response of the copy-object operation was missing VersionId property when source bucket is not versioned. --- moto/s3/models.py | 19 ++++++++++--------- tests/test_s3/test_s3.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/moto/s3/models.py b/moto/s3/models.py index 37fed3335..9e4a6a766 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -87,10 +87,13 @@ class FakeKey(BaseModel): new_value = new_value.encode(DEFAULT_TEXT_ENCODING) self._value_buffer.write(new_value) - def copy(self, new_name=None): + def copy(self, new_name=None, new_is_versioned=None): r = copy.deepcopy(self) if new_name is not None: r.name = new_name + if new_is_versioned is not None: + r._is_versioned = new_is_versioned + r.refresh_version() return r def set_metadata(self, metadata, replace=False): @@ -973,17 +976,15 @@ class S3Backend(BaseBackend): dest_bucket = self.get_bucket(dest_bucket_name) key = self.get_key(src_bucket_name, src_key_name, version_id=src_version_id) - if dest_key_name != src_key_name: - key = key.copy(dest_key_name) - dest_bucket.keys[dest_key_name] = key - # By this point, the destination key must exist, or KeyError - if dest_bucket.is_versioned: - dest_bucket.keys[dest_key_name].refresh_version() + new_key = key.copy(dest_key_name, dest_bucket.is_versioned) + if storage is not None: - key.set_storage_class(storage) + new_key.set_storage_class(storage) if acl is not None: - key.set_acl(acl) + new_key.set_acl(acl) + + dest_bucket.keys[dest_key_name] = new_key def set_bucket_acl(self, bucket_name, acl): bucket = self.get_bucket(bucket_name) diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index cf45822b5..6af23849c 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1530,6 +1530,23 @@ def test_boto3_copy_object_with_versioning(): obj2_version_new.should_not.equal(obj2_version) +@mock_s3 +def test_boto3_copy_object_from_unversioned_to_versioned_bucket(): + client = boto3.client('s3', region_name='us-east-1') + + client.create_bucket(Bucket='src', CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'}) + client.create_bucket(Bucket='dest', CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'}) + client.put_bucket_versioning(Bucket='dest', VersioningConfiguration={'Status': 'Enabled'}) + + client.put_object(Bucket='src', Key='test', Body=b'content') + + obj2_version_new = client.copy_object(CopySource={'Bucket': 'src', 'Key': 'test'}, Bucket='dest', Key='test') \ + .get('VersionId') + + # VersionId should be present in the response + obj2_version_new.should_not.equal(None) + + @mock_s3 def test_boto3_deleted_versionings_list(): client = boto3.client('s3', region_name='us-east-1') From 120874e408515f5bdf91d5915f9c813c96183ad4 Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 11:00:02 +0100 Subject: [PATCH 09/15] Feature: AWS Secrets Manager list-secrets --- moto/secretsmanager/models.py | 4 ++++ moto/secretsmanager/responses.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 1350ab469..0d276c944 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -188,6 +188,10 @@ class SecretsManagerBackend(BaseBackend): return response + def list_secrets(self, max_results, next_token): + # implement here + return secret_list, next_token + available_regions = ( boto3.session.Session().get_available_regions("secretsmanager") diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index 932e7bfd7..73921a11a 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -64,3 +64,13 @@ class SecretsManagerResponse(BaseResponse): rotation_lambda_arn=rotation_lambda_arn, rotation_rules=rotation_rules ) + + def list_secrets(self): + max_results = self._get_int_param("MaxResults") + next_token = self._get_param("NextToken") + secret_list, next_token = self.secretsmanager_backend.list_secrets( + max_results=max_results, + next_token=next_token, + ) + # TODO: adjust response + return json.dumps(dict(secretList=secret_list, nextToken=next_token)) From 89e4ab93eeefa07e827a0653dd775472c34d3c48 Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 13:33:28 +0100 Subject: [PATCH 10/15] Implement ListSecrets --- IMPLEMENTATION_COVERAGE.md | 2 +- moto/secretsmanager/models.py | 25 +++++++++++-- moto/secretsmanager/responses.py | 7 ++-- .../test_secretsmanager.py | 36 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a5650f572..e206467b5 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3659,7 +3659,7 @@ - [X] get_random_password - [X] get_secret_value - [ ] list_secret_version_ids -- [ ] list_secrets +- [x] list_secrets - [ ] put_secret_value - [ ] restore_secret - [X] rotate_secret diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 0d276c944..a6aec8b81 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -189,9 +189,30 @@ class SecretsManagerBackend(BaseBackend): return response def list_secrets(self, max_results, next_token): - # implement here + # TODO implement pagination + + secret_list = [{ + "ARN": secret_arn(self.region, secret['secret_id']), + "DeletedDate": None, + "Description": "", + "KmsKeyId": "", + "LastAccessedDate": None, + "LastChangedDate": None, + "LastRotatedDate": None, + "Name": secret['name'], + "RotationEnabled": secret['rotation_enabled'], + "RotationLambdaARN": secret['rotation_lambda_arn'], + "RotationRules": { + "AutomaticallyAfterDays": secret['auto_rotate_after_days'] + }, + "SecretVersionsToStages": { + secret['version_id']: ["AWSCURRENT"] + }, + "Tags": secret['tags'] + } for secret in self.secrets.values()] + return secret_list, next_token - + available_regions = ( boto3.session.Session().get_available_regions("secretsmanager") diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index 73921a11a..ab1fe0d9d 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -4,6 +4,8 @@ from moto.core.responses import BaseResponse from .models import secretsmanager_backends +import json + class SecretsManagerResponse(BaseResponse): @@ -68,9 +70,8 @@ class SecretsManagerResponse(BaseResponse): def list_secrets(self): max_results = self._get_int_param("MaxResults") next_token = self._get_param("NextToken") - secret_list, next_token = self.secretsmanager_backend.list_secrets( + secret_list, next_token = secretsmanager_backends[self.region].list_secrets( max_results=max_results, next_token=next_token, ) - # TODO: adjust response - return json.dumps(dict(secretList=secret_list, nextToken=next_token)) + return json.dumps(dict(SecretList=secret_list, NextToken=next_token)) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 169282421..6146698d9 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -203,6 +203,42 @@ def test_describe_secret_that_does_not_match(): with assert_raises(ClientError): result = conn.get_secret_value(SecretId='i-dont-match') + +@mock_secretsmanager +def test_list_secrets_empty(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + secrets = conn.list_secrets() + + assert secrets['SecretList'] == [] + + +@mock_secretsmanager +def test_list_secrets(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + + conn.create_secret(Name='test-secret', + SecretString='foosecret') + + conn.create_secret(Name='test-secret-2', + SecretString='barsecret', + Tags=[{ + 'Key': 'a', + 'Value': '1' + }]) + + secrets = conn.list_secrets() + + assert secrets['SecretList'][0]['ARN'] is not None + assert secrets['SecretList'][0]['Name'] == 'test-secret' + assert secrets['SecretList'][1]['ARN'] is not None + assert secrets['SecretList'][1]['Name'] == 'test-secret-2' + assert secrets['SecretList'][1]['Tags'] == [{ + 'Key': 'a', + 'Value': '1' + }] + + @mock_secretsmanager def test_rotate_secret(): secret_name = 'test-secret' From 2d6be24ffcc94a69c8340a7f3c1bf0d584429a1e Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 13:54:11 +0100 Subject: [PATCH 11/15] Fix lint error --- moto/secretsmanager/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index ab1fe0d9d..f017ec0dc 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -66,7 +66,7 @@ class SecretsManagerResponse(BaseResponse): rotation_lambda_arn=rotation_lambda_arn, rotation_rules=rotation_rules ) - + def list_secrets(self): max_results = self._get_int_param("MaxResults") next_token = self._get_param("NextToken") From 7fcedcb783d8bd5fc0e944029ccaf25c647aadef Mon Sep 17 00:00:00 2001 From: Chris K Date: Fri, 5 Apr 2019 15:59:38 +0100 Subject: [PATCH 12/15] Fix: Ensure the returned next_token is None (avoid client going round in a loop) --- moto/secretsmanager/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index a6aec8b81..74cf89039 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -189,7 +189,7 @@ class SecretsManagerBackend(BaseBackend): return response def list_secrets(self, max_results, next_token): - # TODO implement pagination + # TODO implement pagination and limits secret_list = [{ "ARN": secret_arn(self.region, secret['secret_id']), @@ -211,7 +211,7 @@ class SecretsManagerBackend(BaseBackend): "Tags": secret['tags'] } for secret in self.secrets.values()] - return secret_list, next_token + return secret_list, None available_regions = ( From eff4cdfbeead3ea332f91b1fc055bb9732f3b743 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 7 Apr 2019 13:34:26 +0200 Subject: [PATCH 13/15] README: Use SVG badges for readability --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aeff847ed..56f73e28e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Join the chat at https://gitter.im/awsmoto/Lobby](https://badges.gitter.im/awsmoto/Lobby.svg)](https://gitter.im/awsmoto/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/spulec/moto.png?branch=master)](https://travis-ci.org/spulec/moto) -[![Coverage Status](https://coveralls.io/repos/spulec/moto/badge.png?branch=master)](https://coveralls.io/r/spulec/moto) +[![Build Status](https://travis-ci.org/spulec/moto.svg?branch=master)](https://travis-ci.org/spulec/moto) +[![Coverage Status](https://coveralls.io/repos/spulec/moto/badge.svg?branch=master)](https://coveralls.io/r/spulec/moto) [![Docs](https://readthedocs.org/projects/pip/badge/?version=stable)](http://docs.getmoto.org) # In a nutshell From e28bcf20ea28afab7602ef4b2e488055e406ad52 Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Mon, 15 Apr 2019 10:48:55 +0100 Subject: [PATCH 14/15] Bump Jinja2 to >=2.10.1, addresses CVE-2019-10906 Given how moto is intended to be used, and how it uses Jinja2, [CVE-2019-10906](https://nvd.nist.gov/vuln/detail/CVE-2019-10906) is unlikely to affect many users, but we should use a secure version anyway just in case moto is being used in unforeseen ways. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 99be632db..2981d9fc5 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ def read(*parts): install_requires = [ - "Jinja2>=2.7.3", + "Jinja2>=2.10.1", "boto>=2.36.0", "boto3>=1.9.86", "botocore>=1.12.86", From 4f1a1a9d1ee8cd899b5d42e9e2b33e6eaa42dfca Mon Sep 17 00:00:00 2001 From: Santosh Ananthakrishnan Date: Tue, 16 Apr 2019 19:29:48 +0000 Subject: [PATCH 15/15] [iam] create_policy_version: Fix version id calculation When creating a new IAM policy version with create_policy_version, we cannot use the length of the versions list to calculate VersionId. Keep track of the next version id to use as a non-decreasing counter. Fixes #2157 --- moto/iam/models.py | 11 +++++++++-- tests/test_iam/test_iam.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/moto/iam/models.py b/moto/iam/models.py index 8937d262d..fd97d5eb3 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -48,7 +48,13 @@ class Policy(BaseModel): self.description = description or '' self.id = random_policy_id() self.path = path or '/' - self.default_version_id = default_version_id or 'v1' + + if default_version_id: + self.default_version_id = default_version_id + self.next_version_num = int(default_version_id.lstrip('v')) + 1 + else: + self.default_version_id = 'v1' + self.next_version_num = 2 self.versions = [PolicyVersion(self.arn, document, True)] self.create_datetime = datetime.now(pytz.utc) @@ -716,7 +722,8 @@ class IAMBackend(BaseBackend): raise IAMNotFoundException("Policy not found") version = PolicyVersion(policy_arn, policy_document, set_as_default) policy.versions.append(version) - version.version_id = 'v{0}'.format(len(policy.versions)) + version.version_id = 'v{0}'.format(policy.next_version_num) + policy.next_version_num += 1 if set_as_default: policy.default_version_id = version.version_id return version diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index ceec5e06a..b01f8ab48 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -303,8 +303,17 @@ def test_create_policy_versions(): PolicyDocument='{"some":"policy"}') version = conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", - PolicyDocument='{"some":"policy"}') + PolicyDocument='{"some":"policy"}', + SetAsDefault=True) version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'}) + version.get('PolicyVersion').get('VersionId').should.equal("v2") + conn.delete_policy_version( + PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + VersionId="v1") + version = conn.create_policy_version( + PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + PolicyDocument='{"some":"policy"}') + version.get('PolicyVersion').get('VersionId').should.equal("v3") @mock_iam