This commit is contained in:
TBurnip 2019-04-18 20:05:22 +01:00
commit fcc83318fc
20 changed files with 298 additions and 24 deletions

View File

@ -916,7 +916,7 @@
- [ ] update_auth_event_feedback - [ ] update_auth_event_feedback
- [ ] update_device_status - [ ] update_device_status
- [ ] update_group - [ ] update_group
- [ ] update_identity_provider - [x] update_identity_provider
- [ ] update_resource_server - [ ] update_resource_server
- [ ] update_user_attributes - [ ] update_user_attributes
- [ ] update_user_pool - [ ] update_user_pool
@ -2334,7 +2334,7 @@
- [ ] update_service_specific_credential - [ ] update_service_specific_credential
- [X] update_signing_certificate - [X] update_signing_certificate
- [ ] update_ssh_public_key - [ ] update_ssh_public_key
- [ ] update_user - [X] update_user
- [X] upload_server_certificate - [X] upload_server_certificate
- [X] upload_signing_certificate - [X] upload_signing_certificate
- [ ] upload_ssh_public_key - [ ] upload_ssh_public_key
@ -3659,7 +3659,7 @@
- [X] get_random_password - [X] get_random_password
- [X] get_secret_value - [X] get_secret_value
- [ ] list_secret_version_ids - [ ] list_secret_version_ids
- [ ] list_secrets - [x] list_secrets
- [ ] put_secret_value - [ ] put_secret_value
- [ ] restore_secret - [ ] restore_secret
- [X] rotate_secret - [X] rotate_secret

View File

@ -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) [![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) [![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.png?branch=master)](https://coveralls.io/r/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) [![Docs](https://readthedocs.org/projects/pip/badge/?version=stable)](http://docs.getmoto.org)
# In a nutshell # In a nutshell

View File

@ -183,7 +183,7 @@ class LambdaResponse(BaseResponse):
fn = self.lambda_backend.publish_function(function_name) fn = self.lambda_backend.publish_function(function_name)
if fn: if fn:
config = fn.get_configuration() config = fn.get_configuration()
return 200, {}, json.dumps(config) return 201, {}, json.dumps(config)
else: else:
return 404, {}, "{}" return 404, {}, "{}"

View File

@ -426,6 +426,19 @@ class CognitoIdpBackend(BaseBackend):
return identity_provider 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): def delete_identity_provider(self, user_pool_id, name):
user_pool = self.user_pools.get(user_pool_id) user_pool = self.user_pools.get(user_pool_id)
if not user_pool: if not user_pool:

View File

@ -143,6 +143,14 @@ class CognitoIdpResponse(BaseResponse):
"IdentityProvider": identity_provider.to_json(extended=True) "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): def delete_identity_provider(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
name = self._get_param("ProviderName") name = self._get_param("ProviderName")

View File

@ -134,6 +134,8 @@ def utc_date_and_time():
def validate_resource_ids(resource_ids): def validate_resource_ids(resource_ids):
if not resource_ids:
raise MissingParameterError(parameter='resourceIdSet')
for resource_id in resource_ids: for resource_id in resource_ids:
if not is_valid_resource_id(resource_id): if not is_valid_resource_id(resource_id):
raise InvalidID(resource_id=resource_id) raise InvalidID(resource_id=resource_id)

View File

@ -48,7 +48,13 @@ class Policy(BaseModel):
self.description = description or '' self.description = description or ''
self.id = random_policy_id() self.id = random_policy_id()
self.path = path or '/' 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.versions = [PolicyVersion(self.arn, document, True)]
self.create_datetime = datetime.now(pytz.utc) self.create_datetime = datetime.now(pytz.utc)
@ -716,7 +722,8 @@ class IAMBackend(BaseBackend):
raise IAMNotFoundException("Policy not found") raise IAMNotFoundException("Policy not found")
version = PolicyVersion(policy_arn, policy_document, set_as_default) version = PolicyVersion(policy_arn, policy_document, set_as_default)
policy.versions.append(version) 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: if set_as_default:
policy.default_version_id = version.version_id policy.default_version_id = version.version_id
return version return version
@ -892,6 +899,18 @@ class IAMBackend(BaseBackend):
return users 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): def list_roles(self, path_prefix, marker, max_items):
roles = None roles = None
try: try:

