Merge branch 'master' into jsondiff-1.1.2

This commit is contained in:
Steve Pulec 2018-12-28 19:50:34 -05:00 committed by GitHub
commit 0c5010989a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 974 additions and 186 deletions

2
.gitignore vendored
View File

@ -15,4 +15,4 @@ python_env
.ropeproject/ .ropeproject/
.pytest_cache/ .pytest_cache/
venv/ venv/
.vscode/

View File

@ -23,6 +23,8 @@ matrix:
sudo: true sudo: true
before_install: before_install:
- export BOTO_CONFIG=/dev/null - export BOTO_CONFIG=/dev/null
- export AWS_SECRET_ACCESS_KEY=foobar_secret
- export AWS_ACCESS_KEY_ID=foobar_key
install: install:
# We build moto first so the docker container doesn't try to compile it as well, also note we don't use # We build moto first so the docker container doesn't try to compile it as well, also note we don't use
# -d for docker run so the logs show up in travis # -d for docker run so the logs show up in travis
@ -32,8 +34,6 @@ install:
if [ "$TEST_SERVER_MODE" = "true" ]; then if [ "$TEST_SERVER_MODE" = "true" ]; then
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh & docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh &
export AWS_SECRET_ACCESS_KEY=foobar_secret
export AWS_ACCESS_KEY_ID=foobar_key
fi fi
travis_retry pip install boto==2.45.0 travis_retry pip install boto==2.45.0
travis_retry pip install boto3 travis_retry pip install boto3

View File

@ -1,6 +1,11 @@
Moto Changelog Moto Changelog
=================== ===================
1.3.7
-----
* Switch from mocking requests to using before-send for AWS calls
1.3.6 1.3.6
----- -----

View File

@ -259,7 +259,7 @@ It uses flask, which isn't a default dependency. You can install the
server 'extra' package with: server 'extra' package with:
```python ```python
pip install moto[server] pip install "moto[server]"
``` ```
You can then start it running a service: You can then start it running a service:

View File

@ -3,7 +3,7 @@ import logging
# logging.getLogger('boto').setLevel(logging.CRITICAL) # logging.getLogger('boto').setLevel(logging.CRITICAL)
__title__ = 'moto' __title__ = 'moto'
__version__ = '1.3.6' __version__ = '1.3.7'
from .acm import mock_acm # flake8: noqa from .acm import mock_acm # flake8: noqa
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa

View File

@ -10,6 +10,7 @@ from boto3.session import Session
import responses import responses
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from .utils import create_id from .utils import create_id
from moto.core.utils import path_url
from .exceptions import StageNotFoundException, ApiKeyNotFoundException from .exceptions import StageNotFoundException, ApiKeyNotFoundException
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}" STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
@ -372,7 +373,8 @@ class RestAPI(BaseModel):
# TODO deal with no matching resource # TODO deal with no matching resource
def resource_callback(self, request): def resource_callback(self, request):
path_after_stage_name = '/'.join(request.path_url.split("/")[2:]) path = path_url(request.url)
path_after_stage_name = '/'.join(path.split("/")[2:])
if not path_after_stage_name: if not path_after_stage_name:
path_after_stage_name = '/' path_after_stage_name = '/'

View File

@ -508,6 +508,15 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
{% else %} {% else %}
<LoadBalancerNames/> <LoadBalancerNames/>
{% endif %} {% endif %}
{% if group.target_group_arns %}
<TargetGroupARNs>
{% for target_group_arn in group.target_group_arns %}
<member>{{ target_group_arn }}</member>
{% endfor %}
</TargetGroupARNs>
{% else %}
<TargetGroupARNs/>
{% endif %}
<MinSize>{{ group.min_size }}</MinSize> <MinSize>{{ group.min_size }}</MinSize>
{% if group.vpc_zone_identifier %} {% if group.vpc_zone_identifier %}
<VPCZoneIdentifier>{{ group.vpc_zone_identifier }}</VPCZoneIdentifier> <VPCZoneIdentifier>{{ group.vpc_zone_identifier }}</VPCZoneIdentifier>

View File

@ -7,7 +7,7 @@ try:
except ImportError: except ImportError:
from urllib.parse import unquote from urllib.parse import unquote
from moto.core.utils import amz_crc32, amzn_request_id from moto.core.utils import amz_crc32, amzn_request_id, path_url
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from .models import lambda_backends from .models import lambda_backends
@ -94,7 +94,7 @@ class LambdaResponse(BaseResponse):
return self._add_policy(request, full_url, headers) return self._add_policy(request, full_url, headers)
def _add_policy(self, request, full_url, headers): def _add_policy(self, request, full_url, headers):
path = request.path if hasattr(request, 'path') else request.path_url path = request.path if hasattr(request, 'path') else path_url(request.url)
function_name = path.split('/')[-2] function_name = path.split('/')[-2]
if self.lambda_backend.get_function(function_name): if self.lambda_backend.get_function(function_name):
policy = request.body.decode('utf8') policy = request.body.decode('utf8')
@ -104,7 +104,7 @@ class LambdaResponse(BaseResponse):
return 404, {}, "{}" return 404, {}, "{}"
def _get_policy(self, request, full_url, headers): def _get_policy(self, request, full_url, headers):
path = request.path if hasattr(request, 'path') else request.path_url path = request.path if hasattr(request, 'path') else path_url(request.url)
function_name = path.split('/')[-2] function_name = path.split('/')[-2]
if self.lambda_backend.get_function(function_name): if self.lambda_backend.get_function(function_name):
lambda_function = self.lambda_backend.get_function(function_name) lambda_function = self.lambda_backend.get_function(function_name)

View File

@ -1,6 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import functools
import itertools
import json import json
import os import os
import time import time
@ -20,6 +22,39 @@ UserStatus = {
} }
def paginate(limit, start_arg="next_token", limit_arg="max_results"):
"""Returns a limited result list, and an offset into list of remaining items
Takes the next_token, and max_results kwargs given to a function and handles
the slicing of the results. The kwarg `next_token` is the offset into the
list to begin slicing from. `max_results` is the size of the result required
If the max_results is not supplied then the `limit` parameter is used as a
default
:param limit_arg: the name of argument in the decorated function that
controls amount of items returned
:param start_arg: the name of the argument in the decorated that provides
the starting offset
:param limit: A default maximum items to return
:return: a tuple containing a list of items, and the offset into the list
"""
default_start = 0
def outer_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = int(default_start if kwargs.get(start_arg) is None else kwargs[start_arg])
lim = int(limit if kwargs.get(limit_arg) is None else kwargs[limit_arg])
stop = start + lim
result = func(*args, **kwargs)
limited_results = list(itertools.islice(result, start, stop))
next_token = stop if stop < len(result) else None
return limited_results, next_token
return wrapper
return outer_wrapper
class CognitoIdpUserPool(BaseModel): class CognitoIdpUserPool(BaseModel):
def __init__(self, region, name, extended_config): def __init__(self, region, name, extended_config):
@ -242,7 +277,8 @@ class CognitoIdpBackend(BaseBackend):
self.user_pools[user_pool.id] = user_pool self.user_pools[user_pool.id] = user_pool
return user_pool return user_pool
def list_user_pools(self): @paginate(60)
def list_user_pools(self, max_results=None, next_token=None):
return self.user_pools.values() return self.user_pools.values()
def describe_user_pool(self, user_pool_id): def describe_user_pool(self, user_pool_id):
@ -289,7 +325,8 @@ class CognitoIdpBackend(BaseBackend):
user_pool.clients[user_pool_client.id] = user_pool_client user_pool.clients[user_pool_client.id] = user_pool_client
return user_pool_client return user_pool_client
def list_user_pool_clients(self, user_pool_id): @paginate(60)
def list_user_pool_clients(self, user_pool_id, max_results=None, next_token=None):
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:
raise ResourceNotFoundError(user_pool_id) raise ResourceNotFoundError(user_pool_id)
@ -339,7 +376,8 @@ class CognitoIdpBackend(BaseBackend):
user_pool.identity_providers[name] = identity_provider user_pool.identity_providers[name] = identity_provider
return identity_provider return identity_provider
def list_identity_providers(self, user_pool_id): @paginate(60)
def list_identity_providers(self, user_pool_id, max_results=None, next_token=None):
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:
raise ResourceNotFoundError(user_pool_id) raise ResourceNotFoundError(user_pool_id)
@ -387,7 +425,8 @@ class CognitoIdpBackend(BaseBackend):
return user_pool.users[username] return user_pool.users[username]
def list_users(self, user_pool_id): @paginate(60, "pagination_token", "limit")
def list_users(self, user_pool_id, pagination_token=None, limit=None):
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:
raise ResourceNotFoundError(user_pool_id) raise ResourceNotFoundError(user_pool_id)

