diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index ed245489d..184ee1dc2 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3262,8 +3262,7 @@ - [ ] describe_events ## iam -60% implemented -- [ ] add_client_id_to_open_id_connect_provider +61% implemented- [ ] add_client_id_to_open_id_connect_provider - [X] add_role_to_instance_profile - [X] add_user_to_group - [X] attach_group_policy @@ -3317,7 +3316,7 @@ - [ ] generate_service_last_accessed_details - [X] get_access_key_last_used - [X] get_account_authorization_details -- [ ] get_account_password_policy +- [X] get_account_password_policy - [ ] get_account_summary - [ ] get_context_keys_for_custom_policy - [ ] get_context_keys_for_principal_policy diff --git a/moto/iam/exceptions.py b/moto/iam/exceptions.py index b9b0176e0..3fc084152 100644 --- a/moto/iam/exceptions.py +++ b/moto/iam/exceptions.py @@ -127,4 +127,13 @@ class InvalidInput(RESTError): code = 400 def __init__(self, message): - super(InvalidInput, self).__init__("InvalidInput", message) + super(InvalidInput, self).__init__( + 'InvalidInput', message) + + +class NoSuchEntity(RESTError): + code = 404 + + def __init__(self, message): + super(NoSuchEntity, self).__init__( + 'NoSuchEntity', message) diff --git a/moto/iam/models.py b/moto/iam/models.py index db233e82b..4a4881bf0 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -35,6 +35,7 @@ from .exceptions import ( EntityAlreadyExists, ValidationError, InvalidInput, + NoSuchEntity, ) from .utils import ( random_access_key, @@ -1671,5 +1672,11 @@ class IAMBackend(BaseBackend): require_uppercase_characters ) + def get_account_password_policy(self): + if not self.account_password_policy: + raise NoSuchEntity('The Password Policy with domain name {} cannot be found.'.format(ACCOUNT_ID)) + + return self.account_password_policy + iam_backend = IAMBackend() diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 9bde665b2..d34e7f599 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -857,6 +857,12 @@ class IamResponse(BaseResponse): template = self.response_template(UPDATE_ACCOUNT_PASSWORD_POLICY_TEMPLATE) return template.render() + def get_account_password_policy(self): + account_password_policy = iam_backend.get_account_password_policy() + + template = self.response_template(GET_ACCOUNT_PASSWORD_POLICY_TEMPLATE) + return template.render(password_policy=account_password_policy) + LIST_ENTITIES_FOR_POLICY_TEMPLATE = """ @@ -2196,3 +2202,30 @@ UPDATE_ACCOUNT_PASSWORD_POLICY_TEMPLATE = """7a62c49f-347e-4fc4-9331-6e8eEXAMPLE """ + + +GET_ACCOUNT_PASSWORD_POLICY_TEMPLATE = """ + + + {{ password_policy.allow_users_to_change_password | lower }} + {{ password_policy.expire_passwords | lower }} + {% if password_policy.hard_expiry %} + {{ password_policy.hard_expiry | lower }} + {% endif %} + {% if password_policy.max_password_age %} + {{ password_policy.max_password_age }} + {% endif %} + {{ password_policy.minimum_password_length }} + {% if password_policy.password_reuse_prevention %} + {{ password_policy.password_reuse_prevention }} + {% endif %} + {{ password_policy.require_lowercase_characters | lower }} + {{ password_policy.require_numbers | lower }} + {{ password_policy.require_symbols | lower }} + {{ password_policy.require_uppercase_characters | lower }} + + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index dbcdea5f2..943baa204 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -2200,17 +2200,29 @@ def test_list_open_id_connect_providers(): @mock_iam def test_update_account_password_policy(): client = boto3.client('iam', region_name='us-east-1') + client.update_account_password_policy() + response = client.get_account_password_policy() + response['PasswordPolicy'].should.equal({ + 'AllowUsersToChangePassword': False, + 'ExpirePasswords': False, + 'MinimumPasswordLength': 6, + 'RequireLowercaseCharacters': False, + 'RequireNumbers': False, + 'RequireSymbols': False, + 'RequireUppercaseCharacters': False + }) + @mock_iam def test_update_account_password_policy_errors(): client = boto3.client('iam', region_name='us-east-1') client.update_account_password_policy.when.called_with( - MaxPasswordAge = 1096, - MinimumPasswordLength = 129, - PasswordReusePrevention = 25 + MaxPasswordAge=1096, + MinimumPasswordLength=129, + PasswordReusePrevention=25 ).should.throw( ClientError, '3 validation errors detected: ' @@ -2221,3 +2233,44 @@ def test_update_account_password_policy_errors(): 'Value "1096" at "maxPasswordAge" failed to satisfy constraint: ' 'Member must have value less than or equal to 1095' ) + + +@mock_iam +def test_get_account_password_policy(): + client = boto3.client('iam', region_name='us-east-1') + client.update_account_password_policy( + AllowUsersToChangePassword=True, + HardExpiry=True, + MaxPasswordAge=60, + MinimumPasswordLength=10, + PasswordReusePrevention=3, + RequireLowercaseCharacters=True, + RequireNumbers=True, + RequireSymbols=True, + RequireUppercaseCharacters=True + ) + + response = client.get_account_password_policy() + + response['PasswordPolicy'].should.equal({ + 'AllowUsersToChangePassword': True, + 'ExpirePasswords': True, + 'HardExpiry': True, + 'MaxPasswordAge': 60, + 'MinimumPasswordLength': 10, + 'PasswordReusePrevention': 3, + 'RequireLowercaseCharacters': True, + 'RequireNumbers': True, + 'RequireSymbols': True, + 'RequireUppercaseCharacters': True + }) + + +@mock_iam +def test_get_account_password_policy_errors(): + client = boto3.client('iam', region_name='us-east-1') + + client.get_account_password_policy.when.called_with().should.throw( + ClientError, + 'The Password Policy with domain name 123456789012 cannot be found.' + )