View File

@ -440,6 +440,18 @@ class IamResponse(BaseResponse):
template = self.response_template(LIST_USERS_TEMPLATE) template = self.response_template(LIST_USERS_TEMPLATE)
return template.render(action='List', users=users) 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): def create_login_profile(self):
user_name = self._get_param('UserName') user_name = self._get_param('UserName')
password = self._get_param('Password') password = self._get_param('Password')

View File

@ -249,11 +249,11 @@ class KmsResponse(BaseResponse):
value = self.parameters.get("Plaintext") value = self.parameters.get("Plaintext")
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
value = value.encode('utf-8') 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): def decrypt(self):
value = self.parameters.get("CiphertextBlob") 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): def disable_key(self):
key_id = self.parameters.get('KeyId') key_id = self.parameters.get('KeyId')

View File

@ -87,10 +87,13 @@ class FakeKey(BaseModel):
new_value = new_value.encode(DEFAULT_TEXT_ENCODING) new_value = new_value.encode(DEFAULT_TEXT_ENCODING)
self._value_buffer.write(new_value) 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) r = copy.deepcopy(self)
if new_name is not None: if new_name is not None:
r.name = new_name r.name = new_name
if new_is_versioned is not None:
r._is_versioned = new_is_versioned
r.refresh_version()
return r return r
def set_metadata(self, metadata, replace=False): def set_metadata(self, metadata, replace=False):
@ -973,17 +976,15 @@ class S3Backend(BaseBackend):
dest_bucket = self.get_bucket(dest_bucket_name) dest_bucket = self.get_bucket(dest_bucket_name)
key = self.get_key(src_bucket_name, src_key_name, key = self.get_key(src_bucket_name, src_key_name,
version_id=src_version_id) 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 new_key = key.copy(dest_key_name, dest_bucket.is_versioned)
if dest_bucket.is_versioned:
dest_bucket.keys[dest_key_name].refresh_version()
if storage is not None: if storage is not None:
key.set_storage_class(storage) new_key.set_storage_class(storage)
if acl is not None: 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): def set_bucket_acl(self, bucket_name, acl):
bucket = self.get_bucket(bucket_name) bucket = self.get_bucket(bucket_name)

View File

@ -188,6 +188,31 @@ class SecretsManagerBackend(BaseBackend):
return response return response
def list_secrets(self, max_results, next_token):
# TODO implement pagination and limits
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, None
available_regions = ( available_regions = (
boto3.session.Session().get_available_regions("secretsmanager") boto3.session.Session().get_available_regions("secretsmanager")

View File

@ -4,6 +4,8 @@ from moto.core.responses import BaseResponse
from .models import secretsmanager_backends from .models import secretsmanager_backends
import json
class SecretsManagerResponse(BaseResponse): class SecretsManagerResponse(BaseResponse):
@ -64,3 +66,12 @@ class SecretsManagerResponse(BaseResponse):
rotation_lambda_arn=rotation_lambda_arn, rotation_lambda_arn=rotation_lambda_arn,
rotation_rules=rotation_rules 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 = secretsmanager_backends[self.region].list_secrets(
max_results=max_results,
next_token=next_token,
)
return json.dumps(dict(SecretList=secret_list, NextToken=next_token))

View File

@ -19,7 +19,7 @@ def read(*parts):
install_requires = [ install_requires = [
"Jinja2>=2.7.3", "Jinja2>=2.10.1",
"boto>=2.36.0", "boto>=2.36.0",
"boto3>=1.9.86", "boto3>=1.9.86",
"botocore>=1.12.86", "botocore>=1.12.86",

View File

@ -471,7 +471,8 @@ def test_publish():
function_list['Functions'].should.have.length_of(1) function_list['Functions'].should.have.length_of(1)
latest_arn = function_list['Functions'][0]['FunctionArn'] latest_arn = function_list['Functions'][0]['FunctionArn']
conn.publish_version(FunctionName='testFunction') res = conn.publish_version(FunctionName='testFunction')
assert res['ResponseMetadata']['HTTPStatusCode'] == 201
function_list = conn.list_functions() function_list = conn.list_functions()
function_list['Functions'].should.have.length_of(2) function_list['Functions'].should.have.length_of(2)
@ -853,8 +854,8 @@ def test_list_versions_by_function():
Publish=True, Publish=True,
) )
conn.publish_version(FunctionName='testFunction') res = conn.publish_version(FunctionName='testFunction')
assert res['ResponseMetadata']['HTTPStatusCode'] == 201
versions = conn.list_versions_by_function(FunctionName='testFunction') versions = conn.list_versions_by_function(FunctionName='testFunction')
assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST'

View File

@ -484,6 +484,82 @@ def test_describe_identity_providers():
result["IdentityProvider"]["ProviderDetails"]["thing"].should.equal(value) 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_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 @mock_cognitoidp
def test_delete_identity_providers(): def test_delete_identity_providers():
conn = boto3.client("cognito-idp", "us-west-2") conn = boto3.client("cognito-idp", "us-west-2")

View File

@ -5,6 +5,7 @@ import itertools
import boto import boto
import boto3 import boto3
from botocore.exceptions import ClientError
from boto.exception import EC2ResponseError from boto.exception import EC2ResponseError
from boto.ec2.instance import Reservation from boto.ec2.instance import Reservation
import sure # noqa import sure # noqa
@ -451,3 +452,31 @@ def test_create_snapshot_with_tags():
}] }]
assert snapshot['Tags'] == expected_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')