View File

@ -22,10 +22,17 @@ class CognitoIdpResponse(BaseResponse):
}) })
def list_user_pools(self): def list_user_pools(self):
user_pools = cognitoidp_backends[self.region].list_user_pools() max_results = self._get_param("MaxResults")
return json.dumps({ next_token = self._get_param("NextToken", "0")
"UserPools": [user_pool.to_json() for user_pool in user_pools] user_pools, next_token = cognitoidp_backends[self.region].list_user_pools(
}) max_results=max_results, next_token=next_token
)
response = {
"UserPools": [user_pool.to_json() for user_pool in user_pools],
}
if next_token:
response["NextToken"] = str(next_token)
return json.dumps(response)
def describe_user_pool(self): def describe_user_pool(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
@ -72,10 +79,16 @@ class CognitoIdpResponse(BaseResponse):
def list_user_pool_clients(self): def list_user_pool_clients(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
user_pool_clients = cognitoidp_backends[self.region].list_user_pool_clients(user_pool_id) max_results = self._get_param("MaxResults")
return json.dumps({ next_token = self._get_param("NextToken", "0")
user_pool_clients, next_token = cognitoidp_backends[self.region].list_user_pool_clients(user_pool_id,
max_results=max_results, next_token=next_token)
response = {
"UserPoolClients": [user_pool_client.to_json() for user_pool_client in user_pool_clients] "UserPoolClients": [user_pool_client.to_json() for user_pool_client in user_pool_clients]
}) }
if next_token:
response["NextToken"] = str(next_token)
return json.dumps(response)
def describe_user_pool_client(self): def describe_user_pool_client(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
@ -110,10 +123,17 @@ class CognitoIdpResponse(BaseResponse):
def list_identity_providers(self): def list_identity_providers(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
identity_providers = cognitoidp_backends[self.region].list_identity_providers(user_pool_id) max_results = self._get_param("MaxResults")
return json.dumps({ next_token = self._get_param("NextToken", "0")
identity_providers, next_token = cognitoidp_backends[self.region].list_identity_providers(
user_pool_id, max_results=max_results, next_token=next_token
)
response = {
"Providers": [identity_provider.to_json() for identity_provider in identity_providers] "Providers": [identity_provider.to_json() for identity_provider in identity_providers]
}) }
if next_token:
response["NextToken"] = str(next_token)
return json.dumps(response)
def describe_identity_provider(self): def describe_identity_provider(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
@ -155,10 +175,15 @@ class CognitoIdpResponse(BaseResponse):
def list_users(self): def list_users(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")
users = cognitoidp_backends[self.region].list_users(user_pool_id) limit = self._get_param("Limit")
return json.dumps({ token = self._get_param("PaginationToken")
"Users": [user.to_json(extended=True) for user in users] users, token = cognitoidp_backends[self.region].list_users(user_pool_id,
}) limit=limit,
pagination_token=token)
response = {"Users": [user.to_json(extended=True) for user in users]}
if token:
response["PaginationToken"] = str(token)
return json.dumps(response)
def admin_disable_user(self): def admin_disable_user(self):
user_pool_id = self._get_param("UserPoolId") user_pool_id = self._get_param("UserPoolId")

View File

@ -2,11 +2,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from collections import defaultdict
import functools import functools
import inspect import inspect
import re import re
import six import six
from io import BytesIO
from collections import defaultdict
from botocore.handlers import BUILTIN_HANDLERS
from botocore.awsrequest import AWSResponse
from moto import settings from moto import settings
import responses import responses
@ -233,7 +236,111 @@ class ResponsesMockAWS(BaseMockAWS):
pass pass
MockAWS = ResponsesMockAWS BOTOCORE_HTTP_METHODS = [
'GET', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'
]
class MockRawResponse(BytesIO):
def __init__(self, input):
if isinstance(input, six.text_type):
input = input.encode('utf-8')
super(MockRawResponse, self).__init__(input)
def stream(self, **kwargs):
contents = self.read()
while contents:
yield contents
contents = self.read()
class BotocoreStubber(object):
def __init__(self):
self.enabled = False
self.methods = defaultdict(list)
def reset(self):
self.methods.clear()
def register_response(self, method, pattern, response):
matchers = self.methods[method]
matchers.append((pattern, response))
def __call__(self, event_name, request, **kwargs):
if not self.enabled:
return None
response = None
response_callback = None
found_index = None
matchers = self.methods.get(request.method)
base_url = request.url.split('?', 1)[0]
for i, (pattern, callback) in enumerate(matchers):
if pattern.match(base_url):
if found_index is None:
found_index = i
response_callback = callback
else:
matchers.pop(found_index)
break
if response_callback is not None:
for header, value in request.headers.items():
if isinstance(value, six.binary_type):
request.headers[header] = value.decode('utf-8')
status, headers, body = response_callback(request, request.url, request.headers)
body = MockRawResponse(body)
response = AWSResponse(request.url, status, headers, body)
return response
botocore_stubber = BotocoreStubber()
BUILTIN_HANDLERS.append(('before-send', botocore_stubber))
class BotocoreEventMockAWS(BaseMockAWS):
def reset(self):
botocore_stubber.reset()
responses_mock.reset()
def enable_patching(self):
botocore_stubber.enabled = True
for method in BOTOCORE_HTTP_METHODS:
for backend in self.backends_for_urls.values():
for key, value in backend.urls.items():
pattern = re.compile(key)
botocore_stubber.register_response(method, pattern, value)
if not hasattr(responses_mock, '_patcher') or not hasattr(responses_mock._patcher, 'target'):
responses_mock.start()
for method in RESPONSES_METHODS:
# for backend in default_backends.values():
for backend in self.backends_for_urls.values():
for key, value in backend.urls.items():
responses_mock.add(
CallbackResponse(
method=method,
url=re.compile(key),
callback=convert_flask_to_responses_response(value),
stream=True,
match_querystring=False,
)
)
def disable_patching(self):
botocore_stubber.enabled = False
self.reset()
try:
responses_mock.stop()
except RuntimeError:
pass
MockAWS = BotocoreEventMockAWS
class ServerModeMockAWS(BaseMockAWS): class ServerModeMockAWS(BaseMockAWS):

View File

@ -8,6 +8,7 @@ import random
import re import re
import six import six
import string import string
from six.moves.urllib.parse import urlparse
REQUEST_ID_LONG = string.digits + string.ascii_uppercase REQUEST_ID_LONG = string.digits + string.ascii_uppercase
@ -286,3 +287,13 @@ def amzn_request_id(f):
return status, headers, body return status, headers, body
return _wrapper return _wrapper
def path_url(url):
parsed_url = urlparse(url)
path = parsed_url.path
if not path:
path = '/'
if parsed_url.query:
path = path + '?' + parsed_url.query
return path

View File

@ -8,4 +8,6 @@ class ServiceNotFoundException(RESTError):
def __init__(self, service_name): def __init__(self, service_name):
super(ServiceNotFoundException, self).__init__( super(ServiceNotFoundException, self).__init__(
error_type="ServiceNotFoundException", error_type="ServiceNotFoundException",
message="The service {0} does not exist".format(service_name)) message="The service {0} does not exist".format(service_name),
template='error_json',
)

View File

@ -613,13 +613,11 @@ DESCRIBE_STEP_TEMPLATE = """<DescribeStepResponse xmlns="http://elasticmapreduce
<Id>{{ step.id }}</Id> <Id>{{ step.id }}</Id>
<Name>{{ step.name | escape }}</Name> <Name>{{ step.name | escape }}</Name>
<Status> <Status>
<!-- does not exist for botocore 1.4.28
<FailureDetails> <FailureDetails>
<Reason/> <Reason/>
<Message/> <Message/>
<LogFile/> <LogFile/>
</FailureDetails> </FailureDetails>
-->
<State>{{ step.state }}</State> <State>{{ step.state }}</State>
<StateChangeReason>{{ step.state_change_reason }}</StateChangeReason> <StateChangeReason>{{ step.state_change_reason }}</StateChangeReason>
<Timeline> <Timeline>

View File

@ -24,3 +24,11 @@ class IAMReportNotPresentException(RESTError):
def __init__(self, message): def __init__(self, message):
super(IAMReportNotPresentException, self).__init__( super(IAMReportNotPresentException, self).__init__(
"ReportNotPresent", message) "ReportNotPresent", message)
class MalformedCertificate(RESTError):
code = 400
def __init__(self, cert):
super(MalformedCertificate, self).__init__(
'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert))

View File

@ -1,14 +1,18 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import base64 import base64
import sys
from datetime import datetime from datetime import datetime
import json import json
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import pytz import pytz
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_without_milliseconds from moto.core.utils import iso_8601_datetime_without_milliseconds
from .aws_managed_policies import aws_managed_policies_data from .aws_managed_policies import aws_managed_policies_data
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
ACCOUNT_ID = 123456789012 ACCOUNT_ID = 123456789012
@ -117,6 +121,7 @@ class Role(BaseModel):
self.path = path self.path = path
self.policies = {} self.policies = {}
self.managed_policies = {} self.managed_policies = {}
self.create_date = datetime.now(pytz.utc)
@classmethod @classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
@ -168,6 +173,7 @@ class InstanceProfile(BaseModel):
self.name = name self.name = name
self.path = path self.path = path
self.roles = roles if roles else [] self.roles = roles if roles else []
self.create_date = datetime.now(pytz.utc)
@classmethod @classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
@ -213,6 +219,16 @@ class Certificate(BaseModel):
return "arn:aws:iam::{0}:server-certificate{1}{2}".format(ACCOUNT_ID, self.path, self.cert_name) return "arn:aws:iam::{0}:server-certificate{1}{2}".format(ACCOUNT_ID, self.path, self.cert_name)
class SigningCertificate(BaseModel):
def __init__(self, id, user_name, body):
self.id = id
self.user_name = user_name
self.body = body
self.upload_date = datetime.strftime(datetime.utcnow(), "%Y-%m-%d-%H-%M-%S")
self.status = 'Active'
class AccessKey(BaseModel): class AccessKey(BaseModel):
def __init__(self, user_name): def __init__(self, user_name):
@ -297,6 +313,7 @@ class User(BaseModel):
self.access_keys = [] self.access_keys = []
self.password = None self.password = None
self.password_reset_required = False self.password_reset_required = False
self.signing_certificates = {}
@property @property
def arn(self): def arn(self):
@ -765,6 +782,48 @@ class IAMBackend(BaseBackend):
return users return users
def upload_signing_certificate(self, user_name, body):
user = self.get_user(user_name)
cert_id = random_resource_id(size=32)
# Validate the signing cert:
try:
if sys.version_info < (3, 0):
data = bytes(body)
else:
data = bytes(body, 'utf8')
x509.load_pem_x509_certificate(data, default_backend())
except Exception:
raise MalformedCertificate(body)
user.signing_certificates[cert_id] = SigningCertificate(cert_id, user_name, body)
return user.signing_certificates[cert_id]
def delete_signing_certificate(self, user_name, cert_id):
user = self.get_user(user_name)
try:
del user.signing_certificates[cert_id]
except KeyError:
raise IAMNotFoundException("The Certificate with id {id} cannot be found.".format(id=cert_id))
def list_signing_certificates(self, user_name):
user = self.get_user(user_name)
return list(user.signing_certificates.values())
def update_signing_certificate(self, user_name, cert_id, status):
user = self.get_user(user_name)
try:
user.signing_certificates[cert_id].status = status
except KeyError:
raise IAMNotFoundException("The Certificate with id {id} cannot be found.".format(id=cert_id))
def create_login_profile(self, user_name, password): def create_login_profile(self, user_name, password):
# This does not currently deal with PasswordPolicyViolation. # This does not currently deal with PasswordPolicyViolation.
user = self.get_user(user_name) user = self.get_user(user_name)

View File

@ -201,7 +201,7 @@ class IamResponse(BaseResponse):
def create_instance_profile(self): def create_instance_profile(self):
profile_name = self._get_param('InstanceProfileName') profile_name = self._get_param('InstanceProfileName')
path = self._get_param('Path') path = self._get_param('Path', '/')
profile = iam_backend.create_instance_profile( profile = iam_backend.create_instance_profile(
profile_name, path, role_ids=[]) profile_name, path, role_ids=[])
@ -552,6 +552,38 @@ class IamResponse(BaseResponse):
roles=account_details['roles'] roles=account_details['roles']
) )
def upload_signing_certificate(self):
user_name = self._get_param('UserName')
cert_body = self._get_param('CertificateBody')
cert = iam_backend.upload_signing_certificate(user_name, cert_body)
template = self.response_template(UPLOAD_SIGNING_CERTIFICATE_TEMPLATE)
return template.render(cert=cert)
def update_signing_certificate(self):
user_name = self._get_param('UserName')
cert_id = self._get_param('CertificateId')
status = self._get_param('Status')
iam_backend.update_signing_certificate(user_name, cert_id, status)
template = self.response_template(UPDATE_SIGNING_CERTIFICATE_TEMPLATE)
return template.render()
def delete_signing_certificate(self):
user_name = self._get_param('UserName')
cert_id = self._get_param('CertificateId')
iam_backend.delete_signing_certificate(user_name, cert_id)
template = self.response_template(DELETE_SIGNING_CERTIFICATE_TEMPLATE)
return template.render()
def list_signing_certificates(self):
user_name = self._get_param('UserName')
certs = iam_backend.list_signing_certificates(user_name)
template = self.response_template(LIST_SIGNING_CERTIFICATES_TEMPLATE)
return template.render(user_name=user_name, certificates=certs)
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse> ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
<ResponseMetadata> <ResponseMetadata>
@ -734,7 +766,7 @@ CREATE_INSTANCE_PROFILE_TEMPLATE = """<CreateInstanceProfileResponse xmlns="http
<InstanceProfileName>{{ profile.name }}</InstanceProfileName> <InstanceProfileName>{{ profile.name }}</InstanceProfileName>
<Path>{{ profile.path }}</Path> <Path>{{ profile.path }}</Path>
<Arn>{{ profile.arn }}</Arn> <Arn>{{ profile.arn }}</Arn>
<CreateDate>2012-05-09T16:11:10.222Z</CreateDate> <CreateDate>{{ profile.create_date }}</CreateDate>
</InstanceProfile> </InstanceProfile>
</CreateInstanceProfileResult> </CreateInstanceProfileResult>
<ResponseMetadata> <ResponseMetadata>
@ -753,7 +785,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -761,7 +793,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
<InstanceProfileName>{{ profile.name }}</InstanceProfileName> <InstanceProfileName>{{ profile.name }}</InstanceProfileName>
<Path>{{ profile.path }}</Path> <Path>{{ profile.path }}</Path>
<Arn>{{ profile.arn }}</Arn> <Arn>{{ profile.arn }}</Arn>
<CreateDate>2012-05-09T16:11:10Z</CreateDate> <CreateDate>{{ profile.create_date }}</CreateDate>
</InstanceProfile> </InstanceProfile>
</GetInstanceProfileResult> </GetInstanceProfileResult>
<ResponseMetadata> <ResponseMetadata>
@ -776,7 +808,7 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-08T23:34:01.495Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</Role> </Role>
</CreateRoleResult> </CreateRoleResult>
@ -803,7 +835,7 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-08T23:34:01Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</Role> </Role>
</GetRoleResult> </GetRoleResult>
@ -834,7 +866,7 @@ LIST_ROLES_TEMPLATE = """<ListRolesResponse xmlns="https://iam.amazonaws.com/doc
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -865,7 +897,7 @@ CREATE_POLICY_VERSION_TEMPLATE = """<CreatePolicyVersionResponse xmlns="https://
<Document>{{ policy_version.document }}</Document> <Document>{{ policy_version.document }}</Document>
<VersionId>{{ policy_version.version_id }}</VersionId> <VersionId>{{ policy_version.version_id }}</VersionId>
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion> <IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ policy_version.create_datetime }}</CreateDate>
</PolicyVersion> </PolicyVersion>
</CreatePolicyVersionResult> </CreatePolicyVersionResult>
<ResponseMetadata> <ResponseMetadata>
@ -879,7 +911,7 @@ GET_POLICY_VERSION_TEMPLATE = """<GetPolicyVersionResponse xmlns="https://iam.am
<Document>{{ policy_version.document }}</Document> <Document>{{ policy_version.document }}</Document>
<VersionId>{{ policy_version.version_id }}</VersionId> <VersionId>{{ policy_version.version_id }}</VersionId>
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion> <IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ policy_version.create_datetime }}</CreateDate>
</PolicyVersion> </PolicyVersion>
</GetPolicyVersionResult> </GetPolicyVersionResult>
<ResponseMetadata> <ResponseMetadata>
@ -896,7 +928,7 @@ LIST_POLICY_VERSIONS_TEMPLATE = """<ListPolicyVersionsResponse xmlns="https://ia
<Document>{{ policy_version.document }}</Document> <Document>{{ policy_version.document }}</Document>
<VersionId>{{ policy_version.version_id }}</VersionId> <VersionId>{{ policy_version.version_id }}</VersionId>
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion> <IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ policy_version.create_datetime }}</CreateDate>
</member> </member>
{% endfor %} {% endfor %}
</Versions> </Versions>
@ -912,7 +944,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
<InstanceProfiles> <InstanceProfiles>
{% for instance in instance_profiles %} {% for instance in instance_profiles %}
<member> <member>
<Id>{{ instance.id }}</Id> <InstanceProfileId>{{ instance.id }}</InstanceProfileId>
<Roles> <Roles>
{% for role in instance.roles %} {% for role in instance.roles %}
<member> <member>
@ -920,7 +952,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -928,7 +960,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
<InstanceProfileName>{{ instance.name }}</InstanceProfileName> <InstanceProfileName>{{ instance.name }}</InstanceProfileName>
<Path>{{ instance.path }}</Path> <Path>{{ instance.path }}</Path>
<Arn>{{ instance.arn }}</Arn> <Arn>{{ instance.arn }}</Arn>
<CreateDate>2012-05-09T16:27:03Z</CreateDate> <CreateDate>{{ instance.create_date }}</CreateDate>
</member> </member>
{% endfor %} {% endfor %}
</InstanceProfiles> </InstanceProfiles>
@ -1199,8 +1231,8 @@ LIST_USER_POLICIES_TEMPLATE = """<ListUserPoliciesResponse>
<member>{{ policy }}</member> <member>{{ policy }}</member>
{% endfor %} {% endfor %}
</PolicyNames> </PolicyNames>
</ListUserPoliciesResult>
<IsTruncated>false</IsTruncated> <IsTruncated>false</IsTruncated>
</ListUserPoliciesResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId> <RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata> </ResponseMetadata>
@ -1277,7 +1309,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
<InstanceProfiles> <InstanceProfiles>
{% for profile in instance_profiles %} {% for profile in instance_profiles %}
<member> <member>
<Id>{{ profile.id }}</Id> <InstanceProfileId>{{ profile.id }}</InstanceProfileId>
<Roles> <Roles>
{% for role in profile.roles %} {% for role in profile.roles %}
<member> <member>
@ -1285,7 +1317,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -1293,7 +1325,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
<InstanceProfileName>{{ profile.name }}</InstanceProfileName> <InstanceProfileName>{{ profile.name }}</InstanceProfileName>
<Path>{{ profile.path }}</Path> <Path>{{ profile.path }}</Path>
<Arn>{{ profile.arn }}</Arn> <Arn>{{ profile.arn }}</Arn>
<CreateDate>2012-05-09T16:27:11Z</CreateDate> <CreateDate>{{ profile.create_date }}</CreateDate>
</member> </member>
{% endfor %} {% endfor %}
</InstanceProfiles> </InstanceProfiles>
@ -1382,7 +1414,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<Path>{{ user.path }}</Path> <Path>{{ user.path }}</Path>
<UserName>{{ user.name }}</UserName> <UserName>{{ user.name }}</UserName>
<Arn>{{ user.arn }}</Arn> <Arn>{{ user.arn }}</Arn>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ user.created_iso_8601 }}</CreateDate>
</member> </member>
{% endfor %} {% endfor %}
</UserDetailList> </UserDetailList>
@ -1401,7 +1433,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<GroupName>{{ group.name }}</GroupName> <GroupName>{{ group.name }}</GroupName>
<Path>{{ group.path }}</Path> <Path>{{ group.path }}</Path>
<Arn>{{ group.arn }}</Arn> <Arn>{{ group.arn }}</Arn>
<CreateDate>2012-05-09T16:27:11Z</CreateDate> <CreateDate>{{ group.create_date }}</CreateDate>
<GroupPolicyList/> <GroupPolicyList/>
</member> </member>
{% endfor %} {% endfor %}
@ -1421,7 +1453,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<InstanceProfileList> <InstanceProfileList>
{% for profile in instance_profiles %} {% for profile in instance_profiles %}
<member> <member>
<Id>{{ profile.id }}</Id> <InstanceProfileId>{{ profile.id }}</InstanceProfileId>
<Roles> <Roles>
{% for role in profile.roles %} {% for role in profile.roles %}
<member> <member>
@ -1429,7 +1461,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -1437,7 +1469,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<InstanceProfileName>{{ profile.name }}</InstanceProfileName> <InstanceProfileName>{{ profile.name }}</InstanceProfileName>
<Path>{{ profile.path }}</Path> <Path>{{ profile.path }}</Path>
<Arn>{{ profile.arn }}</Arn> <Arn>{{ profile.arn }}</Arn>
<CreateDate>2012-05-09T16:27:11Z</CreateDate> <CreateDate>{{ profile.create_date }}</CreateDate>
</member> </member>
{% endfor %} {% endfor %}
</InstanceProfileList> </InstanceProfileList>
@ -1445,7 +1477,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<Arn>{{ role.arn }}</Arn> <Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName> <RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2014-07-30T17:09:20Z</CreateDate> <CreateDate>{{ role.create_date }}</CreateDate>
<RoleId>{{ role.id }}</RoleId> <RoleId>{{ role.id }}</RoleId>
</member> </member>
{% endfor %} {% endfor %}
@ -1474,9 +1506,9 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
</PolicyVersionList> </PolicyVersionList>
<Arn>{{ policy.arn }}</Arn> <Arn>{{ policy.arn }}</Arn>
<AttachmentCount>1</AttachmentCount> <AttachmentCount>1</AttachmentCount>
<CreateDate>2012-05-09T16:27:11Z</CreateDate> <CreateDate>{{ policy.create_datetime }}</CreateDate>
<IsAttachable>true</IsAttachable> <IsAttachable>true</IsAttachable>
<UpdateDate>2012-05-09T16:27:11Z</UpdateDate> <UpdateDate>{{ policy.update_datetime }}</UpdateDate>
</member> </member>
{% endfor %} {% endfor %}
</Policies> </Policies>
@ -1485,3 +1517,53 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
<RequestId>92e79ae7-7399-11e4-8c85-4b53eEXAMPLE</RequestId> <RequestId>92e79ae7-7399-11e4-8c85-4b53eEXAMPLE</RequestId>
</ResponseMetadata> </ResponseMetadata>
</GetAccountAuthorizationDetailsResponse>""" </GetAccountAuthorizationDetailsResponse>"""
UPLOAD_SIGNING_CERTIFICATE_TEMPLATE = """<UploadSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<UploadSigningCertificateResult>
<Certificate>
<UserName>{{ cert.user_name }}</UserName>
<CertificateId>{{ cert.id }}</CertificateId>
<CertificateBody>{{ cert.body }}</CertificateBody>
<Status>{{ cert.status }}</Status>
</Certificate>
</UploadSigningCertificateResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</UploadSigningCertificateResponse>"""
UPDATE_SIGNING_CERTIFICATE_TEMPLATE = """<UpdateSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
</ResponseMetadata>
</UpdateSigningCertificateResponse>"""
DELETE_SIGNING_CERTIFICATE_TEMPLATE = """<DeleteSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</DeleteSigningCertificateResponse>"""
LIST_SIGNING_CERTIFICATES_TEMPLATE = """<ListSigningCertificatesResponse>
<ListSigningCertificatesResult>
<UserName>{{ user_name }}</UserName>
<Certificates>
{% for cert in certificates %}
<member>
<UserName>{{ user_name }}</UserName>
<CertificateId>{{ cert.id }}</CertificateId>
<CertificateBody>{{ cert.body }}</CertificateBody>
<Status>{{ cert.status }}</Status>
</member>
{% endfor %}
</Certificates>
<IsTruncated>false</IsTruncated>
</ListSigningCertificatesResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</ListSigningCertificatesResponse>"""

View File

@ -12,8 +12,7 @@ def random_alphanumeric(length):
) )
def random_resource_id(): def random_resource_id(size=20):
size = 20
chars = list(range(10)) + list(string.ascii_lowercase) chars = list(range(10)) + list(string.ascii_lowercase)
return ''.join(six.text_type(random.choice(chars)) for x in range(size)) return ''.join(six.text_type(random.choice(chars)) for x in range(size))

View File

@ -178,3 +178,13 @@ class InvalidStorageClass(S3ClientError):
"InvalidStorageClass", "InvalidStorageClass",
"The storage class you specified is not valid", "The storage class you specified is not valid",
*args, **kwargs) *args, **kwargs)
class DuplicateTagKeys(S3ClientError):
code = 400
def __init__(self, *args, **kwargs):
super(DuplicateTagKeys, self).__init__(
"InvalidTag",
"Cannot provide multiple Tags with the same key",
*args, **kwargs)

View File

@ -15,7 +15,7 @@ from bisect import insort
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
from .exceptions import BucketAlreadyExists, MissingBucket, InvalidPart, EntityTooSmall, MissingKey, \ from .exceptions import BucketAlreadyExists, MissingBucket, InvalidPart, EntityTooSmall, MissingKey, \
InvalidNotificationDestination, MalformedXML, InvalidStorageClass InvalidNotificationDestination, MalformedXML, InvalidStorageClass, DuplicateTagKeys
from .utils import clean_key_name, _VersionedKeyStore from .utils import clean_key_name, _VersionedKeyStore
UPLOAD_ID_BYTES = 43 UPLOAD_ID_BYTES = 43
@ -773,6 +773,9 @@ class S3Backend(BaseBackend):
return key return key
def put_bucket_tagging(self, bucket_name, tagging): def put_bucket_tagging(self, bucket_name, tagging):
tag_keys = [tag.key for tag in tagging.tag_set.tags]
if len(tag_keys) != len(set(tag_keys)):
raise DuplicateTagKeys()
bucket = self.get_bucket(bucket_name) bucket = self.get_bucket(bucket_name)
bucket.set_tags(tagging) bucket.set_tags(tagging)

View File

@ -10,6 +10,7 @@ import xmltodict
from moto.packages.httpretty.core import HTTPrettyRequest from moto.packages.httpretty.core import HTTPrettyRequest
from moto.core.responses import _TemplateEnvironmentMixin from moto.core.responses import _TemplateEnvironmentMixin
from moto.core.utils import path_url
from moto.s3bucket_path.utils import bucket_name_from_url as bucketpath_bucket_name_from_url, \ from moto.s3bucket_path.utils import bucket_name_from_url as bucketpath_bucket_name_from_url, \
parse_key_name as bucketpath_parse_key_name, is_delete_keys as bucketpath_is_delete_keys parse_key_name as bucketpath_parse_key_name, is_delete_keys as bucketpath_is_delete_keys
@ -487,7 +488,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
if isinstance(request, HTTPrettyRequest): if isinstance(request, HTTPrettyRequest):
path = request.path path = request.path
else: else:
path = request.full_path if hasattr(request, 'full_path') else request.path_url path = request.full_path if hasattr(request, 'full_path') else path_url(request.url)
if self.is_delete_keys(request, path, bucket_name): if self.is_delete_keys(request, path, bucket_name):
return self._bucket_response_delete_keys(request, body, bucket_name, headers) return self._bucket_response_delete_keys(request, body, bucket_name, headers)
@ -708,7 +709,10 @@ class ResponseObject(_TemplateEnvironmentMixin):
# Copy key # Copy key
# you can have a quoted ?version=abc with a version Id, so work on # you can have a quoted ?version=abc with a version Id, so work on
# we need to parse the unquoted string first # we need to parse the unquoted string first
src_key_parsed = urlparse(request.headers.get("x-amz-copy-source")) src_key = request.headers.get("x-amz-copy-source")
if isinstance(src_key, six.binary_type):
src_key = src_key.decode('utf-8')
src_key_parsed = urlparse(src_key)
src_bucket, src_key = unquote(src_key_parsed.path).\ src_bucket, src_key = unquote(src_key_parsed.path).\
lstrip("/").split("/", 1) lstrip("/").split("/", 1)
src_version_id = parse_qs(src_key_parsed.query).get( src_version_id = parse_qs(src_key_parsed.query).get(

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import time import time
import json import json
import uuid
import boto3 import boto3
@ -18,10 +19,6 @@ class SecretsManager(BaseModel):
def __init__(self, region_name, **kwargs): def __init__(self, region_name, **kwargs):
self.region = region_name self.region = region_name
self.secret_id = kwargs.get('secret_id', '')
self.version_id = kwargs.get('version_id', '')
self.version_stage = kwargs.get('version_stage', '')
self.secret_string = ''
class SecretsManagerBackend(BaseBackend): class SecretsManagerBackend(BaseBackend):
@ -29,14 +26,7 @@ class SecretsManagerBackend(BaseBackend):
def __init__(self, region_name=None, **kwargs): def __init__(self, region_name=None, **kwargs):
super(SecretsManagerBackend, self).__init__() super(SecretsManagerBackend, self).__init__()
self.region = region_name self.region = region_name
self.secret_id = kwargs.get('secret_id', '') self.secrets = {}
self.name = kwargs.get('name', '')
self.createdate = int(time.time())
self.secret_string = ''
self.rotation_enabled = False
self.rotation_lambda_arn = ''
self.auto_rotate_after_days = 0
self.version_id = ''
def reset(self): def reset(self):
region_name = self.region region_name = self.region
@ -44,36 +34,50 @@ class SecretsManagerBackend(BaseBackend):
self.__init__(region_name) self.__init__(region_name)
def _is_valid_identifier(self, identifier): def _is_valid_identifier(self, identifier):
return identifier in (self.name, self.secret_id) return identifier in self.secrets
def get_secret_value(self, secret_id, version_id, version_stage): def get_secret_value(self, secret_id, version_id, version_stage):
if not self._is_valid_identifier(secret_id): if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException() raise ResourceNotFoundException()
secret = self.secrets[secret_id]
response = json.dumps({ response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id), "ARN": secret_arn(self.region, secret['secret_id']),
"Name": self.name, "Name": secret['name'],
"VersionId": "A435958A-D821-4193-B719-B7769357AER4", "VersionId": secret['version_id'],
"SecretString": self.secret_string, "SecretString": secret['secret_string'],
"VersionStages": [ "VersionStages": [
"AWSCURRENT", "AWSCURRENT",
], ],
"CreatedDate": "2018-05-23 13:16:57.198000" "CreatedDate": secret['createdate']
}) })
return response return response
def create_secret(self, name, secret_string, **kwargs): def create_secret(self, name, secret_string, tags, **kwargs):
self.secret_string = secret_string generated_version_id = str(uuid.uuid4())
self.secret_id = name
self.name = name secret = {
'secret_string': secret_string,
'secret_id': name,
'name': name,
'createdate': int(time.time()),
'rotation_enabled': False,
'rotation_lambda_arn': '',
'auto_rotate_after_days': 0,
'version_id': generated_version_id,
'tags': tags
}
self.secrets[name] = secret
response = json.dumps({ response = json.dumps({
"ARN": secret_arn(self.region, name), "ARN": secret_arn(self.region, name),
"Name": self.name, "Name": name,
"VersionId": "A435958A-D821-4193-B719-B7769357AER4", "VersionId": generated_version_id,
}) })
return response return response
@ -82,26 +86,23 @@ class SecretsManagerBackend(BaseBackend):
if not self._is_valid_identifier(secret_id): if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException raise ResourceNotFoundException
secret = self.secrets[secret_id]
response = json.dumps({ response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id), "ARN": secret_arn(self.region, secret['secret_id']),
"Name": self.name, "Name": secret['name'],
"Description": "", "Description": "",
"KmsKeyId": "", "KmsKeyId": "",
"RotationEnabled": self.rotation_enabled, "RotationEnabled": secret['rotation_enabled'],
"RotationLambdaARN": self.rotation_lambda_arn, "RotationLambdaARN": secret['rotation_lambda_arn'],
"RotationRules": { "RotationRules": {
"AutomaticallyAfterDays": self.auto_rotate_after_days "AutomaticallyAfterDays": secret['auto_rotate_after_days']
}, },
"LastRotatedDate": None, "LastRotatedDate": None,
"LastChangedDate": None, "LastChangedDate": None,
"LastAccessedDate": None, "LastAccessedDate": None,
"DeletedDate": None, "DeletedDate": None,
"Tags": [ "Tags": secret['tags']
{
"Key": "",
"Value": ""
},
]
}) })
return response return response
@ -141,17 +142,19 @@ class SecretsManagerBackend(BaseBackend):
) )
raise InvalidParameterException(msg) raise InvalidParameterException(msg)
self.version_id = client_request_token or '' secret = self.secrets[secret_id]
self.rotation_lambda_arn = rotation_lambda_arn or ''
secret['version_id'] = client_request_token or ''
secret['rotation_lambda_arn'] = rotation_lambda_arn or ''
if rotation_rules: if rotation_rules:
self.auto_rotate_after_days = rotation_rules.get(rotation_days, 0) secret['auto_rotate_after_days'] = rotation_rules.get(rotation_days, 0)
if self.auto_rotate_after_days > 0: if secret['auto_rotate_after_days'] > 0:
self.rotation_enabled = True secret['rotation_enabled'] = True
response = json.dumps({ response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id), "ARN": secret_arn(self.region, secret['secret_id']),
"Name": self.name, "Name": secret['name'],
"VersionId": self.version_id "VersionId": secret['version_id']
}) })
return response return response

View File

@ -19,9 +19,11 @@ class SecretsManagerResponse(BaseResponse):
def create_secret(self): def create_secret(self):
name = self._get_param('Name') name = self._get_param('Name')
secret_string = self._get_param('SecretString') secret_string = self._get_param('SecretString')
tags = self._get_param('Tags', if_none=[])
return secretsmanager_backends[self.region].create_secret( return secretsmanager_backends[self.region].create_secret(
name=name, name=name,
secret_string=secret_string secret_string=secret_string,
tags=tags
) )
def get_random_password(self): def get_random_password(self):

View File

@ -52,8 +52,9 @@ def random_password(password_length, exclude_characters, exclude_numbers,
def secret_arn(region, secret_id): def secret_arn(region, secret_id):
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format( id_string = ''.join(random.choice(string.ascii_letters) for _ in range(5))
region, secret_id) return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-{2}".format(
region, secret_id, id_string)
def _exclude_characters(password, exclude_characters): def _exclude_characters(password, exclude_characters):

View File

@ -8,7 +8,7 @@ freezegun
flask flask
boto>=2.45.0 boto>=2.45.0
boto3>=1.4.4 boto3>=1.4.4
botocore>=1.8.36 botocore>=1.12.13
six>=1.9 six>=1.9
prompt-toolkit==1.0.14 prompt-toolkit==1.0.14
click==6.7 click==6.7

View File

@ -1,15 +1,28 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import unicode_literals from __future__ import unicode_literals
import codecs
import os
import re
import setuptools import setuptools
from setuptools import setup, find_packages from setuptools import setup, find_packages
import sys import sys
# Borrowed from pip at https://github.com/pypa/pip/blob/62c27dee45625e1b63d1e023b0656310f276e050/setup.py#L11-L15
here = os.path.abspath(os.path.dirname(__file__))
def read(*parts):
# intentionally *not* adding an encoding option to open, See:
# https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
with codecs.open(os.path.join(here, *parts), 'r') as fp:
return fp.read()
install_requires = [ install_requires = [
"Jinja2>=2.7.3", "Jinja2>=2.7.3",
"boto>=2.36.0", "boto>=2.36.0",
"boto3>=1.6.16,<1.8", "boto3>=1.6.16",
"botocore>=1.9.16,<1.11", "botocore>=1.12.13",
"cryptography>=2.3.0", "cryptography>=2.3.0",
"requests>=2.5", "requests>=2.5",
"xmltodict", "xmltodict",
@ -22,7 +35,7 @@ install_requires = [
"mock", "mock",
"docker>=2.5.1", "docker>=2.5.1",
"jsondiff==1.1.2", "jsondiff==1.1.2",
"aws-xray-sdk<0.96,>=0.93", "aws-xray-sdk!=0.96,>=0.93",
"responses>=0.9.0", "responses>=0.9.0",
] ]
@ -40,9 +53,11 @@ else:
setup( setup(
name='moto', name='moto',
version='1.3.6', version='1.3.7',
description='A library that allows your python tests to easily' description='A library that allows your python tests to easily'
' mock out the boto library', ' mock out the boto library',
long_description=read('README.md'),
long_description_content_type='text/markdown',
author='Steve Pulec', author='Steve Pulec',
author_email='spulec@gmail.com', author_email='spulec@gmail.com',
url='https://github.com/spulec/moto', url='https://github.com/spulec/moto',

View File

@ -41,6 +41,56 @@ def test_list_user_pools():
result["UserPools"][0]["Name"].should.equal(name) result["UserPools"][0]["Name"].should.equal(name)
@mock_cognitoidp
def test_list_user_pools_returns_max_items():
conn = boto3.client("cognito-idp", "us-west-2")
# Given 10 user pools
pool_count = 10
for i in range(pool_count):
conn.create_user_pool(PoolName=str(uuid.uuid4()))
max_results = 5
result = conn.list_user_pools(MaxResults=max_results)
result["UserPools"].should.have.length_of(max_results)
result.should.have.key("NextToken")
@mock_cognitoidp
def test_list_user_pools_returns_next_tokens():
conn = boto3.client("cognito-idp", "us-west-2")
# Given 10 user pool clients
pool_count = 10
for i in range(pool_count):
conn.create_user_pool(PoolName=str(uuid.uuid4()))
max_results = 5
result = conn.list_user_pools(MaxResults=max_results)
result["UserPools"].should.have.length_of(max_results)
result.should.have.key("NextToken")
next_token = result["NextToken"]
result_2 = conn.list_user_pools(MaxResults=max_results, NextToken=next_token)
result_2["UserPools"].should.have.length_of(max_results)
result_2.shouldnt.have.key("NextToken")
@mock_cognitoidp
def test_list_user_pools_when_max_items_more_than_total_items():
conn = boto3.client("cognito-idp", "us-west-2")
# Given 10 user pool clients
pool_count = 10
for i in range(pool_count):
conn.create_user_pool(PoolName=str(uuid.uuid4()))
max_results = pool_count + 5
result = conn.list_user_pools(MaxResults=max_results)
result["UserPools"].should.have.length_of(pool_count)
result.shouldnt.have.key("NextToken")
@mock_cognitoidp @mock_cognitoidp
def test_describe_user_pool(): def test_describe_user_pool():
conn = boto3.client("cognito-idp", "us-west-2") conn = boto3.client("cognito-idp", "us-west-2")
@ -140,6 +190,67 @@ def test_list_user_pool_clients():
result["UserPoolClients"][0]["ClientName"].should.equal(client_name) result["UserPoolClients"][0]["ClientName"].should.equal(client_name)
@mock_cognitoidp
def test_list_user_pool_clients_returns_max_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 user pool clients
client_count = 10
for i in range(client_count):
client_name = str(uuid.uuid4())
conn.create_user_pool_client(UserPoolId=user_pool_id,
ClientName=client_name)
max_results = 5
result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
MaxResults=max_results)
result["UserPoolClients"].should.have.length_of(max_results)
result.should.have.key("NextToken")
@mock_cognitoidp
def test_list_user_pool_clients_returns_next_tokens():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 user pool clients
client_count = 10
for i in range(client_count):
client_name = str(uuid.uuid4())
conn.create_user_pool_client(UserPoolId=user_pool_id,
ClientName=client_name)
max_results = 5
result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
MaxResults=max_results)
result["UserPoolClients"].should.have.length_of(max_results)
result.should.have.key("NextToken")
next_token = result["NextToken"]
result_2 = conn.list_user_pool_clients(UserPoolId=user_pool_id,
MaxResults=max_results,
NextToken=next_token)
result_2["UserPoolClients"].should.have.length_of(max_results)
result_2.shouldnt.have.key("NextToken")
@mock_cognitoidp
def test_list_user_pool_clients_when_max_items_more_than_total_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 user pool clients
client_count = 10
for i in range(client_count):
client_name = str(uuid.uuid4())
conn.create_user_pool_client(UserPoolId=user_pool_id,
ClientName=client_name)
max_results = client_count + 5
result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
MaxResults=max_results)
result["UserPoolClients"].should.have.length_of(client_count)
result.shouldnt.have.key("NextToken")
@mock_cognitoidp @mock_cognitoidp
def test_describe_user_pool_client(): def test_describe_user_pool_client():
conn = boto3.client("cognito-idp", "us-west-2") conn = boto3.client("cognito-idp", "us-west-2")
@ -264,6 +375,83 @@ def test_list_identity_providers():
result["Providers"][0]["ProviderType"].should.equal(provider_type) result["Providers"][0]["ProviderType"].should.equal(provider_type)
@mock_cognitoidp
def test_list_identity_providers_returns_max_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 identity providers linked to a user pool
identity_provider_count = 10
for i in range(identity_provider_count):
provider_name = str(uuid.uuid4())
provider_type = "Facebook"
conn.create_identity_provider(
UserPoolId=user_pool_id,
ProviderName=provider_name,
ProviderType=provider_type,
ProviderDetails={},
)
max_results = 5
result = conn.list_identity_providers(UserPoolId=user_pool_id,
MaxResults=max_results)
result["Providers"].should.have.length_of(max_results)
result.should.have.key("NextToken")
@mock_cognitoidp
def test_list_identity_providers_returns_next_tokens():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 identity providers linked to a user pool
identity_provider_count = 10
for i in range(identity_provider_count):
provider_name = str(uuid.uuid4())
provider_type = "Facebook"
conn.create_identity_provider(
UserPoolId=user_pool_id,
ProviderName=provider_name,
ProviderType=provider_type,
ProviderDetails={},
)
max_results = 5
result = conn.list_identity_providers(UserPoolId=user_pool_id, MaxResults=max_results)
result["Providers"].should.have.length_of(max_results)
result.should.have.key("NextToken")
next_token = result["NextToken"]
result_2 = conn.list_identity_providers(UserPoolId=user_pool_id,
MaxResults=max_results,
NextToken=next_token)
result_2["Providers"].should.have.length_of(max_results)
result_2.shouldnt.have.key("NextToken")
@mock_cognitoidp
def test_list_identity_providers_when_max_items_more_than_total_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 identity providers linked to a user pool
identity_provider_count = 10
for i in range(identity_provider_count):
provider_name = str(uuid.uuid4())
provider_type = "Facebook"
conn.create_identity_provider(
UserPoolId=user_pool_id,
ProviderName=provider_name,
ProviderType=provider_type,
ProviderDetails={},
)
max_results = identity_provider_count + 5
result = conn.list_identity_providers(UserPoolId=user_pool_id, MaxResults=max_results)
result["Providers"].should.have.length_of(identity_provider_count)
result.shouldnt.have.key("NextToken")
@mock_cognitoidp @mock_cognitoidp
def test_describe_identity_providers(): def test_describe_identity_providers():
conn = boto3.client("cognito-idp", "us-west-2") conn = boto3.client("cognito-idp", "us-west-2")
@ -396,6 +584,62 @@ def test_list_users():
result["Users"][0]["Username"].should.equal(username) result["Users"][0]["Username"].should.equal(username)
@mock_cognitoidp
def test_list_users_returns_limit_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 users
user_count = 10
for i in range(user_count):
conn.admin_create_user(UserPoolId=user_pool_id,
Username=str(uuid.uuid4()))
max_results = 5
result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
result["Users"].should.have.length_of(max_results)
result.should.have.key("PaginationToken")
@mock_cognitoidp
def test_list_users_returns_pagination_tokens():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 users
user_count = 10
for i in range(user_count):
conn.admin_create_user(UserPoolId=user_pool_id,
Username=str(uuid.uuid4()))
max_results = 5
result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
result["Users"].should.have.length_of(max_results)
result.should.have.key("PaginationToken")
next_token = result["PaginationToken"]
result_2 = conn.list_users(UserPoolId=user_pool_id,
Limit=max_results, PaginationToken=next_token)
result_2["Users"].should.have.length_of(max_results)
result_2.shouldnt.have.key("PaginationToken")
@mock_cognitoidp
def test_list_users_when_limit_more_than_total_items():
conn = boto3.client("cognito-idp", "us-west-2")
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
# Given 10 users
user_count = 10
for i in range(user_count):
conn.admin_create_user(UserPoolId=user_pool_id,
Username=str(uuid.uuid4()))
max_results = user_count + 5
result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
result["Users"].should.have.length_of(user_count)
result.shouldnt.have.key("PaginationToken")
@mock_cognitoidp @mock_cognitoidp
def test_admin_disable_user(): def test_admin_disable_user():
conn = boto3.client("cognito-idp", "us-west-2") conn = boto3.client("cognito-idp", "us-west-2")

View File

@ -633,6 +633,21 @@ def test_delete_service():
'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1') 'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1')
@mock_ecs
def test_update_non_existant_service():
client = boto3.client('ecs', region_name='us-east-1')
try:
client.update_service(
cluster="my-clustet",
service="my-service",
desiredCount=0,
)
except ClientError as exc:
error_code = exc.response['Error']['Code']
error_code.should.equal('ServiceNotFoundException')
else:
raise Exception("Didn't raise ClientError")
@mock_ec2 @mock_ec2
@mock_ecs @mock_ecs

View File

@ -14,6 +14,19 @@ from nose.tools import raises
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
MOCK_CERT = """-----BEGIN CERTIFICATE-----
MIIBpzCCARACCQCY5yOdxCTrGjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQKDAxt
b3RvIHRlc3RpbmcwIBcNMTgxMTA1MTkwNTIwWhgPMjI5MjA4MTkxOTA1MjBaMBcx
FTATBgNVBAoMDG1vdG8gdGVzdGluZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEA1Jn3g2h7LD3FLqdpcYNbFXCS4V4eDpuTCje9vKFcC3pi/01147X3zdfPy8Mt
ZhKxcREOwm4NXykh23P9KW7fBovpNwnbYsbPqj8Hf1ZaClrgku1arTVhEnKjx8zO
vaR/bVLCss4uE0E0VM1tJn/QGQsfthFsjuHtwx8uIWz35tUCAwEAATANBgkqhkiG
9w0BAQsFAAOBgQBWdOQ7bDc2nWkUhFjZoNIZrqjyNdjlMUndpwREVD7FQ/DuxJMj
FyDHrtlrS80dPUQWNYHw++oACDpWO01LGLPPrGmuO/7cOdojPEd852q5gd+7W9xt
8vUH+pBa6IBLbvBp+szli51V3TLSWcoyy4ceJNQU2vCkTLoFdS0RLd/7tQ==
-----END CERTIFICATE-----"""
@mock_iam_deprecated() @mock_iam_deprecated()
def test_get_all_server_certs(): def test_get_all_server_certs():
conn = boto.connect_iam() conn = boto.connect_iam()
@ -108,6 +121,10 @@ def test_create_role_and_instance_profile():
conn.list_roles().roles[0].role_name.should.equal('my-role') conn.list_roles().roles[0].role_name.should.equal('my-role')
# Test with an empty path:
profile = conn.create_instance_profile('my-other-profile')
profile.path.should.equal('/')
@mock_iam_deprecated() @mock_iam_deprecated()
def test_remove_role_from_instance_profile(): def test_remove_role_from_instance_profile():
@ -700,10 +717,10 @@ def test_get_account_authorization_details():
import json import json
conn = boto3.client('iam', region_name='us-east-1') conn = boto3.client('iam', region_name='us-east-1')
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
conn.create_user(Path='/', UserName='testCloudAuxUser') conn.create_user(Path='/', UserName='testUser')
conn.create_group(Path='/', GroupName='testCloudAuxGroup') conn.create_group(Path='/', GroupName='testGroup')
conn.create_policy( conn.create_policy(
PolicyName='testCloudAuxPolicy', PolicyName='testPolicy',
Path='/', Path='/',
PolicyDocument=json.dumps({ PolicyDocument=json.dumps({
"Version": "2012-10-17", "Version": "2012-10-17",
@ -715,46 +732,110 @@ def test_get_account_authorization_details():
} }
] ]
}), }),
Description='Test CloudAux Policy' Description='Test Policy'
) )
conn.create_instance_profile(InstanceProfileName='ipn')
conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role')
result = conn.get_account_authorization_details(Filter=['Role']) result = conn.get_account_authorization_details(Filter=['Role'])
len(result['RoleDetailList']) == 1 assert len(result['RoleDetailList']) == 1
len(result['UserDetailList']) == 0 assert len(result['UserDetailList']) == 0
len(result['GroupDetailList']) == 0 assert len(result['GroupDetailList']) == 0
len(result['Policies']) == 0 assert len(result['Policies']) == 0
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
result = conn.get_account_authorization_details(Filter=['User']) result = conn.get_account_authorization_details(Filter=['User'])
len(result['RoleDetailList']) == 0 assert len(result['RoleDetailList']) == 0
len(result['UserDetailList']) == 1 assert len(result['UserDetailList']) == 1
len(result['GroupDetailList']) == 0 assert len(result['GroupDetailList']) == 0
len(result['Policies']) == 0 assert len(result['Policies']) == 0
result = conn.get_account_authorization_details(Filter=['Group']) result = conn.get_account_authorization_details(Filter=['Group'])
len(result['RoleDetailList']) == 0 assert len(result['RoleDetailList']) == 0
len(result['UserDetailList']) == 0 assert len(result['UserDetailList']) == 0
len(result['GroupDetailList']) == 1 assert len(result['GroupDetailList']) == 1
len(result['Policies']) == 0 assert len(result['Policies']) == 0
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy']) result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
len(result['RoleDetailList']) == 0 assert len(result['RoleDetailList']) == 0
len(result['UserDetailList']) == 0 assert len(result['UserDetailList']) == 0
len(result['GroupDetailList']) == 0 assert len(result['GroupDetailList']) == 0
len(result['Policies']) == 1 assert len(result['Policies']) == 1
# Check for greater than 1 since this should always be greater than one but might change. # Check for greater than 1 since this should always be greater than one but might change.
# See iam/aws_managed_policies.py # See iam/aws_managed_policies.py
result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy']) result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy'])
len(result['RoleDetailList']) == 0 assert len(result['RoleDetailList']) == 0
len(result['UserDetailList']) == 0 assert len(result['UserDetailList']) == 0
len(result['GroupDetailList']) == 0 assert len(result['GroupDetailList']) == 0
len(result['Policies']) > 1 assert len(result['Policies']) > 1
result = conn.get_account_authorization_details() result = conn.get_account_authorization_details()
len(result['RoleDetailList']) == 1 assert len(result['RoleDetailList']) == 1
len(result['UserDetailList']) == 1 assert len(result['UserDetailList']) == 1
len(result['GroupDetailList']) == 1 assert len(result['GroupDetailList']) == 1
len(result['Policies']) > 1 assert len(result['Policies']) > 1
@mock_iam
def test_signing_certs():
client = boto3.client('iam', region_name='us-east-1')
# Create the IAM user first:
client.create_user(UserName='testing')
# Upload the cert:
resp = client.upload_signing_certificate(UserName='testing', CertificateBody=MOCK_CERT)['Certificate']
cert_id = resp['CertificateId']
assert resp['UserName'] == 'testing'
assert resp['Status'] == 'Active'
assert resp['CertificateBody'] == MOCK_CERT
assert resp['CertificateId']
# Upload a the cert with an invalid body:
with assert_raises(ClientError) as ce:
client.upload_signing_certificate(UserName='testing', CertificateBody='notacert')
assert ce.exception.response['Error']['Code'] == 'MalformedCertificate'
# Upload with an invalid user:
with assert_raises(ClientError):
client.upload_signing_certificate(UserName='notauser', CertificateBody=MOCK_CERT)
# Update:
client.update_signing_certificate(UserName='testing', CertificateId=cert_id, Status='Inactive')
with assert_raises(ClientError):
client.update_signing_certificate(UserName='notauser', CertificateId=cert_id, Status='Inactive')
with assert_raises(ClientError) as ce:
client.update_signing_certificate(UserName='testing', CertificateId='x' * 32, Status='Inactive')
assert ce.exception.response['Error']['Message'] == 'The Certificate with id {id} cannot be found.'.format(
id='x' * 32)
# List the certs:
resp = client.list_signing_certificates(UserName='testing')['Certificates']
assert len(resp) == 1
assert resp[0]['CertificateBody'] == MOCK_CERT
assert resp[0]['Status'] == 'Inactive' # Changed with the update call above.
with assert_raises(ClientError):
client.list_signing_certificates(UserName='notauser')
# Delete:
client.delete_signing_certificate(UserName='testing', CertificateId=cert_id)
with assert_raises(ClientError):
client.delete_signing_certificate(UserName='notauser', CertificateId=cert_id)
with assert_raises(ClientError) as ce:
client.delete_signing_certificate(UserName='testing', CertificateId=cert_id)
assert ce.exception.response['Error']['Message'] == 'The Certificate with id {id} cannot be found.'.format(
id=cert_id)
# Verify that it's not in the list:
resp = client.list_signing_certificates(UserName='testing')
assert not resp['Certificates']

View File

@ -1553,6 +1553,24 @@ def test_boto3_put_bucket_tagging():
}) })
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
# With duplicate tag keys:
with assert_raises(ClientError) as err:
resp = s3.put_bucket_tagging(Bucket=bucket_name,
Tagging={
"TagSet": [
{
"Key": "TagOne",
"Value": "ValueOne"
},
{
"Key": "TagOne",
"Value": "ValueOneAgain"
}
]
})
e = err.exception
e.response["Error"]["Code"].should.equal("InvalidTag")
e.response["Error"]["Message"].should.equal("Cannot provide multiple Tags with the same key")
@mock_s3 @mock_s3
def test_boto3_get_bucket_tagging(): def test_boto3_get_bucket_tagging():

View File

@ -39,12 +39,28 @@ def test_create_secret():
conn = boto3.client('secretsmanager', region_name='us-east-1') conn = boto3.client('secretsmanager', region_name='us-east-1')
result = conn.create_secret(Name='test-secret', SecretString="foosecret") result = conn.create_secret(Name='test-secret', SecretString="foosecret")
assert result['ARN'] == ( assert result['ARN']
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
assert result['Name'] == 'test-secret' assert result['Name'] == 'test-secret'
secret = conn.get_secret_value(SecretId='test-secret') secret = conn.get_secret_value(SecretId='test-secret')
assert secret['SecretString'] == 'foosecret' assert secret['SecretString'] == 'foosecret'
@mock_secretsmanager
def test_create_secret_with_tags():
conn = boto3.client('secretsmanager', region_name='us-east-1')
secret_name = 'test-secret-with-tags'
result = conn.create_secret(
Name=secret_name,
SecretString="foosecret",
Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]
)
assert result['ARN']
assert result['Name'] == secret_name
secret_value = conn.get_secret_value(SecretId=secret_name)
assert secret_value['SecretString'] == 'foosecret'
secret_details = conn.describe_secret(SecretId=secret_name)
assert secret_details['Tags'] == [{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]
@mock_secretsmanager @mock_secretsmanager
def test_get_random_password_default_length(): def test_get_random_password_default_length():
conn = boto3.client('secretsmanager', region_name='us-west-2') conn = boto3.client('secretsmanager', region_name='us-west-2')
@ -159,10 +175,17 @@ def test_describe_secret():
conn.create_secret(Name='test-secret', conn.create_secret(Name='test-secret',
SecretString='foosecret') SecretString='foosecret')
conn.create_secret(Name='test-secret-2',
SecretString='barsecret')
secret_description = conn.describe_secret(SecretId='test-secret') secret_description = conn.describe_secret(SecretId='test-secret')
secret_description_2 = conn.describe_secret(SecretId='test-secret-2')
assert secret_description # Returned dict is not empty assert secret_description # Returned dict is not empty
assert secret_description['ARN'] == ( assert secret_description['Name'] == ('test-secret')
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad') assert secret_description['ARN'] != '' # Test arn not empty
assert secret_description_2['Name'] == ('test-secret-2')
assert secret_description_2['ARN'] != '' # Test arn not empty
@mock_secretsmanager @mock_secretsmanager
def test_describe_secret_that_does_not_exist(): def test_describe_secret_that_does_not_exist():
@ -190,9 +213,7 @@ def test_rotate_secret():
rotated_secret = conn.rotate_secret(SecretId=secret_name) rotated_secret = conn.rotate_secret(SecretId=secret_name)
assert rotated_secret assert rotated_secret
assert rotated_secret['ARN'] == ( assert rotated_secret['ARN'] != '' # Test arn not empty
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad'
)
assert rotated_secret['Name'] == secret_name assert rotated_secret['Name'] == secret_name
assert rotated_secret['VersionId'] != '' assert rotated_secret['VersionId'] != ''

View File

@ -82,12 +82,21 @@ def test_create_secret():
headers={ headers={
"X-Amz-Target": "secretsmanager.CreateSecret"}, "X-Amz-Target": "secretsmanager.CreateSecret"},
) )
res_2 = test_client.post('/',
data={"Name": "test-secret-2",
"SecretString": "bar-secret"},
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"},
)
json_data = json.loads(res.data.decode("utf-8")) json_data = json.loads(res.data.decode("utf-8"))
assert json_data['ARN'] == ( assert json_data['ARN'] != ''
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
assert json_data['Name'] == 'test-secret' assert json_data['Name'] == 'test-secret'
json_data_2 = json.loads(res_2.data.decode("utf-8"))
assert json_data_2['ARN'] != ''
assert json_data_2['Name'] == 'test-secret-2'
@mock_secretsmanager @mock_secretsmanager
def test_describe_secret(): def test_describe_secret():
@ -108,11 +117,29 @@ def test_describe_secret():
}, },
) )
create_secret_2 = test_client.post('/',
data={"Name": "test-secret-2",
"SecretString": "barsecret"},
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"
},
)
describe_secret_2 = test_client.post('/',
data={"SecretId": "test-secret-2"},
headers={
"X-Amz-Target": "secretsmanager.DescribeSecret"
},
)
json_data = json.loads(describe_secret.data.decode("utf-8")) json_data = json.loads(describe_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty assert json_data # Returned dict is not empty
assert json_data['ARN'] == ( assert json_data['ARN'] != ''
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad' assert json_data['Name'] == 'test-secret'
)
json_data_2 = json.loads(describe_secret_2.data.decode("utf-8"))
assert json_data_2 # Returned dict is not empty
assert json_data_2['ARN'] != ''
assert json_data_2['Name'] == 'test-secret-2'
@mock_secretsmanager @mock_secretsmanager
def test_describe_secret_that_does_not_exist(): def test_describe_secret_that_does_not_exist():
@ -179,9 +206,7 @@ def test_rotate_secret():
json_data = json.loads(rotate_secret.data.decode("utf-8")) json_data = json.loads(rotate_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty assert json_data # Returned dict is not empty
assert json_data['ARN'] == ( assert json_data['ARN'] != ''
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
)
assert json_data['Name'] == 'test-secret' assert json_data['Name'] == 'test-secret'
assert json_data['VersionId'] == client_request_token assert json_data['VersionId'] == client_request_token