Add iam.create_virtual_mfa_device
This commit is contained in:
parent
b433399cb5
commit
9671730f16
@ -3163,7 +3163,7 @@
|
||||
- [ ] describe_events
|
||||
|
||||
## iam
|
||||
57% implemented
|
||||
59% implemented
|
||||
- [ ] add_client_id_to_open_id_connect_provider
|
||||
- [X] add_role_to_instance_profile
|
||||
- [X] add_user_to_group
|
||||
@ -3184,7 +3184,7 @@
|
||||
- [ ] create_service_linked_role
|
||||
- [ ] create_service_specific_credential
|
||||
- [X] create_user
|
||||
- [ ] create_virtual_mfa_device
|
||||
- [X] create_virtual_mfa_device
|
||||
- [X] deactivate_mfa_device
|
||||
- [X] delete_access_key
|
||||
- [X] delete_account_alias
|
||||
|
@ -98,9 +98,9 @@ class TooManyTags(RESTError):
|
||||
class EntityAlreadyExists(RESTError):
|
||||
code = 409
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, message):
|
||||
super(EntityAlreadyExists, self).__init__(
|
||||
'EntityAlreadyExists', "Unknown")
|
||||
'EntityAlreadyExists', message)
|
||||
|
||||
|
||||
class ValidationError(RESTError):
|
||||
|
@ -1,5 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import json
|
||||
@ -40,6 +43,22 @@ class MFADevice(object):
|
||||
return iso_8601_datetime_without_milliseconds(self.enable_date)
|
||||
|
||||
|
||||
class VirtualMfaDevice(object):
|
||||
def __init__(self, device_name):
|
||||
self.serial_number = 'arn:aws:iam::{0}:mfa{1}'.format(ACCOUNT_ID, device_name)
|
||||
|
||||
random_base32_string = ''.join(random.choice(string.ascii_uppercase + '234567') for _ in range(64))
|
||||
self.base32_string_seed = base64.b64encode(random_base32_string.encode('ascii')).decode('ascii')
|
||||
self.qr_code_png = base64.b64encode(os.urandom(64)) # this would be a generated PNG
|
||||
|
||||
self.enable_date = None
|
||||
self.user_attribute = None
|
||||
|
||||
@property
|
||||
def enabled_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.enable_date)
|
||||
|
||||
|
||||
class Policy(BaseModel):
|
||||
is_attachable = False
|
||||
|
||||
@ -596,6 +615,7 @@ class IAMBackend(BaseBackend):
|
||||
self.open_id_providers = {}
|
||||
self.policy_arn_regex = re.compile(
|
||||
r'^arn:aws:iam::[0-9]*:policy/.*$')
|
||||
self.virtual_mfa_devices = {}
|
||||
super(IAMBackend, self).__init__()
|
||||
|
||||
def _init_managed_policies(self):
|
||||
@ -1250,6 +1270,31 @@ class IAMBackend(BaseBackend):
|
||||
user = self.get_user(user_name)
|
||||
return user.mfa_devices.values()
|
||||
|
||||
def create_virtual_mfa_device(self, device_name, path):
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
if not path.startswith('/') and not path.endswith('/'):
|
||||
raise ValidationError('The specified value for path is invalid. '
|
||||
'It must begin and end with / and contain only alphanumeric characters and/or / characters.')
|
||||
|
||||
if any(not len(part) for part in path.split('/')[1:-1]):
|
||||
raise ValidationError('The specified value for path is invalid. '
|
||||
'It must begin and end with / and contain only alphanumeric characters and/or / characters.')
|
||||
|
||||
if len(path) > 512:
|
||||
raise ValidationError('1 validation error detected: '
|
||||
'Value "{}" at "path" failed to satisfy constraint: '
|
||||
'Member must have length less than or equal to 512')
|
||||
|
||||
device = VirtualMfaDevice(path + device_name)
|
||||
|
||||
if device.serial_number in self.virtual_mfa_devices:
|
||||
raise EntityAlreadyExists('MFADevice entity at the same path and name already exists.')
|
||||
|
||||
self.virtual_mfa_devices[device.serial_number] = device
|
||||
return device
|
||||
|
||||
def delete_user(self, user_name):
|
||||
try:
|
||||
del self.users[user_name]
|
||||
@ -1347,7 +1392,7 @@ class IAMBackend(BaseBackend):
|
||||
open_id_provider = OpenIDConnectProvider(url, thumbprint_list, client_id_list)
|
||||
|
||||
if open_id_provider.arn in self.open_id_providers:
|
||||
raise EntityAlreadyExists
|
||||
raise EntityAlreadyExists('Unknown')
|
||||
|
||||
self.open_id_providers[open_id_provider.arn] = open_id_provider
|
||||
return open_id_provider
|
||||
|
@ -598,6 +598,15 @@ class IamResponse(BaseResponse):
|
||||
template = self.response_template(LIST_MFA_DEVICES_TEMPLATE)
|
||||
return template.render(user_name=user_name, devices=devices)
|
||||
|
||||
def create_virtual_mfa_device(self):
|
||||
path = self._get_param('Path')
|
||||
virtual_mfa_device_name = self._get_param('VirtualMFADeviceName')
|
||||
|
||||
virtual_mfa_device = iam_backend.create_virtual_mfa_device(virtual_mfa_device_name, path)
|
||||
|
||||
template = self.response_template(CREATE_VIRTUAL_MFA_DEVICE_TEMPLATE)
|
||||
return template.render(device=virtual_mfa_device)
|
||||
|
||||
def delete_user(self):
|
||||
user_name = self._get_param('UserName')
|
||||
iam_backend.delete_user(user_name)
|
||||
@ -1600,6 +1609,7 @@ CREDENTIAL_REPORT_GENERATING = """
|
||||
</ResponseMetadata>
|
||||
</GenerateCredentialReportResponse>"""
|
||||
|
||||
|
||||
CREDENTIAL_REPORT_GENERATED = """<GenerateCredentialReportResponse>
|
||||
<GenerateCredentialReportResult>
|
||||
<State>COMPLETE</State>
|
||||
@ -1609,6 +1619,7 @@ CREDENTIAL_REPORT_GENERATED = """<GenerateCredentialReportResponse>
|
||||
</ResponseMetadata>
|
||||
</GenerateCredentialReportResponse>"""
|
||||
|
||||
|
||||
CREDENTIAL_REPORT = """<GetCredentialReportResponse>
|
||||
<GetCredentialReportResult>
|
||||
<Content>{{ report }}</Content>
|
||||
@ -1620,6 +1631,7 @@ CREDENTIAL_REPORT = """<GetCredentialReportResponse>
|
||||
</ResponseMetadata>
|
||||
</GetCredentialReportResponse>"""
|
||||
|
||||
|
||||
LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleResponse>
|
||||
<ListInstanceProfilesForRoleResult>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
@ -1652,6 +1664,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
||||
</ResponseMetadata>
|
||||
</ListInstanceProfilesForRoleResponse>"""
|
||||
|
||||
|
||||
LIST_MFA_DEVICES_TEMPLATE = """<ListMFADevicesResponse>
|
||||
<ListMFADevicesResult>
|
||||
<MFADevices>
|
||||
@ -1670,6 +1683,20 @@ LIST_MFA_DEVICES_TEMPLATE = """<ListMFADevicesResponse>
|
||||
</ListMFADevicesResponse>"""
|
||||
|
||||
|
||||
CREATE_VIRTUAL_MFA_DEVICE_TEMPLATE = """<CreateVirtualMFADeviceResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<CreateVirtualMFADeviceResult>
|
||||
<VirtualMFADevice>
|
||||
<SerialNumber>{{ device.serial_number }}</SerialNumber>
|
||||
<Base32StringSeed>{{ device.base32_string_seed }}</Base32StringSeed>
|
||||
<QRCodePNG>{{ device.qr_code_png }}</QRCodePNG>
|
||||
</VirtualMFADevice>
|
||||
</CreateVirtualMFADeviceResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateVirtualMFADeviceResponse>"""
|
||||
|
||||
|
||||
LIST_ACCOUNT_ALIASES_TEMPLATE = """<ListAccountAliasesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ListAccountAliasesResult>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
|
@ -723,6 +723,83 @@ def test_mfa_devices():
|
||||
len(response['MFADevices']).should.equal(0)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_virtual_mfa_device():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
response = client.create_virtual_mfa_device(
|
||||
VirtualMFADeviceName='test-device'
|
||||
)
|
||||
device = response['VirtualMFADevice']
|
||||
|
||||
device['SerialNumber'].should.equal('arn:aws:iam::123456789012:mfa/test-device')
|
||||
device['Base32StringSeed'].decode('ascii').should.match('[A-Z234567]')
|
||||
device['QRCodePNG'].should_not.be.empty
|
||||
|
||||
response = client.create_virtual_mfa_device(
|
||||
Path='/',
|
||||
VirtualMFADeviceName='test-device-2'
|
||||
)
|
||||
device = response['VirtualMFADevice']
|
||||
|
||||
device['SerialNumber'].should.equal('arn:aws:iam::123456789012:mfa/test-device-2')
|
||||
device['Base32StringSeed'].decode('ascii').should.match('[A-Z234567]')
|
||||
device['QRCodePNG'].should_not.be.empty
|
||||
|
||||
response = client.create_virtual_mfa_device(
|
||||
Path='/test/',
|
||||
VirtualMFADeviceName='test-device'
|
||||
)
|
||||
device = response['VirtualMFADevice']
|
||||
|
||||
device['SerialNumber'].should.equal('arn:aws:iam::123456789012:mfa/test/test-device')
|
||||
device['Base32StringSeed'].decode('ascii').should.match('[A-Z234567]')
|
||||
device['QRCodePNG'].should_not.be.empty
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_virtual_mfa_device_errors():
|
||||
client = boto3.client('iam', region_name='us-east-1')
|
||||
client.create_virtual_mfa_device(
|
||||
VirtualMFADeviceName='test-device'
|
||||
)
|
||||
|
||||
client.create_virtual_mfa_device.when.called_with(
|
||||
VirtualMFADeviceName='test-device'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'MFADevice entity at the same path and name already exists.'
|
||||
)
|
||||
|
||||
client.create_virtual_mfa_device.when.called_with(
|
||||
Path='test',
|
||||
VirtualMFADeviceName='test-device'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The specified value for path is invalid. '
|
||||
'It must begin and end with / and contain only alphanumeric characters and/or / characters.'
|
||||
)
|
||||
|
||||
client.create_virtual_mfa_device.when.called_with(
|
||||
Path='/test//test/',
|
||||
VirtualMFADeviceName='test-device'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'The specified value for path is invalid. '
|
||||
'It must begin and end with / and contain only alphanumeric characters and/or / characters.'
|
||||
)
|
||||
|
||||
too_long_path = '/{}/'.format('b' * 511)
|
||||
client.create_virtual_mfa_device.when.called_with(
|
||||
Path=too_long_path,
|
||||
VirtualMFADeviceName='test-device'
|
||||
).should.throw(
|
||||
ClientError,
|
||||
'1 validation error detected: '
|
||||
'Value "{}" at "path" failed to satisfy constraint: '
|
||||
'Member must have length less than or equal to 512'
|
||||
)
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_delete_user_deprecated():
|
||||
conn = boto.connect_iam()
|
||||
|
Loading…
Reference in New Issue
Block a user