View File

@ -303,8 +303,17 @@ def test_create_policy_versions():
PolicyDocument='{"some":"policy"}') PolicyDocument='{"some":"policy"}')
version = conn.create_policy_version( version = conn.create_policy_version(
PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", 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('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 @mock_iam
@ -401,6 +410,19 @@ def test_get_user():
conn.get_user('my-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() @mock_iam_deprecated()
def test_get_current_user(): def test_get_current_user():
"""If no user is specific, IAM returns the current user""" """If no user is specific, IAM returns the current user"""

View File

@ -171,6 +171,7 @@ def test_encrypt():
conn = boto.kms.connect_to_region("us-west-2") conn = boto.kms.connect_to_region("us-west-2")
response = conn.encrypt('key_id', 'encryptme'.encode('utf-8')) response = conn.encrypt('key_id', 'encryptme'.encode('utf-8'))
response['CiphertextBlob'].should.equal(b'ZW5jcnlwdG1l') response['CiphertextBlob'].should.equal(b'ZW5jcnlwdG1l')
response['KeyId'].should.equal('key_id')
@mock_kms_deprecated @mock_kms_deprecated
@ -178,6 +179,7 @@ def test_decrypt():
conn = boto.kms.connect_to_region('us-west-2') conn = boto.kms.connect_to_region('us-west-2')
response = conn.decrypt('ZW5jcnlwdG1l'.encode('utf-8')) response = conn.decrypt('ZW5jcnlwdG1l'.encode('utf-8'))
response['Plaintext'].should.equal(b'encryptme') response['Plaintext'].should.equal(b'encryptme')
response['KeyId'].should.equal('key_id')
@mock_kms_deprecated @mock_kms_deprecated

View File

@ -1530,6 +1530,23 @@ def test_boto3_copy_object_with_versioning():
obj2_version_new.should_not.equal(obj2_version) 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 @mock_s3
def test_boto3_deleted_versionings_list(): def test_boto3_deleted_versionings_list():
client = boto3.client('s3', region_name='us-east-1') client = boto3.client('s3', region_name='us-east-1')

View File

@ -203,6 +203,42 @@ def test_describe_secret_that_does_not_match():
with assert_raises(ClientError): with assert_raises(ClientError):
result = conn.get_secret_value(SecretId='i-dont-match') 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 @mock_secretsmanager
def test_rotate_secret(): def test_rotate_secret():
secret_name = 'test-secret' secret_name = 'test-secret'