diff --git a/rebar.config b/rebar.config index 616b6736..13b7ca35 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,7 @@ {erl_opts, [nowarn_unused_type, debug_info, {d, maps_support}]}. {deps, [{hackney, "1.18.0"}, {jsx, "3.0.0"}, - {aws_signature, "0.3.1"} + {aws_signature, "0.3.3"} ]}. {project_plugins, [rebar3_hex, rebar3_ex_doc, rebar3_check_app_calls]}. {hex, [{doc, #{provider => ex_doc}}]}. diff --git a/rebar.lock b/rebar.lock index 9e7f637e..6046cf9b 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,5 +1,5 @@ {"1.2.0", -[{<<"aws_signature">>,{pkg,<<"aws_signature">>,<<"0.3.1">>},0}, +[{<<"aws_signature">>,{pkg,<<"aws_signature">>,<<"0.3.3">>},0}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.2">>},1}, {<<"hackney">>,{pkg,<<"hackney">>,<<"1.16.0">>},0}, {<<"idna">>,{pkg,<<"idna">>,<<"6.0.1">>},1}, @@ -11,7 +11,7 @@ {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.5.0">>},2}]}. [ {pkg_hash,[ - {<<"aws_signature">>, <<"67F369094CBD55FFA2BBD8CC713EDE14B195FCFB45C86665CD7C5AD010276148">>}, + {<<"aws_signature">>, <<"5844BEE0D3CC42EEFD21D236BBFAA8AA9B16E2F2B7EE79EDAECB321DB3FB6ADF">>}, {<<"certifi">>, <<"B7CFEAE9D2ED395695DD8201C57A2D019C0C43ECAF8B8BCB9320B40D6662F340">>}, {<<"hackney">>, <<"5096AC8E823E3A441477B2D187E30DD3FFF1A82991A806B2003845CE72CE2D84">>}, {<<"idna">>, <<"1D038FB2E7668CE41FBF681D2C45902E52B3CB9E9C77B55334353B222C2EE50C">>}, @@ -22,7 +22,7 @@ {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, {<<"unicode_util_compat">>, <<"8516502659002CEC19E244EBD90D312183064BE95025A319A6C7E89F4BCCD65B">>}]}, {pkg_hash_ext,[ - {<<"aws_signature">>, <<"50FC4DC1D1F7C2D0A8C63F455B3C66ECD74C1CF4C915C768A636F9227704A674">>}, + {<<"aws_signature">>, <<"87E8F42B8E49002AA8D0350A71D13D69EA91B9AFB4CA9B526AE36DB1D585C924">>}, {<<"certifi">>, <<"3B3B5F36493004AC3455966991EAF6E768CE9884693D9968055AEEEB1E575040">>}, {<<"hackney">>, <<"3BF0BEBBD5D3092A3543B783BF065165FA5D3AD4B899B836810E513064134E18">>}, {<<"idna">>, <<"A02C8A1C4FD601215BB0B0324C8A6986749F807CE35F25449EC9E69758708122">>}, diff --git a/src/aws_s3_presigned_url.erl b/src/aws_s3_presigned_url.erl index fca0e69b..3bb793d9 100644 --- a/src/aws_s3_presigned_url.erl +++ b/src/aws_s3_presigned_url.erl @@ -9,7 +9,8 @@ -module(aws_s3_presigned_url). -export([ make_presigned_v4_url/5, - make_presigned_v4_url/6 + make_presigned_v4_url/6, + make_presigned_v4_url/7 ]). -include_lib("hackney/include/hackney_lib.hrl"). @@ -23,6 +24,11 @@ make_presigned_v4_url(Client0, Method, ExpireSeconds, Bucket, Key) -> -spec make_presigned_v4_url(map(), get | put, integer(), binary(), binary(),path|virtual_host) -> {ok, binary()}. make_presigned_v4_url(Client0, Method, ExpireSeconds, Bucket, Key, Style) -> + make_presigned_v4_url(Client0, Method, ExpireSeconds, Bucket, Key, Style, undefined). + +-spec make_presigned_v4_url(map(), get | put, integer(), binary(), binary(), path|virtual_host, undefined|binary()) -> + {ok, binary()}. +make_presigned_v4_url(Client0, Method, ExpireSeconds, Bucket, Key, Style, Tags) -> MethodBin = aws_request:method_to_binary(Method), Path = build_path(Client0,Bucket,Key,Style), Client = Client0#{service => <<"s3">>}, @@ -38,12 +44,20 @@ make_presigned_v4_url(Client0, Method, ExpireSeconds, Bucket, Key, Style) -> , {body_digest, <<"UNSIGNED-PAYLOAD">>} , {uri_encode_path, false} %% We already encode in build_path/4 ], - Options = case SecurityToken of - undefined -> - Options0; - _ -> - [{session_token, hackney_url:urlencode(SecurityToken)} | Options0] - end, + Options1 = + case SecurityToken of + undefined -> + Options0; + _ -> + [{session_token, hackney_url:urlencode(SecurityToken)} | Options0] + end, + Options = + case Tags of + undefined -> + Options1; + _ -> + [{tags, Tags} | Options1] + end, {ok, aws_signature:sign_v4_query_params(AccessKeyID, SecretAccessKey, Region, Service, Now, MethodBin, URL, Options)}. %%==================================================================== @@ -197,4 +211,26 @@ presigned_url_virtual_host_style_test() -> ?assertEqual(<<"3600">>, proplists:get_value(<<"X-Amz-Expires">>, ParsedQs)), ?assertEqual(<<"Token">>, proplists:get_value(<<"X-Amz-Security-Token">>, ParsedQs)), ?assertEqual(<<"host">>, proplists:get_value(<<"X-Amz-SignedHeaders">>, ParsedQs)). + +presigned_url_tags_test() -> + Client = aws_client:make_temporary_client(<<"AccessKeyID">>, <<"SecretAccessKey">>, + <<"Token">>, <<"eu-west-1">>), + {ok, Url} = aws_s3_presigned_url:make_presigned_v4_url(Client, put, 3600, <<"bucket">>, <<"key">>, path, <<"key1=value1&key2=value2">>), + HackneyUrl = hackney_url:parse_url(Url), + ParsedQs = hackney_url:parse_qs(HackneyUrl#hackney_url.qs), + Credential = proplists:get_value(<<"X-Amz-Credential">>, ParsedQs), + [AccessKeyId, _ShortDate, Region, Service, Request] = binary:split(Credential, <<"/">>, [global]), + ?assertEqual(https, HackneyUrl#hackney_url.scheme), + ?assertEqual(443, HackneyUrl#hackney_url.port), + ?assertEqual("s3.eu-west-1.amazonaws.com", HackneyUrl#hackney_url.host), + ?assertEqual(<<"/bucket/key">>, HackneyUrl#hackney_url.path), + ?assertEqual(7, length(ParsedQs)), + ?assertEqual(<<"AccessKeyID">>, AccessKeyId), + ?assertEqual(<<"eu-west-1">>, Region), + ?assertEqual(<<"s3">>, Service), + ?assertEqual(<<"aws4_request">>, Request), + ?assertEqual(<<"AWS4-HMAC-SHA256">>, proplists:get_value(<<"X-Amz-Algorithm">>, ParsedQs)), + ?assertEqual(<<"3600">>, proplists:get_value(<<"X-Amz-Expires">>, ParsedQs)), + ?assertEqual(<<"Token">>, proplists:get_value(<<"X-Amz-Security-Token">>, ParsedQs)), + ?assertEqual(<<"host;x-amz-tagging">>, proplists:get_value(<<"X-Amz-SignedHeaders">>, ParsedQs)). -endif.