diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index e8a2c4c9b..fa43dc37b 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -73,7 +73,7 @@ class BucketAlreadyExists(BucketError): "select a different name and try again" ), *args, - **kwargs + **kwargs, ) @@ -139,7 +139,7 @@ class InvalidPartOrder(S3ClientError): "list must be specified in order by part number." ), *args, - **kwargs + **kwargs, ) @@ -155,7 +155,7 @@ class InvalidPart(S3ClientError): "entity tag might not have matched the part's entity tag." ), *args, - **kwargs + **kwargs, ) @@ -167,7 +167,7 @@ class EntityTooSmall(S3ClientError): "EntityTooSmall", "Your proposed upload is smaller than the minimum allowed object size.", *args, - **kwargs + **kwargs, ) @@ -181,7 +181,7 @@ class InvalidRequest(S3ClientError): method ), *args, - **kwargs + **kwargs, ) @@ -193,7 +193,7 @@ class IllegalLocationConstraintException(S3ClientError): "IllegalLocationConstraintException", "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.", *args, - **kwargs + **kwargs, ) @@ -205,7 +205,7 @@ class MalformedXML(S3ClientError): "MalformedXML", "The XML you provided was not well-formed or did not validate against our published schema", *args, - **kwargs + **kwargs, ) @@ -217,7 +217,7 @@ class MalformedACLError(S3ClientError): "MalformedACLError", "The XML you provided was not well-formed or did not validate against our published schema", *args, - **kwargs + **kwargs, ) @@ -266,7 +266,7 @@ class InvalidNotificationDestination(S3ClientError): "InvalidArgument", "The notification destination service region is not valid for the bucket location constraint", *args, - **kwargs + **kwargs, ) @@ -278,7 +278,7 @@ class InvalidNotificationEvent(S3ClientError): "InvalidArgument", "The event is not supported for notifications", *args, - **kwargs + **kwargs, ) @@ -290,7 +290,7 @@ class InvalidStorageClass(S3ClientError): "InvalidStorageClass", "The storage class you specified is not valid", *args, - **kwargs + **kwargs, ) @@ -311,7 +311,7 @@ class DuplicateTagKeys(S3ClientError): "InvalidTag", "Cannot provide multiple Tags with the same key", *args, - **kwargs + **kwargs, ) @@ -341,7 +341,7 @@ class S3InvalidTokenError(S3ClientError): "InvalidToken", "The provided token is malformed or otherwise invalid.", *args, - **kwargs + **kwargs, ) @@ -353,7 +353,7 @@ class BucketInvalidTokenError(BucketError): "InvalidToken", "The provided token is malformed or otherwise invalid.", *args, - **kwargs + **kwargs, ) @@ -365,7 +365,7 @@ class S3InvalidAccessKeyIdError(S3ClientError): "InvalidAccessKeyId", "The AWS Access Key Id you provided does not exist in our records.", *args, - **kwargs + **kwargs, ) @@ -377,7 +377,7 @@ class BucketInvalidAccessKeyIdError(S3ClientError): "InvalidAccessKeyId", "The AWS Access Key Id you provided does not exist in our records.", *args, - **kwargs + **kwargs, ) @@ -389,7 +389,7 @@ class S3SignatureDoesNotMatchError(S3ClientError): "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", *args, - **kwargs + **kwargs, ) @@ -401,7 +401,7 @@ class BucketSignatureDoesNotMatchError(S3ClientError): "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", *args, - **kwargs + **kwargs, ) @@ -413,7 +413,7 @@ class NoSuchPublicAccessBlockConfiguration(S3ClientError): "NoSuchPublicAccessBlockConfiguration", "The public access block configuration was not found", *args, - **kwargs + **kwargs, ) @@ -425,7 +425,7 @@ class InvalidPublicAccessBlockConfiguration(S3ClientError): "InvalidRequest", "Must specify at least one configuration.", *args, - **kwargs + **kwargs, ) @@ -458,7 +458,7 @@ class NoSuchUpload(S3ClientError): "NoSuchUpload", "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.", *args, - **kwargs + **kwargs, ) @@ -472,7 +472,7 @@ class PreconditionFailed(S3ClientError): "PreconditionFailed", "At least one of the pre-conditions you specified did not hold", condition=failed_condition, - **kwargs + **kwargs, ) @@ -487,7 +487,7 @@ class InvalidRange(S3ClientError): "The requested range is not satisfiable", range_requested=range_requested, actual_size=actual_size, - **kwargs + **kwargs, ) @@ -499,7 +499,7 @@ class InvalidContinuationToken(S3ClientError): "InvalidArgument", "The continuation token provided is incorrect", *args, - **kwargs + **kwargs, ) @@ -556,5 +556,14 @@ class InvalidFilterRuleName(InvalidArgumentError): "FilterRule.Name", value, *args, - **kwargs + **kwargs, + ) + + +class InvalidTagError(S3ClientError): + code = 400 + + def __init__(self, value, *args, **kwargs): + super(InvalidTagError, self).__init__( + "InvalidTag", value, *args, **kwargs, ) diff --git a/moto/s3/models.py b/moto/s3/models.py index 97205af59..1db77064f 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -26,7 +26,7 @@ from moto.core.utils import ( ) from moto.cloudwatch.models import MetricDatum from moto.utilities.tagging_service import TaggingService -from .exceptions import ( +from moto.s3.exceptions import ( AccessDeniedByLock, BucketAlreadyExists, BucketNeedsToBeNew, @@ -45,6 +45,7 @@ from .exceptions import ( InvalidPublicAccessBlockConfiguration, WrongPublicAccessBlockAccountIdError, NoSuchUpload, + InvalidTagError, ) from .cloud_formation import cfn_to_api_encryption, is_replacement_update from .utils import clean_key_name, _VersionedKeyStore, undo_clean_key_name @@ -1633,9 +1634,13 @@ class S3Backend(BaseBackend): def set_key_tags(self, key, tags, key_name=None): if key is None: raise MissingKey(key_name) + boto_tags_dict = self.tagger.convert_dict_to_tags_input(tags) + errmsg = self.tagger.validate_tags(boto_tags_dict) + if errmsg: + raise InvalidTagError(errmsg) self.tagger.delete_all_tags_for_resource(key.arn) self.tagger.tag_resource( - key.arn, [{"Key": k, "Value": v} for (k, v) in tags.items()], + key.arn, boto_tags_dict, ) return key diff --git a/moto/utilities/tagging_service.py b/moto/utilities/tagging_service.py index ca33a2dd0..be5ec49d3 100644 --- a/moto/utilities/tagging_service.py +++ b/moto/utilities/tagging_service.py @@ -156,3 +156,8 @@ class TaggingService: if errors else "" ) + + @staticmethod + def convert_dict_to_tags_input(tags): + """ Given a dictionary, return generic boto params for tags """ + return [{"Key": k, "Value": v} for (k, v) in tags.items()] diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 31ad13c57..9b37485e0 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -34,6 +34,7 @@ import sure # noqa from moto import settings, mock_s3, mock_s3_deprecated, mock_config import moto.s3.models as s3model +from moto.s3.exceptions import InvalidTagError from moto.core.exceptions import InvalidNextTokenException from moto.settings import get_s3_default_key_buffer_size, S3_UPLOAD_PART_MIN_SIZE from uuid import uuid4 @@ -3412,6 +3413,19 @@ def test_boto3_copy_object_with_replacement_tagging(): Bucket="mybucket", Key="original", Body=b"test", Tagging="tag=old" ) + # using system tags will fail + with pytest.raises(ClientError) as err: + client.copy_object( + CopySource={"Bucket": "mybucket", "Key": "original"}, + Bucket="mybucket", + Key="copy1", + TaggingDirective="REPLACE", + Tagging="aws:tag=invalid_key", + ) + + e = err.value + e.response["Error"]["Code"].should.equal("InvalidTag") + client.copy_object( CopySource={"Bucket": "mybucket", "Key": "original"}, Bucket="mybucket", @@ -3937,6 +3951,13 @@ def test_boto3_put_object_with_tagging(): key = "key-with-tags" s3.create_bucket(Bucket=bucket_name) + # using system tags will fail + with pytest.raises(ClientError) as err: + s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="aws:foo=bar") + + e = err.value + e.response["Error"]["Code"].should.equal("InvalidTag") + s3.put_object(Bucket=bucket_name, Key=key, Body="test", Tagging="foo=bar") s3.get_object_tagging(Bucket=bucket_name, Key=key)["TagSet"].should.contain( @@ -4666,6 +4687,17 @@ def test_boto3_put_object_tagging(): s3.put_object(Bucket=bucket_name, Key=key, Body="test") + # using system tags will fail + with pytest.raises(ClientError) as err: + s3.put_object_tagging( + Bucket=bucket_name, + Key=key, + Tagging={"TagSet": [{"Key": "aws:item1", "Value": "foo"},]}, + ) + + e = err.value + e.response["Error"]["Code"].should.equal("InvalidTag") + resp = s3.put_object_tagging( Bucket=bucket_name, Key=key,