| 
									
										
										
										
											2021-10-12 17:50:36 +00:00
										 |  |  | import boto3 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import pytest | 
					
						
							|  |  |  | import requests | 
					
						
							| 
									
										
										
										
											2021-10-18 19:44:29 +00:00
										 |  |  | import sure  # noqa # pylint: disable=unused-import | 
					
						
							| 
									
										
										
										
											2021-10-12 17:50:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | from botocore.exceptions import ClientError | 
					
						
							|  |  |  | from botocore.handlers import disable_signing | 
					
						
							|  |  |  | from moto import mock_s3 | 
					
						
							|  |  |  | from uuid import uuid4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | DEFAULT_REGION_NAME = "us-east-1" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @pytest.mark.parametrize( | 
					
						
							|  |  |  |     "type_key", [("CanonicalUser", "id", "ID"), ("Group", "url", "URI")] | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | @pytest.mark.parametrize( | 
					
						
							|  |  |  |     "value", ["AllUsers", "http://acs.amazonaws.com/groups/global/AllUsers"] | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | @pytest.mark.parametrize("has_quotes", [True, False]) | 
					
						
							|  |  |  | @pytest.mark.parametrize("readwrite", ["Read", "Write"]) | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_put_object_acl_using_grant(readwrite, type_key, value, has_quotes): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     client = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     bucket_name = str(uuid4()) | 
					
						
							|  |  |  |     bucket = s3.Bucket(bucket_name) | 
					
						
							|  |  |  |     bucket.create() | 
					
						
							|  |  |  |     keyname = "test.txt" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket.put_object(Key=keyname, Body=b"asdf") | 
					
						
							|  |  |  |     _type, key, response_key = type_key | 
					
						
							|  |  |  |     header_value = f'"{value}"' if has_quotes else value | 
					
						
							|  |  |  |     args = { | 
					
						
							|  |  |  |         "Bucket": bucket_name, | 
					
						
							|  |  |  |         "Key": keyname, | 
					
						
							|  |  |  |         f"Grant{readwrite}": f"{key}={header_value}", | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     client.put_object_acl(**args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     grants = client.get_object_acl(Bucket=bucket_name, Key=keyname)["Grants"] | 
					
						
							|  |  |  |     grants.should.have.length_of(1) | 
					
						
							|  |  |  |     grants[0].should.have.key("Grantee").equal({"Type": _type, response_key: value}) | 
					
						
							|  |  |  |     grants[0].should.have.key("Permission").equal(readwrite.upper()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_acl_switching_boto3(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     client = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     bucket = s3.Bucket("foobar") | 
					
						
							|  |  |  |     bucket.create() | 
					
						
							|  |  |  |     keyname = "test.txt" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket.put_object(Key=keyname, Body=b"asdf", ACL="public-read") | 
					
						
							|  |  |  |     client.put_object_acl(ACL="private", Bucket="foobar", Key=keyname) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     grants = client.get_object_acl(Bucket="foobar", Key=keyname)["Grants"] | 
					
						
							|  |  |  |     grants.shouldnt.contain( | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             "Grantee": { | 
					
						
							|  |  |  |                 "Type": "Group", | 
					
						
							|  |  |  |                 "URI": "http://acs.amazonaws.com/groups/global/AllUsers", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             "Permission": "READ", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_acl_switching_nonexistent_key(): | 
					
						
							|  |  |  |     s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     s3.create_bucket(Bucket="mybucket") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as e: | 
					
						
							|  |  |  |         s3.put_object_acl(Bucket="mybucket", Key="nonexistent", ACL="private") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     e.value.response["Error"]["Code"].should.equal("NoSuchKey") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_s3_object_in_public_bucket(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3") | 
					
						
							|  |  |  |     bucket = s3.Bucket("test-bucket") | 
					
						
							|  |  |  |     bucket.create( | 
					
						
							|  |  |  |         ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "us-west-1"} | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     bucket.put_object(Body=b"ABCD", Key="file.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     s3_anonymous = boto3.resource("s3") | 
					
						
							|  |  |  |     s3_anonymous.meta.client.meta.events.register("choose-signer.s3.*", disable_signing) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     contents = ( | 
					
						
							|  |  |  |         s3_anonymous.Object(key="file.txt", bucket_name="test-bucket") | 
					
						
							|  |  |  |         .get()["Body"] | 
					
						
							|  |  |  |         .read() | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     contents.should.equal(b"ABCD") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket.put_object(ACL="private", Body=b"ABCD", Key="file.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as exc: | 
					
						
							|  |  |  |         s3_anonymous.Object(key="file.txt", bucket_name="test-bucket").get() | 
					
						
							|  |  |  |     exc.value.response["Error"]["Code"].should.equal("403") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_s3_object_in_public_bucket_using_multiple_presigned_urls(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3") | 
					
						
							|  |  |  |     bucket = s3.Bucket("test-bucket") | 
					
						
							|  |  |  |     bucket.create( | 
					
						
							|  |  |  |         ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "us-west-1"} | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     bucket.put_object(Body=b"ABCD", Key="file.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     params = {"Bucket": "test-bucket", "Key": "file.txt"} | 
					
						
							|  |  |  |     presigned_url = boto3.client("s3").generate_presigned_url( | 
					
						
							|  |  |  |         "get_object", params, ExpiresIn=900 | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     for i in range(1, 10): | 
					
						
							|  |  |  |         response = requests.get(presigned_url) | 
					
						
							| 
									
										
										
										
											2022-11-17 21:41:08 -01:00
										 |  |  |         assert response.status_code == 200, f"Failed on req number {i}" | 
					
						
							| 
									
										
										
										
											2021-10-12 17:50:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_s3_object_in_private_bucket(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3") | 
					
						
							|  |  |  |     bucket = s3.Bucket("test-bucket") | 
					
						
							|  |  |  |     bucket.create( | 
					
						
							|  |  |  |         ACL="private", CreateBucketConfiguration={"LocationConstraint": "us-west-1"} | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     bucket.put_object(ACL="private", Body=b"ABCD", Key="file.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     s3_anonymous = boto3.resource("s3") | 
					
						
							|  |  |  |     s3_anonymous.meta.client.meta.events.register("choose-signer.s3.*", disable_signing) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as exc: | 
					
						
							|  |  |  |         s3_anonymous.Object(key="file.txt", bucket_name="test-bucket").get() | 
					
						
							|  |  |  |     exc.value.response["Error"]["Code"].should.equal("403") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket.put_object(ACL="public-read", Body=b"ABCD", Key="file.txt") | 
					
						
							|  |  |  |     contents = ( | 
					
						
							|  |  |  |         s3_anonymous.Object(key="file.txt", bucket_name="test-bucket") | 
					
						
							|  |  |  |         .get()["Body"] | 
					
						
							|  |  |  |         .read() | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     contents.should.equal(b"ABCD") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_put_bucket_acl_body(): | 
					
						
							|  |  |  |     s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     s3.create_bucket(Bucket="bucket") | 
					
						
							|  |  |  |     bucket_owner = s3.get_bucket_acl(Bucket="bucket")["Owner"] | 
					
						
							|  |  |  |     s3.put_bucket_acl( | 
					
						
							|  |  |  |         Bucket="bucket", | 
					
						
							|  |  |  |         AccessControlPolicy={ | 
					
						
							|  |  |  |             "Grants": [ | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     "Grantee": { | 
					
						
							|  |  |  |                         "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", | 
					
						
							|  |  |  |                         "Type": "Group", | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     "Permission": "WRITE", | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     "Grantee": { | 
					
						
							|  |  |  |                         "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", | 
					
						
							|  |  |  |                         "Type": "Group", | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     "Permission": "READ_ACP", | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             "Owner": bucket_owner, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     result = s3.get_bucket_acl(Bucket="bucket") | 
					
						
							|  |  |  |     assert len(result["Grants"]) == 2 | 
					
						
							|  |  |  |     for g in result["Grants"]: | 
					
						
							|  |  |  |         assert g["Grantee"]["URI"] == "http://acs.amazonaws.com/groups/s3/LogDelivery" | 
					
						
							|  |  |  |         assert g["Grantee"]["Type"] == "Group" | 
					
						
							|  |  |  |         assert g["Permission"] in ["WRITE", "READ_ACP"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # With one: | 
					
						
							|  |  |  |     s3.put_bucket_acl( | 
					
						
							|  |  |  |         Bucket="bucket", | 
					
						
							|  |  |  |         AccessControlPolicy={ | 
					
						
							|  |  |  |             "Grants": [ | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     "Grantee": { | 
					
						
							|  |  |  |                         "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", | 
					
						
							|  |  |  |                         "Type": "Group", | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     "Permission": "WRITE", | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             "Owner": bucket_owner, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     result = s3.get_bucket_acl(Bucket="bucket") | 
					
						
							|  |  |  |     assert len(result["Grants"]) == 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # With no owner: | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as err: | 
					
						
							|  |  |  |         s3.put_bucket_acl( | 
					
						
							|  |  |  |             Bucket="bucket", | 
					
						
							|  |  |  |             AccessControlPolicy={ | 
					
						
							|  |  |  |                 "Grants": [ | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         "Grantee": { | 
					
						
							|  |  |  |                             "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", | 
					
						
							|  |  |  |                             "Type": "Group", | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         "Permission": "WRITE", | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     assert err.value.response["Error"]["Code"] == "MalformedACLError" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # With incorrect permission: | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as err: | 
					
						
							|  |  |  |         s3.put_bucket_acl( | 
					
						
							|  |  |  |             Bucket="bucket", | 
					
						
							|  |  |  |             AccessControlPolicy={ | 
					
						
							|  |  |  |                 "Grants": [ | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         "Grantee": { | 
					
						
							|  |  |  |                             "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", | 
					
						
							|  |  |  |                             "Type": "Group", | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         "Permission": "lskjflkasdjflkdsjfalisdjflkdsjf", | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |                 "Owner": bucket_owner, | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     assert err.value.response["Error"]["Code"] == "MalformedACLError" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Clear the ACLs: | 
					
						
							|  |  |  |     result = s3.put_bucket_acl( | 
					
						
							|  |  |  |         Bucket="bucket", AccessControlPolicy={"Grants": [], "Owner": bucket_owner} | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     assert not result.get("Grants") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_object_acl_with_presigned_post(): | 
					
						
							|  |  |  |     s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket_name = "imageS3Bucket" | 
					
						
							|  |  |  |     object_name = "text.txt" | 
					
						
							|  |  |  |     fields = {"acl": "public-read"} | 
					
						
							|  |  |  |     file = open("text.txt", "w") | 
					
						
							|  |  |  |     file.write("test") | 
					
						
							|  |  |  |     file.close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     s3.create_bucket(Bucket=bucket_name) | 
					
						
							|  |  |  |     response = s3.generate_presigned_post( | 
					
						
							|  |  |  |         bucket_name, object_name, Fields=fields, ExpiresIn=60000 | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with open(object_name, "rb") as f: | 
					
						
							|  |  |  |         files = {"file": (object_name, f)} | 
					
						
							|  |  |  |         requests.post(response["url"], data=response["fields"], files=files) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response = s3.get_object_acl(Bucket=bucket_name, Key=object_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     assert "Grants" in response | 
					
						
							|  |  |  |     assert len(response["Grants"]) == 2 | 
					
						
							|  |  |  |     assert response["Grants"][1]["Permission"] == "READ" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response = s3.get_object(Bucket=bucket_name, Key=object_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     assert "ETag" in response | 
					
						
							|  |  |  |     assert "Body" in response | 
					
						
							|  |  |  |     os.remove("text.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_acl_setting_boto3(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     client = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     bucket = s3.Bucket("foobar") | 
					
						
							|  |  |  |     bucket.create() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     content = b"imafile" | 
					
						
							|  |  |  |     keyname = "test.txt" | 
					
						
							|  |  |  |     bucket.put_object( | 
					
						
							|  |  |  |         Key=keyname, Body=content, ContentType="text/plain", ACL="public-read" | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     grants = client.get_object_acl(Bucket="foobar", Key=keyname)["Grants"] | 
					
						
							|  |  |  |     grants.should.contain( | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             "Grantee": { | 
					
						
							|  |  |  |                 "Type": "Group", | 
					
						
							|  |  |  |                 "URI": "http://acs.amazonaws.com/groups/global/AllUsers", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             "Permission": "READ", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_acl_setting_via_headers_boto3(): | 
					
						
							|  |  |  |     s3 = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     client = boto3.client("s3", region_name=DEFAULT_REGION_NAME) | 
					
						
							|  |  |  |     bucket = s3.Bucket("foobar") | 
					
						
							|  |  |  |     bucket.create() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     keyname = "test.txt" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bucket.put_object(Key=keyname, Body=b"imafile") | 
					
						
							|  |  |  |     client.put_object_acl(ACL="public-read", Bucket="foobar", Key=keyname) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     grants = client.get_object_acl(Bucket="foobar", Key=keyname)["Grants"] | 
					
						
							|  |  |  |     grants.should.contain( | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             "Grantee": { | 
					
						
							|  |  |  |                 "Type": "Group", | 
					
						
							|  |  |  |                 "URI": "http://acs.amazonaws.com/groups/global/AllUsers", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             "Permission": "READ", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2021-11-09 21:49:37 -01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @mock_s3 | 
					
						
							|  |  |  | def test_raise_exception_for_grant_and_acl(): | 
					
						
							|  |  |  |     client = boto3.client("s3") | 
					
						
							|  |  |  |     s3 = boto3.resource("s3") | 
					
						
							|  |  |  |     bucket_name = "bucketname" | 
					
						
							|  |  |  |     client.create_bucket(Bucket=bucket_name) | 
					
						
							|  |  |  |     bucket = s3.Bucket(bucket_name) | 
					
						
							|  |  |  |     acl = client.get_bucket_acl(Bucket=bucket_name) | 
					
						
							|  |  |  |     acl_grantee_id = acl["Owner"]["ID"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # This should raise an exception or provide some error message, but runs without exception instead. | 
					
						
							|  |  |  |     with pytest.raises(ClientError) as exc: | 
					
						
							|  |  |  |         bucket.put_object( | 
					
						
							|  |  |  |             ACL="bucket-owner-full-control", | 
					
						
							|  |  |  |             Body="example-file-path", | 
					
						
							|  |  |  |             Key="example-key", | 
					
						
							|  |  |  |             ContentType="text/plain", | 
					
						
							|  |  |  |             GrantFullControl=f'id="{acl_grantee_id}"', | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     err = exc.value.response["Error"] | 
					
						
							|  |  |  |     err["Code"].should.equal("InvalidRequest") | 
					
						
							|  |  |  |     err["Message"].should.equal( | 
					
						
							|  |  |  |         "Specifying both Canned ACLs and Header Grants is not allowed" | 
					
						
							|  |  |  |     ) |