Repository: getmoto/moto
Pull request #5942 (2023-02-17 22:20:03)

IAM: get_user() should return PasswordLastUsed-field if set
See #5927 


Patch:
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 602d5ae8b..31554d683 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -1181,6 +1181,13 @@ class User(CloudFormationModel):
     def created_iso_8601(self):
         return iso_8601_datetime_with_milliseconds(self.create_date)
 
+    @property
+    def password_last_used_iso_8601(self):
+        if self.password_last_used is not None:
+            return iso_8601_datetime_with_milliseconds(self.password_last_used)
+        else:
+            return None
+
     def get_policy(self, policy_name):
         policy_json = None
         try:
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index 06f2fa1e7..3a75e0367 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -1805,6 +1805,9 @@ USER_TEMPLATE = """<{{ action }}UserResponse>
          <UserId>{{ user.id }}</UserId>
          <CreateDate>{{ user.created_iso_8601 }}</CreateDate>
          <Arn>{{ user.arn }}</Arn>
+         {% if user.password_last_used_iso_8601 %}
+         <PasswordLastUsed>{{ user.password_last_used_iso_8601 }}</PasswordLastUsed>
+         {% endif %}
          {% if tags %}
          <Tags>
             {% for tag in tags %}
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 237975724..409b24174 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -1827,8 +1827,6 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         if bucket.is_versioned:
             keys = existing_keys + [new_key]
         else:
-            for key in existing_keys:
-                key.dispose()
             keys = [new_key]
         bucket.keys.setlist(key_name, keys)
 
diff --git a/moto/s3/utils.py b/moto/s3/utils.py
index 11c53d59e..d6bd84fb5 100644
--- a/moto/s3/utils.py
+++ b/moto/s3/utils.py
@@ -148,6 +148,12 @@ class _VersionedKeyStore(dict):
         elif not isinstance(list_, list):
             list_ = [list_]
 
+        for existing_version in self.getlist(key, []):
+            # Dispose of any FakeKeys that we will not keep
+            # We should only have FakeKeys here - but we're checking hasattr to be sure
+            if existing_version not in list_ and hasattr(existing_version, "dispose"):
+                existing_version.dispose()
+
         super().__setitem__(key, list_)
 
     def _iteritems(self):


Test patch:
diff --git a/tests/test_iam/test_iam_password_last_used.py b/tests/test_iam/test_iam_password_last_used.py
new file mode 100644
index 000000000..045cc7f27
--- /dev/null
+++ b/tests/test_iam/test_iam_password_last_used.py
@@ -0,0 +1,37 @@
+import boto3
+from datetime import datetime
+from moto import mock_iam, settings
+from moto.backends import get_backend
+from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
+from unittest import SkipTest
+
+
+@mock_iam
+def test_password_last_used():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Can't set password_last_used in ServerMode")
+    client = boto3.client("iam", "us-east-1")
+    username = "test.user"
+    client.create_user(Path="/staff/", UserName=username)["User"]
+    client.create_login_profile(
+        UserName=username, Password="Password1", PasswordResetRequired=False
+    )
+
+    access_key = client.create_access_key(UserName=username)["AccessKey"]
+
+    as_new_user = boto3.resource(
+        "iam",
+        region_name="us-east-1",
+        aws_access_key_id=access_key["AccessKeyId"],
+        aws_secret_access_key=access_key["SecretAccessKey"],
+    )
+
+    # Username is set, but password not yet
+    assert as_new_user.CurrentUser().user_name == username
+    assert not as_new_user.CurrentUser().password_last_used
+
+    iam_backend = get_backend("iam")[ACCOUNT_ID]["global"]
+    iam_backend.users[username].password_last_used = datetime.utcnow()
+
+    # Password is returned now
+    assert as_new_user.CurrentUser().password_last_used
diff --git a/tests/test_s3/test_s3_file_handles.py b/tests/test_s3/test_s3_file_handles.py
index b98fb2cef..cb4366500 100644
--- a/tests/test_s3/test_s3_file_handles.py
+++ b/tests/test_s3/test_s3_file_handles.py
@@ -251,6 +251,17 @@ class TestS3FileHandleClosuresUsingMocks(TestCase):
         with mock_s3():
             pass
 
+    @verify_zero_warnings
+    def test_delete_object_with_version(self):
+        with mock_s3():
+            self.s3.create_bucket(Bucket="foo")
+            self.s3.put_bucket_versioning(
+                Bucket="foo",
+                VersioningConfiguration={"Status": "Enabled", "MFADelete": "Disabled"},
+            )
+            version = self.s3.put_object(Bucket="foo", Key="b", Body="s")["VersionId"]
+            self.s3.delete_object(Bucket="foo", Key="b", VersionId=version)
+
 
 def test_verify_key_can_be_copied_after_disposing():
     # https://github.com/getmoto/moto/issues/5588