diff --git a/CHANGELOG.md b/CHANGELOG.md index 704484b..d28bdc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `httpx_auth.AWS4Auth.default_include_headers` is not available anymore, use `httpx_auth.AWS4Auth` `include_headers` parameter instead to include additional headers if the default does not fit your need (refer to documentation for an exhaustive list). - `httpx_auth.AWS4Auth` `include_headers` values will not be stripped anymore, meaning that you can now include headers prefixed and/or suffixed with blank spaces. -- `httpx_auth.AWS4Auth` query fragment (`#` and everything following) is not considered as part of the canonical query string anymore. Feel free to open an issue if this is one. - `httpx_auth.AWS4Auth` does not includes `date` header by default anymore. You will have to provide it via `include_headers` yourself if you need to. - Note that it should not be required as `httpx_auth.AWS4Auth` is sending `x-amz-date` by default and AWS documentation states that the request date can be specified by using either the HTTP `Date` or the `x-amz-date` header. If both headers are present, `x-amz-date` takes precedence. - `httpx_auth.AWS4Auth` `include_headers` does not needs to include `host`, `content-type` or `x-amz-*` anymore as those headers will always be included. It is now expected to be provided as a list of additional headers. diff --git a/README.md b/README.md index e36c804..5fbec24 100644 --- a/README.md +++ b/README.md @@ -692,7 +692,7 @@ Note that the following changes were made compared to `requests-aws4auth`: - It is not possible to provide an `AWSSigningKey` instance, use explicit parameters instead. - It is not possible to provide `raise_invalid_date` parameter anymore as the date will always be valid. - `host` is not considered as a specific Amazon service anymore (no test specific code). - - Canonical query string computation is entirely based on AWS documentation (and consider undocumented fragment (`#` and following characters) as not part of the query string). + - Canonical query string computation is entirely based on AWS documentation (and consider undocumented fragment (`#` and following characters) as part of the query string). - Canonical uri computation is entirely based on AWS documentation. - Canonical headers computation is entirely based on AWS documentation. diff --git a/httpx_auth/aws.py b/httpx_auth/aws.py index f952383..826937a 100644 --- a/httpx_auth/aws.py +++ b/httpx_auth/aws.py @@ -1,16 +1,14 @@ """ -Provides code for AWSAuth ported to httpx from Sam Washington's requests-aws4auth +Provides code for AWSAuth initially ported to httpx from Sam Washington's requests-aws4auth https://github.com/sam-washington/requests-aws4auth """ import datetime import hashlib import hmac -import re -import shlex from collections import defaultdict from posixpath import normpath -from typing import Generator, Tuple +from typing import Generator from urllib.parse import quote import httpx @@ -282,7 +280,23 @@ def canonical_query_string(url: httpx.URL) -> str: '' You will still need to include the "\n". + + Undocumented: + + As URL fragment are not mentionned in AWS documentation, it is assumed they don't treat it as what it is and part of the query string instead + >>> canonical_query_string(httpx.URL("http://s3.amazonaws.com/examplebucket?#this_will_be_a_parameter=and_its_value")) + '%23this_will_be_a_parameter=and_its_value' + + >>> canonical_query_string(httpx.URL("http://s3.amazonaws.com/examplebucket?#first=1#invalue")) + '%23first=1%23invalue' + + >>> canonical_query_string(httpx.URL("http://s3.amazonaws.com/examplebucket?first#=1&#second=invalue&#")) + '%23second=invalue&first%23=1' """ + if fragment := url.fragment: + url_without_fragment = url.copy_with(fragment=None) + return canonical_query_string(httpx.URL(f"{url_without_fragment}%23{fragment}")) + encoded_params = defaultdict(list) for name, value in url.params.multi_items(): encoded_params[uri_encode(name, is_key=True)].append(uri_encode(value)) diff --git a/tests/aws_signature_v4/test_aws4auth_async.py b/tests/aws_signature_v4/test_aws4auth_async.py index 939fe7c..82231d5 100644 --- a/tests/aws_signature_v4/test_aws4auth_async.py +++ b/tests/aws_signature_v4/test_aws4auth_async.py @@ -686,7 +686,7 @@ async def test_aws_auth_query_reserved_with_fragment(httpx_mock: HTTPXMock): method="POST", match_headers={ "x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "Authorization": "AWS4-HMAC-SHA256 Credential=access_id/20181011/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=40f1e969709d1e89729fd9883dd2caca0ed2a8e9ec6f5fe320b5ee5629291116", + "Authorization": "AWS4-HMAC-SHA256 Credential=access_id/20181011/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=73a30ab39b554b5d6b2d0e6b575b4d108794334a532068a2e388027e7914288f", "x-amz-date": "20181011T150505Z", }, ) diff --git a/tests/aws_signature_v4/test_aws4auth_sync.py b/tests/aws_signature_v4/test_aws4auth_sync.py index 8e2a12d..aed510f 100644 --- a/tests/aws_signature_v4/test_aws4auth_sync.py +++ b/tests/aws_signature_v4/test_aws4auth_sync.py @@ -663,7 +663,7 @@ def test_aws_auth_query_reserved_with_fragment(httpx_mock: HTTPXMock): method="POST", match_headers={ "x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "Authorization": "AWS4-HMAC-SHA256 Credential=access_id/20181011/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=40f1e969709d1e89729fd9883dd2caca0ed2a8e9ec6f5fe320b5ee5629291116", + "Authorization": "AWS4-HMAC-SHA256 Credential=access_id/20181011/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=73a30ab39b554b5d6b2d0e6b575b4d108794334a532068a2e388027e7914288f", "x-amz-date": "20181011T150505Z", }, )