Skip to content

Commit

Permalink
Merge pull request #881 from smarlowucf/master
Browse files Browse the repository at this point in the history
Add mfa device endpoints to iam backend.
  • Loading branch information
spulec committed Apr 13, 2017
2 parents be7dc36 + 8b9d685 commit 42f6487
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
60 changes: 60 additions & 0 deletions moto/iam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
ACCOUNT_ID = 123456789012


class MFADevice(object):
"""MFA Device class."""

def __init__(self,
serial_number,
authentication_code_1,
authentication_code_2):
self.enable_date = datetime.now(pytz.utc)
self.serial_number = serial_number
self.authentication_code_1 = authentication_code_1
self.authentication_code_2 = authentication_code_2


class Policy(BaseModel):

is_attachable = False
Expand Down Expand Up @@ -226,6 +239,7 @@ def __init__(self, name, path=None):
datetime.utcnow(),
"%Y-%m-%d-%H-%M-%S"
)
self.mfa_devices = {}
self.policies = {}
self.access_keys = []
self.password = None
Expand All @@ -251,6 +265,9 @@ def get_policy(self, policy_name):
def put_policy(self, policy_name, policy_json):
self.policies[policy_name] = policy_json

def deactivate_mfa_device(self, serial_number):
self.mfa_devices.pop(serial_number)

def delete_policy(self, policy_name):
if policy_name not in self.policies:
raise IAMNotFoundException(
Expand All @@ -263,6 +280,16 @@ def create_access_key(self):
self.access_keys.append(access_key)
return access_key

def enable_mfa_device(self,
serial_number,
authentication_code_1,
authentication_code_2):
self.mfa_devices[serial_number] = MFADevice(
serial_number,
authentication_code_1,
authentication_code_2
)

def get_all_access_keys(self):
return self.access_keys

Expand Down Expand Up @@ -724,6 +751,39 @@ def delete_access_key(self, access_key_id, user_name):
user = self.get_user(user_name)
user.delete_access_key(access_key_id)

def enable_mfa_device(self,
user_name,
serial_number,
authentication_code_1,
authentication_code_2):
"""Enable MFA Device for user."""
user = self.get_user(user_name)
if serial_number in user.mfa_devices:
raise IAMConflictException(
"EntityAlreadyExists",
"Device {0} already exists".format(serial_number)
)

user.enable_mfa_device(
serial_number,
authentication_code_1,
authentication_code_2
)

def deactivate_mfa_device(self, user_name, serial_number):
"""Deactivate and detach MFA Device from user if device exists."""
user = self.get_user(user_name)
if serial_number not in user.mfa_devices:
raise IAMNotFoundException(
"Device {0} not found".format(serial_number)
)

user.deactivate_mfa_device(serial_number)

def list_mfa_devices(self, user_name):
user = self.get_user(user_name)
return user.mfa_devices.values()

def delete_user(self, user_name):
try:
del self.users[user_name]
Expand Down
46 changes: 46 additions & 0 deletions moto/iam/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,35 @@ def delete_access_key(self):
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name='DeleteAccessKey')

def deactivate_mfa_device(self):
user_name = self._get_param('UserName')
serial_number = self._get_param('SerialNumber')

iam_backend.deactivate_mfa_device(user_name, serial_number)
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name='DeactivateMFADevice')

def enable_mfa_device(self):
user_name = self._get_param('UserName')
serial_number = self._get_param('SerialNumber')
authentication_code_1 = self._get_param('AuthenticationCode1')
authentication_code_2 = self._get_param('AuthenticationCode2')

iam_backend.enable_mfa_device(
user_name,
serial_number,
authentication_code_1,
authentication_code_2
)
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name='EnableMFADevice')

def list_mfa_devices(self):
user_name = self._get_param('UserName')
devices = iam_backend.list_mfa_devices(user_name)
template = self.response_template(LIST_MFA_DEVICES_TEMPLATE)
return template.render(user_name=user_name, devices=devices)

def delete_user(self):
user_name = self._get_param('UserName')
iam_backend.delete_user(user_name)
Expand Down Expand Up @@ -922,3 +951,20 @@ def get_credential_report(self):
<RequestId>6a8c3992-99f4-11e1-a4c3-27EXAMPLE804</RequestId>
</ResponseMetadata>
</ListInstanceProfilesForRoleResponse>"""

LIST_MFA_DEVICES_TEMPLATE = """<ListMFADevicesResponse>
<ListMFADevicesResult>
<MFADevices>
{% for device in devices %}
<member>
<UserName>{{ user_name }}</UserName>
<SerialNumber>{{ device.serial_number }}</SerialNumber>
</member>
{% endfor %}
</MFADevices>
<IsTruncated>false</IsTruncated>
</ListMFADevicesResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</ListMFADevicesResponse>"""
23 changes: 23 additions & 0 deletions tests/test_iam/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,29 @@ def test_delete_access_key():
conn.delete_access_key(access_key_id, 'my-user')


@mock_iam()
def test_mfa_devices():
# Test enable device
conn = boto3.client('iam', region_name='us-east-1')
conn.create_user(UserName='my-user')
conn.enable_mfa_device(
UserName='my-user',
SerialNumber='123456789',
AuthenticationCode1='234567',
AuthenticationCode2='987654'
)

# Test list mfa devices
response = conn.list_mfa_devices(UserName='my-user')
device = response['MFADevices'][0]
device['SerialNumber'].should.equal('123456789')

# Test deactivate mfa device
conn.deactivate_mfa_device(UserName='my-user', SerialNumber='123456789')
response = conn.list_mfa_devices(UserName='my-user')
len(response['MFADevices']).should.equal(0)


@mock_iam_deprecated()
def test_delete_user():
conn = boto.connect_iam()
Expand Down

0 comments on commit 42f6487

Please sign in to comment.