From f608b1290f4a47bde5343aaf6ad4605df9994371 Mon Sep 17 00:00:00 2001 From: Phu Lien Date: Tue, 31 Dec 2024 12:24:33 +0700 Subject: [PATCH] Support generate presigned URL with tagging --- src/aws_signature.erl | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/aws_signature.erl b/src/aws_signature.erl index 7f7d327..f42ad17 100644 --- a/src/aws_signature.erl +++ b/src/aws_signature.erl @@ -224,6 +224,14 @@ sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Me %% a fixed digest value, such as "UNSIGNED-PAYLOAD", when sending requests without %% signing the body, which is expected for S3. %% +%%
`tags'
+%%
+%% Optional tagging of the object when generating a pre-signed URL. +%% The value of `tags' is a binary() in the format, for example: +%% `<<"key1=value1&key2=value2">>'. The actual request to put or get the object +%% must use the exact `tags' value to ensure the signature is calculated +%% correctly. +%%
%% -spec sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Method, URL, Options) -> FinalURL when AccessKeyID :: binary(), @@ -239,7 +247,8 @@ sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Me | {session_token, binary()} | {ttl, non_neg_integer()} | {body, binary()} - | {body_digest, binary()}, + | {body_digest, binary()} + | {tags, binary()}, FinalURL :: binary(). sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Method, URL, Options) when is_binary(AccessKeyID), @@ -253,6 +262,7 @@ sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Me URIEncodePath = proplists:get_value(uri_encode_path, Options, true), TimeToLive = proplists:get_value(ttl, Options, 86400), SessionToken = proplists:get_value(session_token, Options, undefined), + Tags = proplists:get_value(tags, Options, undefined), BodyDigest = case proplists:get_value(body_digest, Options, undefined) of undefined -> @@ -263,7 +273,13 @@ sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Me end, BaseParams = [{<<"X-Amz-Algorithm">>, <<"AWS4-HMAC-SHA256">>}, - {<<"X-Amz-SignedHeaders">>, <<"host">>}], + {<<"X-Amz-SignedHeaders">>, + if Tags == undefined -> + <<"host">>; + Tags =/= undefined -> + <<"host%3Bx-amz-tagging">> + end} + ], URLMap = aws_signature_utils:parse_url(URL), LongDate = format_datetime_long(DateTime), @@ -276,9 +292,15 @@ sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, DateTime, Me FinalQueryParams = add_date_header(FinalQueryParams2, LongDate), HostHeader = host_header_from_url(URLMap), + Headers = + if Tags == undefined -> + [HostHeader]; + Tags =/= undefined -> + [HostHeader, {<<"X-Amz-Tagging">>, Tags}] + end, CanonicalRequest = - canonical_request(Method, URLMap, [HostHeader], BodyDigest, URIEncodePath, FinalQueryParams), + canonical_request(Method, URLMap, Headers, BodyDigest, URIEncodePath, FinalQueryParams), HashedCanonicalRequest = aws_signature_utils:sha256_hexdigest(CanonicalRequest), SigningKey = signing_key(SecretAccessKey, ShortDate, Region, Service), @@ -1055,6 +1077,36 @@ sign_v4_query_params_with_authority_well_known_port_test() -> ?assertEqual(Expected, Actual). +sign_v4_query_params_with_tagging_test() -> + AccessKeyID = <<"AKIAIOSFODNN7EXAMPLE">>, + SecretAccessKey = <<"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY">>, + Region = <<"us-east-1">>, + Service = <<"s3">>, + DateTime = {{2013, 5, 24}, {0, 0, 0}}, + Method = <<"GET">>, + URL = <<"https://examplebucket.s3.amazonaws.com/test.txt">>, + + Expected = + <<"https://examplebucket.s3.amazonaws.com/test.txt?", + "X-Amz-Algorithm=AWS4-HMAC-SHA256&", + "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&", + "X-Amz-Date=20130524T000000Z&", + "X-Amz-Expires=86400&", + "X-Amz-Signature=5c14ef7998d657c3b8293b37abefaef5fa98cc775bcbfffdb2027f4ce05772ef&", + "X-Amz-SignedHeaders=host%3Bx-amz-tagging">>, + + Actual = + sign_v4_query_params(AccessKeyID, + SecretAccessKey, + Region, + Service, + DateTime, + Method, + URL, + [{tags, <<"key1=value1&key2=value2">>}]), + + ?assertEqual(Expected, Actual). + format_date_long_test() -> Expected = <<"20210126T200815Z">>, Actual = format_datetime_long({{2021,1,26}, {20,8,15}}),